merge

Sat, 04 Jan 2025 16:38:48 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 04 Jan 2025 16:38:48 +0100
changeset 431
bb7da585debc
parent 169
fe49cff3c571 (current diff)
parent 430
ea949c0109d8 (diff)
child 433
605bb5dc34f1

merge

--- a/.hgignore	Sun May 23 09:44:43 2021 +0200
+++ b/.hgignore	Sat Jan 04 16:38:48 2025 +0100
@@ -1,10 +1,8 @@
 relre:^build/.*$
 relre:^config.mk$
 relre:^core$
-relre:^ui/wpf/UIcore/obj
-relre:^ui/wpf/UIwrapper/.vs
-relre:^ui/wpf/UIwrapper/UIwrapper.VC
-relre:^ui/wpf/UIwrapper/UIwrapper/Debug
-relre:^ui/wpf/UIwrapper/UIwrapper/Release
-relre:^ui/wpf/UIwrapper/UIwrapper/x64
-relre:^ui/wpf/UIwrapper/ipch
\ No newline at end of file
+relre:^make/vs/.vs/.*
+relre:^make/vs/packages/.*
+relre:^make/vs/.*vcxproj\.user
+relre:^make/xcode/toolkit/toolkit.xcodeproj/xcuserdata/.*
+relre:^make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/xcuserdata/.*
\ No newline at end of file
--- a/application/Makefile	Sun May 23 09:44:43 2021 +0200
+++ b/application/Makefile	Sat Jan 04 16:38:48 2025 +0100
@@ -26,20 +26,20 @@
 # POSSIBILITY OF SUCH DAMAGE.
 #
 
-BUILD_ROOT = ../
+BUILD_ROOT = ..
 include ../config.mk
 
 CFLAGS += -I../ui/ -I../ucx
 
 SRC = main.c
 
-OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
+OBJ = $(SRC:%.c=../build/application/%$(OBJ_EXT))
 
 all: ../build/bin/mk12
 
 ../build/bin/mk12: $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
-	$(LD) -o ../build/bin/mk12$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS)
+	$(CC) -o ../build/bin/mk12$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS)
 
-../build/application/%.$(OBJ_EXT): %.c
+../build/application/%$(OBJ_EXT): %.c
 	$(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
 
--- a/application/main.c	Sun May 23 09:44:43 2021 +0200
+++ b/application/main.c	Sat Jan 04 16:38:48 2025 +0100
@@ -28,62 +28,433 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <ui/ui.h>
-#include <ucx/buffer.h>
-#include <ucx/utils.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+
+#if !defined(UI_COCOA) && !defined(UI_MOTIF)
 
 typedef struct {
+    UiString *str1;
+    UiString *str2;
+    UiString *path;
     UiText *text;
+    UiDouble *progress;
+    UiList *list;
+    UiList *list2;
+    UiList *menulist;
+    UiInteger *radio;
+    UiInteger *tabview;
+    UiGeneric *image;
+    UiList *srclist1;
+    UiList *srclist2;
+    UiList *items;
 } MyDocument;
 
 MyDocument *doc1;
 MyDocument *doc2;
 
+UIWIDGET tabview;
+
+static UiCondVar *cond;
+static int thr_end = 0;
+static int thr_started = 0;
+
+int threadfunc(void *data) {
+    printf("thr wait for data...\n");
+    ui_condvar_wait(cond);
+    printf("thr data received: {%s} [%d]\n", cond->data, cond->intdata);
+    ui_condvar_destroy(cond);
+    cond = NULL;
+    
+    return 0;
+}
+
+void action_start_thread(UiEvent *event, void *data) {
+    if(!thr_started) {
+        cond = ui_condvar_create();
+        ui_job(event->obj, threadfunc, NULL, NULL, NULL);
+        thr_started = 1;
+    }
+}
+
+void action_notify_thread(UiEvent *event, void *data) {
+    if(!thr_end) {
+        ui_condvar_signal(cond, "hello thread", 123);
+        thr_end = 1;
+    }
+}
 
 void action_menu(UiEvent *event, void *userdata) {
-    printf("action_menu: %s\n", (char*)userdata);
+    
+}
+
+void action_file_selected(UiEvent *event, void *userdata) {
+    UiFileList *files = event->eventdata;
+    MyDocument *doc = event->document;
+    printf("files: %d\n", (int)files->nfiles);
+    if(files->nfiles > 0) {
+        printf("selected file: %s\n", files->files[0]);
+        ui_image_load_file(doc->image, files->files[0]);
+    }
 }
 
 void action_button(UiEvent *event, void *userdata) {
-    printf("button test\n");
-    MyDocument *doc = event->document;
-    if(!doc) {
-        printf("no document\n");
-        return;
-    }
-    
-    char *text = doc->text->get(doc->text);
-    printf("text: {\n%s\n}\n", text);
+    ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_SINGLE, action_file_selected, NULL);
 }
 
 void action_switch(UiEvent *event, void *userdata) {
-    if(event->document == doc1) {
-        ui_set_document(event->obj, doc2);
-    } else {
-        ui_set_document(event->obj, doc1);
-    }
+    
+}
+
+void action_toolbar_button(UiEvent *event, void *userdata) {
+    printf("toolbar button\n");
+    
+    ui_dialog(event->obj, .title = "Dialog Title", .content = "Content Label", .button1_label = "btn1", .button2_label = "btn2", .input = TRUE, .closebutton_label = "Cancel");
+}
+
+void action_dialog_button(UiEvent *event, void *userdata) {
+    ui_close(event->obj);
+}
+
+void action_dialog_onactivate(UiEvent *event, void *userdata) {
+    printf("textfield activate\n");
+    ui_close(event->obj);
 }
 
+void action_toolbar_dialog(UiEvent *event, void *userdata) {
+    
+    UiObject *dialog = ui_dialog_window(event->obj, .title  = "Dialog Window", .lbutton1 = "Cancel 1", .lbutton2 = "Btn2", .rbutton3 = "Btn3", .rbutton4 = "Login 4", .onclick = action_dialog_button, .default_button = 4, .show_closebutton = UI_OFF);
+    
+    ui_vbox(dialog, .margin = 10, .spacing = 10) {
+        ui_label(dialog, .label = "Enter password:");
+        ui_passwordfield(dialog, .varname = "password", .onactivate = action_dialog_onactivate);
+    }
+    
+    ui_show(dialog);
+}
+
+void action_toolbar_newwindow(UiEvent *event, void *userdata) {
+    UiObject *obj = ui_simple_window("New Window", NULL);
+    
+    ui_headerbar0(obj) {
+        ui_headerbar_start(obj) {
+            ui_button(obj, .label = "Open");
+        }
+        ui_headerbar_end(obj) {
+            ui_button(obj, .label = "Test");
+        }
+    }
+    
+    ui_textarea(obj, .varname="text");
+    
+    ui_show(obj);
+}
 
 MyDocument* create_doc(void) {
     MyDocument *doc = ui_document_new(sizeof(MyDocument));
     UiContext *docctx = ui_document_context(doc);
-    doc->text = ui_text_new(docctx, "text");
+    doc->str1 = ui_string_new(docctx, "str1");
+    doc->str1 = ui_string_new(docctx, "str2");
+    doc->path = ui_string_new(docctx, "path");
+    doc->progress = ui_double_new(docctx, "progress");
+    doc->list = ui_list_new(docctx, "list");
+    ui_list_append(doc->list, "test1");
+    ui_list_append(doc->list, "test2");
+    ui_list_append(doc->list, "test3");
+    doc->list2 = ui_list_new(docctx, "list2");
+    ui_list_append(doc->list2, "test1");
+    ui_list_append(doc->list2, "test2");
+    ui_list_append(doc->list2, "test3");
+    doc->radio = ui_int_new(docctx, "radio");
+    doc->tabview = ui_int_new(docctx, "tabview");
+    doc->image = ui_generic_new(docctx, "image");
+    
+    doc->srclist1 = ui_list_new(docctx, "srclist1");
+    doc->srclist2 = ui_list_new(docctx, "srclist2");
+    ui_list_append(doc->srclist1, "test1");
+    ui_list_append(doc->srclist1, "test2");
+    ui_list_append(doc->srclist1, "test3");
+    ui_list_append(doc->srclist2, "x1");
+    ui_list_append(doc->srclist2, "x2");
+    
+    doc->items = ui_list_new(docctx, "items");
+    ui_list_append(doc->items, "Item 1");
+    ui_list_append(doc->items, "Item 2");
+    ui_list_append(doc->items, "Item 3");
+    
+    //doc->text = ui_text_new(docctx, "text");
     return doc;
 }
 
+UiIcon *icon = NULL;
+
+static void* list_getvalue(void *elm, int col) {
+    /*
+    if(col == 0) {
+        if(!icon) {
+            icon = ui_icon("folder", 24);
+        }
+        return icon;
+    }
+    */
+    
+    char *str = elm;
+    return col == 0 ? str : "x";
+}
+
+static UiList *menu_list;
+int new_item_count = 0;
+
+void action_add_menu_item(UiEvent *event, void *userdata) {
+    char str[64];
+    snprintf(str, 64, "new item %d", new_item_count++);
+    
+    ui_list_append(menu_list, strdup(str));
+    ui_list_notify(menu_list);
+}
+
+void action_menu_list(UiEvent *event, void *userdata) {
+    printf("menu list item: %d\n", event->intval);
+}
+
+static int tab_x = 0;
+void action_tab2_button(UiEvent *event, void *userdata) {
+    MyDocument *doc = event->document;
+    printf("current page: %d\n", (int)ui_get(doc->tabview));
+    ui_set(doc->tabview, 0);
+}
+
+
+void action_group1(UiEvent *event, void *userdata) {
+    UiContext *ctx = event->obj->ctx;
+    if(userdata) {
+        ui_unset_group(ctx, 1);
+    } else {
+        ui_set_group(ctx, 1);
+    }
+}
+
+void action_group2(UiEvent *event, void *userdata) {
+    UiContext *ctx = event->obj->ctx;
+    if(userdata) {
+        ui_unset_group(ctx, 2);
+    } else {
+        ui_set_group(ctx, 2);
+    }
+}
+
+static UiObject *ref_window;
+
+void action_button_ref(UiEvent *event, void *userdata) {
+    UiObject *obj = event->obj;
+    printf("action_button_ref: %u\n", obj->ref);
+    ui_object_ref(obj);
+    ref_window = obj;
+}
+
+void action_button_unref(UiEvent *event, void *userdata) {
+    UiObject *obj = userdata;
+    printf("action_button_unref: %u\n", obj->ref);
+    ui_object_unref(obj);
+}
+
+void action_toolbar_unrefwindow(UiEvent *event, void *userdata) {
+    UiObject *obj = ui_simple_window("Unref", NULL);
+    ui_grid(obj, .margin = 20) {
+        ui_button(obj, .label = "Unref", .onclick = action_button_unref, .onclickdata = ref_window);
+    }
+    ui_show(obj);
+}
+
+void action_sourcelist_activate(UiEvent *event, void *userdata) {
+    printf("sourcelist %s index %d\n", event->eventdata, event->intval);
+}
+
+UiMenuBuilder *menubuilder;
+
+void* table_getvalue(void *row, int col) {
+    return row;
+}
+
+void sourcelist_getvalue(void *sublistdata, void *rowdata, int index, UiSubListItem *item) {
+    item->label = strdup(rowdata);
+    item->eventdata = sublistdata;
+}
+
+typedef struct Item {
+    UiObject *obj;
+    MyDocument *doc;
+    void *elm;
+} Item;
+
+void item_remove(UiEvent *event, void *userdata) {
+    Item *item = userdata;
+    int index = 0;
+    void *elm = ui_list_first(item->doc->items);
+    while(elm) {
+        if(elm == item->elm) {
+            break;
+        }
+        elm = ui_list_next(item->doc->items);
+        index++;
+    }
+    
+    ui_list_remove(item->doc->items, index);
+    ui_list_update(item->doc->items);
+}
+
+void create_item(UiObject *obj, int index, void *elm, void *userdata) {
+    Item *i = ui_malloc(obj->ctx, sizeof(Item));
+    i->obj = obj;
+    i->elm = elm;
+    i->doc = userdata;
+    
+    char *item = elm;
+    ui_button(obj, .label = item);
+    ui_checkbox(obj, .label = "Check");
+    ui_label(obj, .fill = UI_ON);
+    ui_button(obj, .label = "X", .onclick = item_remove, .onclickdata = i);
+    
+    
+    
+}
+
 void application_startup(UiEvent *event, void *data) {
+    // global list
+    UiContext *global = ui_global_context();
+    menu_list = ui_list_new(global, "menulist");
+    ui_list_append(menu_list, "menu list item 1");
+    ui_list_append(menu_list, "menu list item 2");
+    ui_list_append(menu_list, "menu list item 3");
     
-    UiObject *obj = ui_window("Test", NULL);
-    ui_textarea_nv(obj, "text");
-    ui_button(obj, "Test", action_button, NULL);
-    ui_button(obj, "Switch Document", action_switch, NULL);
+    
+    
+    UiObject *obj = ui_sidebar_window("Test", NULL);
+    
+    MyDocument *doc = create_doc();
+    ui_attach_document(obj->ctx, doc);
+    
+    ui_sidebar(obj, .margin = 0, .spacing = 0) {
+        ui_sourcelist(obj, .fill = UI_ON,
+                .getvalue = sourcelist_getvalue,
+                .sublists = UI_SUBLISTS(UI_SUBLIST(.varname = "srclist1", .header = "Header 1", .userdata = "Sublist1"), UI_SUBLIST(.varname = "srclist2", .header = "Header 2", .userdata = "Sublist2")),
+                .onactivate = action_sourcelist_activate);
+    }
     
-    doc1 = create_doc();
-    doc2 = create_doc();
+    ui_tabview(obj, .spacing=10, .margin=10, .tabview = UI_TABVIEW_NAVIGATION_SIDE, .varname="tabview") {
+        ui_tab(obj, "Tab 1") {
+            ui_vbox(obj, .fill = UI_OFF, .margin = 15, .spacing = 15) {
+                ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button);
+                ui_togglebutton(obj, .label = "Toggle");
+                ui_checkbox(obj, .label = "My Checkbox");
+            }
+            ui_grid(obj, .fill = UI_OFF, .columnspacing = 15, .rowspacing = 15, .margin = 15) {
+                ui_button(obj, .label = "Activate Group 1", .hexpand = TRUE, .onclick = action_group1);
+                ui_button(obj, .label = "Disable Group 1", .onclick = action_group1, .onclickdata = "disable");
+                ui_newline(obj);
+                ui_button(obj, .label = "Activate Group 2", .hexpand = TRUE, .onclick = action_group2);
+                ui_button(obj, .label = "Disable Group 2", .onclick = action_group2, .onclickdata = "disable");
+                ui_newline(obj);
+                
+                ui_button(obj, .label = "Groups 1,2", .colspan = 2, .groups = UI_GROUPS(1, 2));
+                ui_newline(obj);
+
+                ui_label(obj, .label = "Label Col 1", .align = UI_ALIGN_LEFT);
+                ui_label(obj, .label = "Label Col 2", .style = UI_LABEL_STYLE_TITLE, .align = UI_ALIGN_RIGHT);
+                ui_newline(obj);
+
+                ui_spinner(obj, .step = 5);
+                ui_newline(obj);
+
+                ui_progressbar(obj, .colspan = 2, .varname = "progress");
+                ui_set(doc->progress, 0.75);
+                ui_newline(obj);
+
+                ui_textfield(obj, .value = doc->str1);
+                ui_newline(obj);
+
+                //ui_button(obj, .label="Test");
+                ui_path_textfield(obj, .varname = "path", .hfill = TRUE, .hexpand = TRUE);
+                ui_set(doc->path, "/test/path/longdirectoryname/123");
+                ui_newline(obj);
+
+                //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1);
+                //model->getvalue = list_getvalue;
+                ui_combobox(obj, .hexpand = true, .vexpand = false, .colspan = 2, .varname = "list", .getvalue = list_getvalue);
+                ui_newline(obj);
+
+                ui_hbox0(obj) {
+                    ui_radiobutton(obj, .label = "Radio 1", .varname = "radio");
+                    ui_radiobutton(obj, .label = "Radio 2", .varname = "radio");
+                    ui_radiobutton(obj, .label = "Radio 3", .varname = "radio");
+                }
+                ui_newline(obj);
+                
+                UiModel *model = ui_model(obj->ctx, UI_STRING, "col1", -1);
+                model->getvalue = table_getvalue;
+                ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .hexpand = TRUE, .contextmenu = menubuilder);
+            }
+        }
+        ui_tab(obj, "Tab 2") {
+            ui_button(obj, .label = "Button 1 Start Thread", .onclick=action_start_thread);
+            ui_button(obj, .label = "Button 2 Notify Thread", .onclick=action_notify_thread);
+            ui_button(obj, .label = "Obj Ref", .onclick=action_button_ref);
+            ui_button(obj, .label = "Obj Unref", .onclick=action_button_unref, .onclickdata = obj);
+            ui_button(obj, .label = "Button 5", .onclick=action_tab2_button);
+            ui_button(obj, .label = "Button 6", .onclick=action_tab2_button);
+        }
+        ui_tab(obj, "Tab 3") {
+            UiTabViewArgs args = {0};
+            UI_CTN(obj, tabview=ui_tabview_create(obj, args)) {
+                UiObject *tab1 = ui_tabview_add(tabview, "Sub 1", -1);
+                ui_button(tab1, .label = "Button 1");
+
+
+                UiObject *tab2 = ui_tabview_add(tabview, "Sub 2", -1);
+                ui_button(tab2, .label = "Button 2");
+            }
+        }
+        ui_tab(obj, "Tab 4") {
+            ui_textarea(obj, .varname = "text");
+        }
+        ui_tab(obj, "Tab 5") {
+            ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button);
+            ui_imageviewer(obj, .varname = "image", .style_class = "imageviewer", .contextmenu = menubuilder);
+        }
+        
+        ui_tab(obj, "Tab 6") {
+            ui_scrolledwindow(obj, .fill = UI_ON) {
+                ui_expander(obj, .label = "Expander", .margin = 10, .spacing = 10) {
+                    ui_label(obj, .label = "Test");
+                    ui_button(obj, .label = "Button");
+                }
+                
+                ui_frame(obj, .label = "Frame", .margin = 10, .spacing = 10) {
+                    ui_label(obj, .label = "Title", .style = UI_LABEL_STYLE_TITLE);
+                    ui_label(obj, .label = "Sub-Title", .style = UI_LABEL_STYLE_SUBTITLE);
+                    ui_label(obj, .label = "Dim Label", .style = UI_LABEL_STYLE_DIM);
+                    ui_label(obj, .label = "No Style");
+                }
+                
+                for(int i=0;i<100;i++) {
+                    char labelstr[32];
+                    snprintf(labelstr, 32, "button %d", i);
+                    ui_button(obj, .label = labelstr);
+                }
+            }
+        }
+        
+        ui_tab(obj, "Tab 7") {
+            ui_itemlist(obj, .create_ui = create_item, .varname = "items", .subcontainer = UI_CONTAINER_HBOX, .sub_spacing = 10, .margin = 10, .spacing = 4, .userdata = doc);
+        }
+    }
     
-    ui_attach_document(obj->ctx, doc1);
+    /*
+    
+    */
     
     ui_show(obj);
 }
@@ -93,18 +464,176 @@
     ui_onstartup(application_startup, NULL);
     
     // menu
-    ui_menu("_File");
-    ui_menuitem("_Hello", action_menu, NULL);
-    ui_submenu("Submenu1");
-    ui_submenu("Submenu2");
-    ui_menuitem("item2", action_menu, NULL);
-    ui_submenu_end();
-    ui_menuitem("item3", action_menu, NULL);
-    ui_submenu_end();
-    ui_menuitem("item4", action_menu, NULL);
-
+    ui_menu("File") {
+        ui_menuitem(.label = "Test");
+    }
+    
+    ui_contextmenu(&menubuilder) {
+        ui_menuitem(.label = "Context Item 1");
+        ui_menuitem(.label = "Context Item 2");
+        ui_menu("Context Submenu") {
+            ui_menuitem(.label = "Context Sub Item");
+        }
+    }
+    
+    ui_menu("Edit") {
+        ui_menuitem(.label = "Undo");
+        ui_menuseparator();
+        ui_menu("Submenu") {
+            ui_menuitem(.label = "Subitem");
+        }
+    }
+    
+    ui_toolbar_item("Test", .label = "Test", .onclick = action_toolbar_button);
+    ui_toolbar_item("Test2", .label = "New Window", .onclick = action_toolbar_newwindow);
+    ui_toolbar_item("Test3", .label = "Dialog", .onclick = action_toolbar_dialog);
+    ui_toolbar_item("Test4", .label = "Unref Window", .onclick = action_toolbar_unrefwindow);
+    ui_toolbar_item("Test5", .label = "Test 5", .onclick = action_toolbar_button);
+    ui_toolbar_item("Test6", .label = "Test 6", .onclick = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);
+    ui_toolbar_menu("Menu", .label = "Menu") {
+        ui_menuitem("Secondary Test", .onclick = action_toolbar_button, NULL);
+        ui_menu("Secondary Sub") {
+            ui_menuitem("Secondary subitem", NULL, NULL);
+        }
+        ui_menuseparator();
+        ui_menu_itemlist(.varname = "menulist", .onselect=action_menu_list);
+        ui_menuseparator();
+        ui_menuitem("last", .onclick = action_add_menu_item);
+    }
+    
+    ui_toolbar_appmenu() {
+        ui_menuitem("New");
+        ui_menuitem("Open");
+        ui_menuitem("Save");
+        
+        ui_menuseparator();
+        
+        ui_menuitem("Close");
+    }
+    
+    ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Test6", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Menu", UI_TOOLBAR_LEFT);
+    
+    ui_toolbar_add_default("Test2", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Test3", UI_TOOLBAR_CENTER);
+    
+    ui_toolbar_add_default("Test4", UI_TOOLBAR_RIGHT);
+    ui_toolbar_add_default("Test5", UI_TOOLBAR_RIGHT);
     
     ui_main();
     
     return (EXIT_SUCCESS);
 }
+
+#endif
+
+#if defined(UI_COCOA) || defined(UI_MOTIF)
+
+static UiList *menulist;
+int items = 4;
+
+void action_button(UiEvent *event, void *data) {
+    printf("action_button\n");
+    
+    char *newitem = malloc(32);
+    snprintf(newitem, 32, "Item %d", ++items);
+    ui_list_append(menulist, newitem);
+    ui_list_notify(menulist);
+}
+
+typedef struct WData {
+    UiString *path;
+    UiList *list;
+} WData;
+
+
+int lsitems = 4;
+
+void action_button2(UiEvent *event, void *data) {
+    WData *wdata = event->window;
+    char *newitem = malloc(32);
+    snprintf(newitem, 32, "List Item %d", ++lsitems);
+    ui_list_append(wdata->list, newitem);
+    ui_list_update(wdata->list);
+    UiListSelection sel;
+    int index = lsitems-1;
+    sel.count = 1;
+    sel.rows = &index;
+    wdata->list->setselection(wdata->list, sel);
+}
+
+void action_listevent(UiEvent *event, void *data) {
+    printf("%s: %d\n", data, event->intval);
+    UiListSelection *sel = event->eventdata;
+    for(int i=0;i<sel->count;i++) {
+        printf("sel: %d\n", sel->rows[i]);
+    }
+    printf("\n");
+}
+
+void application_startup(UiEvent *event, void *data) {
+    
+    menulist = ui_list_new(ui_global_context(), "menulist");
+    ui_list_append(menulist, "Item 1");
+    ui_list_append(menulist, "Item 2");
+    ui_list_append(menulist, "Item 3");
+    ui_list_append(menulist, "Item 4");
+    
+    UiObject *obj = ui_window("Test", NULL);
+    
+    WData *wdata = ui_malloc(obj->ctx, sizeof(WData));
+    wdata->path = ui_string_new(obj->ctx, NULL);
+    wdata->list = ui_list_new(obj->ctx, NULL);
+    obj->window = wdata;
+    
+    ui_list_append(wdata->list, "List Item 1");
+    ui_list_append(wdata->list, "List Item 2");
+    ui_list_append(wdata->list, "List Item 3");
+    ui_list_append(wdata->list, "List Item 4");
+    
+    ui_button(obj, .label = "Add Menu Item", .onclick = action_button, .name = "mybutton1");
+    ui_button(obj, .label = "Add List Item", .onclick = action_button2);
+    ui_progressbar(obj, .name = "pb");
+    ui_listview(obj, .list = wdata->list, .fill = UI_ON, .multiselection = TRUE,
+            .onactivate = action_listevent, .onactivatedata = "activate",
+            .onselection = action_listevent, .onselectiondata = "selection");
+    
+    
+    ui_show(obj);
+}
+
+void action_test(UiEvent *event, void *data) {
+    printf("action test\n");
+}
+
+int main(int argc, char** argv) {
+    ui_init("app1", argc, argv);
+    ui_onstartup(application_startup, NULL);
+    
+    // menu
+    ui_menu("File") {
+        ui_menuitem(.label = "Test 1", .onclick = action_test);
+        ui_menuitem(.label = "Test 2", .onclick = action_test);
+        ui_menuitem(.label = "Test 3", .onclick = action_test);
+        ui_menuseparator();
+        ui_menu_toggleitem(.label = "Toggle 1");
+        ui_menu_toggleitem(.label = "Toggle 2");
+        ui_menuseparator();
+        ui_menu_radioitem(.label = "Radio 1", .varname = "menu_radio");
+        ui_menu_radioitem(.label = "Radio 2", .varname = "menu_radio");
+        ui_menu_radioitem(.label = "Radio 3", .varname = "menu_radio");
+        ui_menu_radioitem(.label = "Radio 4", .varname = "menu_radio");
+        ui_menuseparator();
+        ui_menu_itemlist(.varname = "menulist");
+        ui_menuseparator();
+        ui_menuitem(.label = "Quit");
+    }
+    
+    ui_main();
+    return (EXIT_SUCCESS);
+}
+
+#endif
--- a/configure	Sun May 23 09:44:43 2021 +0200
+++ b/configure	Sat Jan 04 16:38:48 2025 +0100
@@ -1,40 +1,53 @@
 #!/bin/sh
 
-
-PREFIX=/usr
-EPREFIX=$PREFIX
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+    :
+else
+    echo "Cannot create tmp dir $TEMP_DIR"
+    echo "Abort"
+    exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
 
-BINDIR=
-SBINDIR=
-LIBDIR=
-LIBEXECDIR=
-DATADIR=
-SYSCONFDIR=
-SHAREDSTATEDIR=
-LOCALSTATEDIR=
-INCLUDEDIR=
-INFODIR=
-MANDIR=
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
 
-OS=`uname -s`
-OS_VERSION=`uname -r`
-
-TEMP_DIR=".tmp-`uname -n`"
-mkdir -p $TEMP_DIR
-if [ $? -ne 0 ]; then
-	echo "Cannot create tmp dir"
-	echo "Abort"
-fi
-touch $TEMP_DIR/options
-touch $TEMP_DIR/features
+# custom variables
 
 # features
 
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
+
 # help text
 printhelp()
 {
-	echo "Usage: $0 [OPTIONS]..."
-	cat << __EOF__
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
 Installation directories:
   --prefix=PREFIX         path prefix for architecture-independent files
                           [/usr]
@@ -47,697 +60,846 @@
   --sysconfdir=DIR        system configuration files [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
   --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
   --infodir=DIR           info documentation [DATAROOTDIR/info]
   --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
 
 Options:
-  --toolkit=(gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+  --debug                 add extra compile flags for debug builds
+  --release               add extra compile flags for release builds
+  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif)
 
 __EOF__
 }
 
 #
-# parse arguments 
+# parse arguments
 #
-for ARG in $@
+BUILD_TYPE="default"
+for ARG in "$@"
 do
     case "$ARG" in
-		"--prefix="*)         PREFIX=${ARG#--prefix=} ;;
-		"--exec-prefix="*)    EPREFIX=${ARG#--exec-prefix=} ;;
-		"--bindir="*)         BINDIR=${ARG#----bindir=} ;;
-		"--sbindir="*)        SBINDIR=${ARG#--sbindir=} ;;
-		"--libdir="*)         LIBDIR=${ARG#--libdir=} ;;
-		"--libexecdir="*)     LIBEXECDIR=${ARG#--libexecdir=} ;;
-		"--datadir="*)        DATADIR=${ARG#--datadir=} ;;
-		"--sysconfdir="*)     SYSCONFDIR=${ARG#--sysconfdir=} ;;
-		"--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;;
-		"--localstatedir="*)  LOCALSTATEDIR=${ARG#--localstatedir=} ;;
-		"--includedir="*)     INCLUDEDIR=${ARG#--includedir=} ;;
-		"--infodir="*)        INFODIR=${ARG#--infodir=} ;;
-		"--mandir"*)          MANDIR=${ARG#--mandir} ;;
-		"--help"*) printhelp; exit 1 ;;
-    	"--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
-		"-"*) echo "unknown option: $ARG"; exit 1 ;;
-	esac
+        "--prefix="*)         prefix=${ARG#--prefix=} ;;
+        "--exec-prefix="*)    exec_prefix=${ARG#--exec-prefix=} ;;
+        "--bindir="*)         bindir=${ARG#----bindir=} ;;
+        "--sbindir="*)        sbindir=${ARG#--sbindir=} ;;
+        "--libdir="*)         libdir=${ARG#--libdir=} ;;
+        "--libexecdir="*)     libexecdir=${ARG#--libexecdir=} ;;
+        "--datarootdir="*)    datarootdir=${ARG#--datarootdir=} ;;
+        "--datadir="*)        datadir=${ARG#--datadir=} ;;
+        "--sysconfdir="*)     sysconfdir=${ARG#--sysconfdir=} ;;
+        "--sharedstatedir="*) sharedstatedir=${ARG#--sharedstatedir=} ;;
+        "--localstatedir="*)  localstatedir=${ARG#--localstatedir=} ;;
+        "--includedir="*)     includedir=${ARG#--includedir=} ;;
+        "--infodir="*)        infodir=${ARG#--infodir=} ;;
+        "--mandir"*)          mandir=${ARG#--mandir} ;;
+        "--localedir"*)       localedir=${ARG#--localedir} ;;
+        "--help"*) printhelp; abort_configure ;;
+        "--debug")           BUILD_TYPE="debug" ;;
+        "--release")         BUILD_TYPE="release" ;;
+        "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+        "-"*) echo "unknown option: $ARG"; abort_configure ;;
+    esac
 done
 
-# set dir variables
-if [ -z "$BINDIR" ]; then
-	BINDIR=$EPREFIX/bin
-fi
-if [ -z "$SBINDIR" ]; then
-	SBINDIR=$EPREFIX/sbin
-fi
-if [ -z "$LIBDIR" ]; then
-	LIBDIR=$EPREFIX/lib
-fi
-if [ -z "$LIBEXEC" ]; then
-	LIBEXECDIR=$EPREFIX/libexec
-fi
-if [ -z "$DATADIR" ]; then
-	DATADIR=$PREFIX/share
-fi
-if [ -z "$SYSCONFDIR" ]; then
-	SYSCONFDIR=$PREFIX/etc
-fi
-if [ -z "$SHAREDSTATEDIR" ]; then
-	SHAREDSTATEDIR=$PREFIX/com
-fi
-if [ -z "$LOCALSTATEDIR" ]; then
-	LOCALSTATEDIR=$PREFIX/var
-fi
-if [ -z "$INCLUDEDIR" ]; then
-	INCLUDEDIR=$PREFIX/include
-fi
-if [ -z "$INFODIR" ]; then
-	INFODIR=$PREFIX/info
-fi
-if [ -z "$MANDIR" ]; then
-	MANDIR=$PREFIX/man
+
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+    # CONFIG_SITE may contain space separated file names
+    for cs in $CONFIG_SITE; do
+        printf "loading defaults from $cs... "
+        . "$cs"
+        echo ok
+    done
+elif [ -f "$prefix/share/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/share/config.site"
+    echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/etc/config.site"
+    echo ok
 fi
 
-which pkg-config > /dev/null
-if [ $? -eq 0 ]; then
-    PKG_CONFIG=pkg-config
-else
-    PKG_CONFIG=false
-fi
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
 
 # Simple uname based platform detection
 # $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
 printf "detect platform... "
-if [ $OS = SunOS ]; then
+if [ "$OS" = "SunOS" ]; then
     PLATFORM="solaris sunos unix svr4"
-fi
-if [ $OS = Linux ]; then
+elif [ "$OS" = "Linux" ]; then
     PLATFORM="linux unix"
-fi
-if [ $OS = FreeBSD ]; then
+elif [ "$OS" = "FreeBSD" ]; then
     PLATFORM="freebsd bsd unix"
-fi
-if [ $OS = Darwin ]; then
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
     PLATFORM="macos osx bsd unix"
-fi
-echo $OS | grep "MINGW" > /dev/null
-if [ $? -eq 0 ]; then
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
     PLATFORM="windows mingw"
 fi
-
-if [ -z "$PLATFORM" ]; then
-    PLATFORM="unix"
-fi
+: ${PLATFORM:="unix"}
 
-for p in $PLATFORM
-do
-	PLATFORM_NAME=$p
-	break
-done
-echo $PLATFORM_NAME
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
 
 isplatform()
 {
     for p in $PLATFORM
     do
-        if [ $p = $1 ]; then
+        if [ "$p" = "$1" ]; then
             return 0
         fi
     done
     return 1
 }
-isnotplatform()
+notisplatform()
 {
     for p in $PLATFORM
     do
-        if [ $p = $1 ]; then
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
             return 1
         fi
     done
     return 0
 }
 
-# generate config.mk and config.h
-cat > $TEMP_DIR/config.mk << __EOF__
-#
-# config.mk generated by configure
-#
 
-# general vars
-
-PREFIX=$PREFIX
-EPREFIX=$EPREFIX
-
-BINDIR=$BINDIR
-SBINDIR=$SBINDIR
-LIBDIR=$LIBDIR
-LIBEXECDIR=$LIBEXECDIR
-DATADIR=$DATADIR
-SYSCONFDIR=$SYSCONFDIR
-SHAREDSTATEDIR=$SHAREDSTATEDIR
-LOCALSTATEDIR=$LOCALSTATEDIR
-INCLUDEDIR=$INCLUDEDIR
-INFODIR=$INFODIR
-MANDIR=$MANDIR
-
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
 __EOF__
 
-echo > $TEMP_DIR/make.mk
-
-ENV_CFLAGS=$CFLAGS
-ENV_LDFLAGS=$LDFLAGS
-ENV_CXXFLAGS=$CXXFLAGS
-
-# Toolchain detection
-# this will insert make vars to config.mk
+# toolchain detection utilities
 . make/toolchain.sh
 
-# add user specified flags to config.mk
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${ENV_CFLAGS}" ]; then
-    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
-fi
-if [ ! -z "${ENV_CXXFLAGS}" ]; then
-    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
-fi
-if [ ! -z "${ENV_LDFLAGS}" ]; then
-    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
-fi
-
 #
 # DEPENDENCIES
 #
 
-dependency_qt4()
+# check languages
+lang_c=
+lang_cpp=
+if detect_c_compiler ; then
+    lang_c=1
+fi
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
 {
-    printf "checking for qt4... "
-    # dependency qt4 
-    while true
-    do
-        qmake-qt4 -o - /dev/null | grep DEFINES\  > /dev/null
-        if [ $? -eq 0 ]; then
-            CFLAGS="$CFLAGS `qmake-qt4 -o - /dev/null | grep DEFINES\ `"
-        else
-            break
-        fi
-        qmake-qt4 -o - /dev/null | grep INCPATH\  > /dev/null
-        if [ $? -eq 0 ]; then
-            CFLAGS="$CFLAGS `qmake-qt4 -o - /dev/null | grep INCPATH\ `"
-        else
-            break
-        fi
-         > /dev/null
-        if [ $? -eq 0 ]; then
-            LDFLAGS="$LDFLAGS ``"
-        else
-            break
-        fi
-        which qmake-qt4 > /dev/null
-        if [ $? -ne 0 ]; then
-        	break
-        fi
-		echo yes
-        return 0
-    done
-	
-	echo no
-	return 1
+    if "$PKG_CONFIG" --exists "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+    else return 1 ; fi
+    return 0
 }
-dependency_gtk2legacy()
+
+print_check_msg()
 {
-    printf "checking for gtk2legacy... "
-    # dependency gtk2legacy 
+    if [ -z "$1" ]; then
+        shift
+        printf "$@"
+    fi
+}
+
+dependency_error_gtk2legacy()
+{
+    print_check_msg "$dep_checked_gtk2legacy" "checking for gtk2legacy... "
+    # dependency gtk2legacy
     while true
     do
         if [ -z "$PKG_CONFIG" ]; then
-        	break
+            break
         fi
-		$PKG_CONFIG gtk+-2.0
-        if [ $? -ne 0 ] ; then
+        if test_pkg_config "gtk+-2.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
+        else
             break
         fi
-        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-2.0`"
-        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-2.0`"
-        CFLAGS="$CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY"    
-        LDFLAGS="$LDFLAGS -lpthread"    
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk2legacy" "yes\n"
+        dep_checked_gtk2legacy=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_gtk2legacy" "no\n"
+    dep_checked_gtk2legacy=1
+    return 0
 }
-dependency_qt5()
+dependency_error_gtk2()
 {
-    printf "checking for qt5... "
-    # dependency qt5 
+    print_check_msg "$dep_checked_gtk2" "checking for gtk2... "
+    # dependency gtk2
     while true
     do
-        qmake-qt5 -o - /dev/null | grep DEFINES\  > /dev/null
-        if [ $? -eq 0 ]; then
-            CFLAGS="$CFLAGS `qmake-qt5 -o - /dev/null | grep DEFINES\ `"
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if pkg-config --atleast-version=2.20 gtk+-2.0 > /dev/null ; then
+            :
+        else
+            break
+        fi
+        if test_pkg_config "gtk+-2.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
         else
             break
         fi
-        qmake-qt5 -o - /dev/null | grep INCPATH\  > /dev/null
-        if [ $? -eq 0 ]; then
-            CFLAGS="$CFLAGS `qmake-qt5 -o - /dev/null | grep INCPATH\ `"
-        else
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk2" "yes\n"
+        dep_checked_gtk2=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_gtk2" "no\n"
+    dep_checked_gtk2=1
+    return 0
+}
+dependency_error_gtk3()
+{
+    print_check_msg "$dep_checked_gtk3" "checking for gtk3... "
+    # dependency gtk3
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
             break
         fi
-         > /dev/null
-        if [ $? -eq 0 ]; then
-            LDFLAGS="$LDFLAGS ``"
+        if test_pkg_config "gtk+-3.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-3.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-3.0`"
         else
             break
         fi
-        which qmake-qt5 > /dev/null
-        if [ $? -ne 0 ]; then
-        	break
-        fi
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK3"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk3" "yes\n"
+        dep_checked_gtk3=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_gtk3" "no\n"
+    dep_checked_gtk3=1
+    return 0
 }
-dependency_gtk2()
+dependency_error_gtk4()
 {
-    printf "checking for gtk2... "
-    # dependency gtk2 
+    print_check_msg "$dep_checked_gtk4" "checking for gtk4... "
+    # dependency gtk4
     while true
     do
         if [ -z "$PKG_CONFIG" ]; then
-        	break
+            break
         fi
-		$PKG_CONFIG gtk+-2.0
-        if [ $? -ne 0 ] ; then
+        if test_pkg_config "gtk4" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk4`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk4`"
+        else
             break
         fi
-        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-2.0`"
-        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-2.0`"
-        CFLAGS="$CFLAGS -DUI_GTK2"    
-        LDFLAGS="$LDFLAGS -lpthread"    
-        pkg-config --atleast-version=2.20 gtk+-2.0 > /dev/null
-        if [ $? -ne 0 ]; then
-        	break
-        fi
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK4"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk4" "yes\n"
+        dep_checked_gtk4=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_gtk4" "no\n"
+    dep_checked_gtk4=1
+    return 0
 }
-dependency_gtk3()
+dependency_error_libadwaita()
 {
-    printf "checking for gtk3... "
-    # dependency gtk3 
+    print_check_msg "$dep_checked_libadwaita" "checking for libadwaita... "
+    # dependency libadwaita
     while true
     do
         if [ -z "$PKG_CONFIG" ]; then
-        	break
+            break
         fi
-		$PKG_CONFIG gtk+-3.0
-        if [ $? -ne 0 ] ; then
+        if test_pkg_config "libadwaita-1" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libadwaita-1`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libadwaita-1`"
+        else
             break
         fi
-        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-3.0`"
-        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-3.0`"
-        CFLAGS="$CFLAGS -DUI_GTK3"    
-        LDFLAGS="$LDFLAGS -lpthread"    
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK4 -DUI_LIBADWAITA"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_libadwaita" "yes\n"
+        dep_checked_libadwaita=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_libadwaita" "no\n"
+    dep_checked_libadwaita=1
+    return 0
 }
-dependency_motif()
+dependency_error_motif()
 {
-    printf "checking for motif... "
-    # dependency motif 
+    print_check_msg "$dep_checked_motif" "checking for motif... "
+    # dependency motif platform="bsd"
     while true
     do
-        CFLAGS="$CFLAGS -DUI_MOTIF"    
-        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
-		echo yes
-        return 0
+        if notisplatform "bsd"; then
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF -I/usr/local/include/X11"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+        print_check_msg "$dep_checked_motif" "yes\n"
+        dep_checked_motif=1
+        return 1
     done
-	
-	echo no
-	return 1
-}
-dependency_wpf()
-{
-    printf "checking for wpf... "
-    # dependency wpf platform="windows"
+
+    # dependency motif
     while true
     do
-    	if isnotplatform "windows"; then
-            break
-        fi
-        CFLAGS="$CFLAGS -DUI_WPF"    
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+        print_check_msg "$dep_checked_motif" "yes\n"
+        dep_checked_motif=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_motif" "no\n"
+    dep_checked_motif=1
+    return 0
 }
-dependency_cocoa()
+dependency_error_cocoa()
 {
-    printf "checking for cocoa... "
+    print_check_msg "$dep_checked_cocoa" "checking for cocoa... "
     # dependency cocoa platform="macos"
     while true
     do
-    	if isnotplatform "macos"; then
+        if notisplatform "macos"; then
             break
         fi
-        CFLAGS="$CFLAGS -DUI_COCOA"    
-        LDFLAGS="$LDFLAGS -lobjc -framework Cocoa"    
-		echo yes
-        return 0
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_COCOA"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lobjc -framework Cocoa"
+        print_check_msg "$dep_checked_cocoa" "yes\n"
+        dep_checked_cocoa=1
+        return 1
     done
-	
-	echo no
-	return 1
+
+    print_check_msg "$dep_checked_cocoa" "no\n"
+    dep_checked_cocoa=1
+    return 0
 }
+dependency_error_winui()
+{
+    print_check_msg "$dep_checked_winui" "checking for winui... "
+    # dependency winui platform="windows"
+    while true
+    do
+        if notisplatform "windows"; then
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WINUI"
+        print_check_msg "$dep_checked_winui" "yes\n"
+        dep_checked_winui=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_winui" "no\n"
+    dep_checked_winui=1
+    return 0
+}
+
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
 
 DEPENDENCIES_FAILED=
 ERROR=0
-# general dependencies
-CFLAGS=
-LDFLAGS=
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
 while true
 do
-    if isnotplatform "macos"; then
+    while true
+    do
+        if [ -z "$lang_c" ] ; then
+            ERROR=1
+            break
+        fi
+
+        break
+    done
+    break
+done
+while true
+do
+    if notisplatform "macos"; then
         break
     fi
     while true
     do
-        
-		cat >> $TEMP_DIR/make.mk << __EOF__
-OBJ_EXT = o
-LIB_EXT = a
+
+        cat >> "$TEMP_DIR/make.mk" << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
 PACKAGE_SCRIPT = package_osx.sh
-
 __EOF__
-        
         break
     done
-    
     break
 done
 while true
 do
-    if isnotplatform "unix"; then
+    if notisplatform "unix"; then
         break
     fi
-    if isplatform "macos"; then
+    if isplatform "macos" || istoolchain "macos"; then
         break
     fi
     while true
     do
-        
-		cat >> $TEMP_DIR/make.mk << __EOF__
-OBJ_EXT = o
-LIB_EXT = a
+
+        cat >> "$TEMP_DIR/make.mk" << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
 PACKAGE_SCRIPT = package_unix.sh
-
 __EOF__
-        
         break
     done
-    
+    break
+done
+while true
+do
+    if notisplatform "bsd"; then
+        break
+    fi
+    while true
+    do
+
+        TEMP_CFLAGS="$TEMP_CFLAGS -I/usr/local/include"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -L/usr/local/lib"
+        break
+    done
     break
 done
 
-# add general dependency flags to config.mk
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${CFLAGS}" ]; then
-    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+# add general dependency flags to flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${CXXFLAGS}" ]; then
-    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${LDFLAGS}" ]; then
-    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
 
 #
 # OPTION VALUES
 #
+checkopt_toolkit_libadwaita()
+{
+    VERR=0
+    if dependency_error_libadwaita ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+    return 0
+}
+checkopt_toolkit_gtk4()
+{
+    VERR=0
+    if dependency_error_gtk4 ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+    return 0
+}
 checkopt_toolkit_gtk3()
 {
-	VERR=0
-	dependency_gtk3
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_gtk3 ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_cairo.o
-
 __EOF__
-	return 0
+    return 0
 }
 checkopt_toolkit_gtk2()
 {
-	VERR=0
-	dependency_gtk2
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_gtk2 ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_cairo.o
-
 __EOF__
-	return 0
+    return 0
 }
 checkopt_toolkit_gtk2legacy()
 {
-	VERR=0
-	dependency_gtk2legacy
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_gtk2legacy ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_gdk.o
-
 __EOF__
-	return 0
+    return 0
 }
 checkopt_toolkit_qt5()
 {
-	VERR=0
-	dependency_qt5
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_qt5 ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = qt
 LD = $(CXX)
-
 __EOF__
-	return 0
+    return 0
 }
 checkopt_toolkit_qt4()
 {
-	VERR=0
-	dependency_qt4
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_qt4 ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = qt
 LD = $(CXX)
-
 __EOF__
-	return 0
+    return 0
+}
+checkopt_toolkit_cocoa()
+{
+    VERR=0
+    if dependency_error_cocoa ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = cocoa
+__EOF__
+    return 0
 }
 checkopt_toolkit_motif()
 {
-	VERR=0
-	dependency_motif
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    if dependency_error_motif ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = motif
-
 __EOF__
-	return 0
+    return 0
 }
 
 #
 # TARGETS
 #
-CFLAGS=
-CXXFLAGS=
-LDFLAGS=
 
-# Target: tk
-CFLAGS=
-LDFLAGS=
-CXXFLAGS=
+echo >> "$TEMP_DIR/flags.mk"
+echo "configuring target: tk"
+echo "# flags for target tk" >> "$TEMP_DIR/flags.mk"
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
 
 
 # Features
 
 # Option: --toolkit
-if [ -z $OPT_TOOLKIT ]; then
-	SAVED_ERROR=$ERROR
-	SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
-	ERROR=0
-	while true
-	do
-		if isplatform "windows"; then
-		checkopt_toolkit_wpf
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: wpf" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		fi
-		if isplatform "macos"; then
-		checkopt_toolkit_cocoa
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: cocoa" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		fi
-		checkopt_toolkit_gtk3
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: gtk3" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		checkopt_toolkit_qt5
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: qt5" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		checkopt_toolkit_gtk2
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: gtk2" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		checkopt_toolkit_qt4
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: qt4" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		checkopt_toolkit_motif
-		if [ $? -eq 0 ]; then
-			echo "  toolkit: motif" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		break
-	done
-	if [ $ERROR -ne 0 ]; then
-		SAVED_ERROR=1
-	fi
-	ERROR=$SAVED_ERROR
-	DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+if [ -z "$OPT_TOOLKIT" ]; then
+    echo "auto-detecting option 'toolkit'"
+    SAVED_ERROR="$ERROR"
+    SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+    ERROR=1
+    while true
+    do
+        if isplatform "windows"; then
+        if checkopt_toolkit_winui ; then
+            echo "  toolkit: winui" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        fi
+        if isplatform "macos"; then
+        if checkopt_toolkit_cocoa ; then
+            echo "  toolkit: cocoa" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        fi
+        if checkopt_toolkit_gtk4 ; then
+            echo "  toolkit: gtk4" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_gtk3 ; then
+            echo "  toolkit: gtk3" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_gtk2 ; then
+            echo "  toolkit: gtk2" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_qt4 ; then
+            echo "  toolkit: qt4" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_motif ; then
+            echo "  toolkit: motif" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        break
+    done
+    if [ $ERROR -ne 0 ]; then
+        SAVED_ERROR=1
+        SAVED_DEPENDENCIES_FAILED="option 'toolkit' $SAVED_DEPENDENCIES_FAILED"
+    fi
+    ERROR="$SAVED_ERROR"
+    DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
 else
-	if false; then
-		false
-	elif [ $OPT_TOOLKIT = "gtk3" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_gtk3
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	elif [ $OPT_TOOLKIT = "gtk2" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_gtk2
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	elif [ $OPT_TOOLKIT = "gtk2legacy" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_gtk2legacy
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	elif [ $OPT_TOOLKIT = "qt5" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_qt5
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	elif [ $OPT_TOOLKIT = "qt4" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_qt4
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	elif [ $OPT_TOOLKIT = "motif" ]; then
-		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
-		checkopt_toolkit_motif
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	fi
+    echo "checking option toolkit = $OPT_TOOLKIT"
+    if false; then
+        false
+    elif [ "$OPT_TOOLKIT" = "libadwaita" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_libadwaita ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk4" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk4 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk3" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk3 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk2" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk2 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk2legacy" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk2legacy ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "qt5" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_qt5 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "qt4" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_qt4 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "cocoa" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_cocoa ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "motif" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_motif ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    fi
 fi
 
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${CFLAGS}" ]; then
-    echo "TK_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "TK_CFLAGS  += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "TK_CXXFLAGS  += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${CXXFLAGS}" ]; then
-    echo "TK_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+if [ "$BUILD_TYPE" = "debug" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'TK_CFLAGS += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'TK_CXXFLAGS += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
 fi
-if [ ! -z "${LDFLAGS}" ]; then
-    echo "TK_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+if [ "$BUILD_TYPE" = "release" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'TK_CFLAGS += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'TK_CXXFLAGS += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "TK_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
 
+
+# final result
 if [ $ERROR -ne 0 ]; then
-	echo
-	echo "Error: Unresolved dependencies"
-	echo $DEPENDENCIES_FAILED
-	rm -Rf $TEMP_DIR
-	exit 1
+    echo
+    echo "Error: Unresolved dependencies"
+    echo "$DEPENDENCIES_FAILED"
+    abort_configure
 fi
 
 echo "configure finished"
 echo
 echo "Build Config:"
-echo "  PREFIX:    $PREFIX"
-echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "  PREFIX:      $prefix"
+echo "  TOOLCHAIN:   $TOOLCHAIN_NAME"
 echo "Options:"
-cat $TEMP_DIR/options
+cat "$TEMP_DIR/options"
 echo
-cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
-rm -Rf $TEMP_DIR
 
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
 
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
--- a/make/Makefile.mk	Sun May 23 09:44:43 2021 +0200
+++ b/make/Makefile.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 #
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 #
-# Copyright 2013 Olaf Wintermann. All rights reserved.
+# Copyright 2023 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:
@@ -32,7 +32,7 @@
 include config.mk
 
 BUILD_DIRS = build/bin build/lib
-BUILD_DIRS += build/application
+BUILD_DIRS += build/application build/ucx
 BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
 
 all: $(BUILD_DIRS) ucx ui application
@@ -41,11 +41,11 @@
 $(BUILD_DIRS):
 	mkdir -p $@
 
-ucx: FORCE
-	cd ucx; $(MAKE)
+ui: ucx FORCE
+	cd ui; $(MAKE) all
 
-ui: FORCE
-	cd ui; $(MAKE) all
+ucx: FORCE
+	cd ucx; $(MAKE) all
 
 application: ui FORCE
 	cd application; $(MAKE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/cc.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,14 @@
+#
+# cc toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
\ No newline at end of file
--- a/make/clang.mk	Sun May 23 09:44:43 2021 +0200
+++ b/make/clang.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -2,8 +2,13 @@
 # clang toolchain config
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -fPIC
 SHLIB_LDFLAGS = -shared
--- a/make/configure.vm	Sun May 23 09:44:43 2021 +0200
+++ b/make/configure.vm	Sat Jan 04 16:38:48 2025 +0100
@@ -1,78 +1,65 @@
 #!/bin/sh
 
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+    :
+else
+    echo "Cannot create tmp dir $TEMP_DIR"
+    echo "Abort"
+    exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
+
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
+
+# custom variables
 #foreach( $var in $vars )
 #if( $var.exec )
-${var.name}=`${var.value}`
+${var.varName}=`${var.value}`
 #else
-${var.name}=${var.value}
+${var.varName}="${var.value}"
 #end
 #end
 
-#if ( ! $project.hasVar("PREFIX") )
-PREFIX=/usr
+# features
+#foreach( $feature in $features )
+#if( ${feature.auto} )
+${feature.varName}=auto
 #end
-#if ( ! $project.hasVar("EPREFIX") )
-EPREFIX=$PREFIX
 #end
 
-#if ( ! $project.hasVar("BINDIR") )
-BINDIR=
-#end
-#if ( ! $project.hasVar("SBINDIR") )
-SBINDIR=
-#end
-#if ( ! $project.hasVar("LIBDIR") )
-LIBDIR=
-#end
-#if ( ! $project.hasVar("LIBEXECDIR") )
-LIBEXECDIR=
-#end
-#if ( ! $project.hasVar("DATADIR") )
-DATADIR=
-#end
-#if ( ! $project.hasVar("SYSCONFDIR") )
-SYSCONFDIR=
-#end
-#if ( ! $project.hasVar("SHAREDSTATEDIR") )
-SHAREDSTATEDIR=
-#end
-#if ( ! $project.hasVar("LOCALSTATEDIR") )
-LOCALSTATEDIR=
-#end
-#if ( ! $project.hasVar("INCLUDEDIR") )
-INCLUDEDIR=
-#end
-#if ( ! $project.hasVar("INFODIR") )
-INFODIR=
-#end
-#if ( ! $project.hasVar("MANDIR") )
-MANDIR=
-#end
-
-OS=`uname -s`
-OS_VERSION=`uname -r`
-
-TEMP_DIR=".tmp-`uname -n`"
-mkdir -p $TEMP_DIR
-if [ $? -ne 0 ]; then
-	echo "Cannot create tmp dir"
-	echo "Abort"
-fi
-touch $TEMP_DIR/options
-touch $TEMP_DIR/features
-
-# features
-#foreach( $feature in $features )
-#if( ${feature.isDefault()} )
-${feature.getVarName()}=on
-#end
-#end
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
 
 # help text
 printhelp()
 {
-	echo "Usage: $0 [OPTIONS]..."
-	cat << __EOF__
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
 Installation directories:
   --prefix=PREFIX         path prefix for architecture-independent files
                           [/usr]
@@ -85,28 +72,28 @@
   --sysconfdir=DIR        system configuration files [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
   --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
   --infodir=DIR           info documentation [DATAROOTDIR/info]
   --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
 
 #if( $options.size() > 0 )
 Options:
+  --debug                 add extra compile flags for debug builds
+  --release               add extra compile flags for release builds
 #foreach( $opt in $options )
-  --${opt.getArgument()}=${opt.getValuesString()}
+  --${opt.argument}=${opt.valuesString}
 #end
 
 #end
 #if( $features.size() > 0 )
 Optional Features:
 #foreach( $feature in $features )
-#if( $feature.default )
-  --disable-${feature.arg}
-#else
-  --enable-${feature.arg}
-#end
+${feature.helpText}
 #end
 
 #end
@@ -114,297 +101,328 @@
 }
 
 #
-# parse arguments 
+# parse arguments
 #
+BUILD_TYPE="default"
 #set( $D = '$' )
-for ARG in $@
+for ARG in "$@"
 do
     case "$ARG" in
-		"--prefix="*)         PREFIX=${D}{ARG#--prefix=} ;;
-		"--exec-prefix="*)    EPREFIX=${D}{ARG#--exec-prefix=} ;;
-		"--bindir="*)         BINDIR=${D}{ARG#----bindir=} ;;
-		"--sbindir="*)        SBINDIR=${D}{ARG#--sbindir=} ;;
-		"--libdir="*)         LIBDIR=${D}{ARG#--libdir=} ;;
-		"--libexecdir="*)     LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
-		"--datadir="*)        DATADIR=${D}{ARG#--datadir=} ;;
-		"--sysconfdir="*)     SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
-		"--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
-		"--localstatedir="*)  LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
-		"--includedir="*)     INCLUDEDIR=${D}{ARG#--includedir=} ;;
-		"--infodir="*)        INFODIR=${D}{ARG#--infodir=} ;;
-		"--mandir"*)          MANDIR=${D}{ARG#--mandir} ;;
-		"--help"*) printhelp; exit 1 ;;
-	#foreach( $opt in $options )
-    	"--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
+        "--prefix="*)         prefix=${D}{ARG#--prefix=} ;;
+        "--exec-prefix="*)    exec_prefix=${D}{ARG#--exec-prefix=} ;;
+        "--bindir="*)         bindir=${D}{ARG#----bindir=} ;;
+        "--sbindir="*)        sbindir=${D}{ARG#--sbindir=} ;;
+        "--libdir="*)         libdir=${D}{ARG#--libdir=} ;;
+        "--libexecdir="*)     libexecdir=${D}{ARG#--libexecdir=} ;;
+        "--datarootdir="*)    datarootdir=${D}{ARG#--datarootdir=} ;;
+        "--datadir="*)        datadir=${D}{ARG#--datadir=} ;;
+        "--sysconfdir="*)     sysconfdir=${D}{ARG#--sysconfdir=} ;;
+        "--sharedstatedir="*) sharedstatedir=${D}{ARG#--sharedstatedir=} ;;
+        "--localstatedir="*)  localstatedir=${D}{ARG#--localstatedir=} ;;
+        "--includedir="*)     includedir=${D}{ARG#--includedir=} ;;
+        "--infodir="*)        infodir=${D}{ARG#--infodir=} ;;
+        "--mandir"*)          mandir=${D}{ARG#--mandir} ;;
+        "--localedir"*)       localedir=${D}{ARG#--localedir} ;;
+        "--help"*) printhelp; abort_configure ;;
+        "--debug")           BUILD_TYPE="debug" ;;
+        "--release")         BUILD_TYPE="release" ;;
+    #foreach( $opt in $options )
+        "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
     #end
-	#foreach( $feature in $features )
-		"--enable-${feature.arg}") ${feature.getVarName()}=on ;;
-		"--disable-${feature.arg}") unset ${feature.getVarName()} ;;
-	#end
-		"-"*) echo "unknown option: $ARG"; exit 1 ;;
-	esac
+    #foreach( $feature in $features )
+        "--enable-${feature.arg}") ${feature.varName}=on ;;
+        "--disable-${feature.arg}") unset ${feature.varName} ;;
+    #end
+        "-"*) echo "unknown option: $ARG"; abort_configure ;;
+    esac
 done
 
-# set dir variables
-if [ -z "$BINDIR" ]; then
-	BINDIR=$EPREFIX/bin
-fi
-if [ -z "$SBINDIR" ]; then
-	SBINDIR=$EPREFIX/sbin
-fi
-if [ -z "$LIBDIR" ]; then
-	LIBDIR=$EPREFIX/lib
-fi
-if [ -z "$LIBEXEC" ]; then
-	LIBEXECDIR=$EPREFIX/libexec
-fi
-if [ -z "$DATADIR" ]; then
-	DATADIR=$PREFIX/share
-fi
-if [ -z "$SYSCONFDIR" ]; then
-	SYSCONFDIR=$PREFIX/etc
-fi
-if [ -z "$SHAREDSTATEDIR" ]; then
-	SHAREDSTATEDIR=$PREFIX/com
-fi
-if [ -z "$LOCALSTATEDIR" ]; then
-	LOCALSTATEDIR=$PREFIX/var
-fi
-if [ -z "$INCLUDEDIR" ]; then
-	INCLUDEDIR=$PREFIX/include
-fi
-if [ -z "$INFODIR" ]; then
-	INFODIR=$PREFIX/info
-fi
-if [ -z "$MANDIR" ]; then
-	MANDIR=$PREFIX/man
+## Begin unparsed content. **
+#[[
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+    # CONFIG_SITE may contain space separated file names
+    for cs in $CONFIG_SITE; do
+        printf "loading defaults from $cs... "
+        . "$cs"
+        echo ok
+    done
+elif [ -f "$prefix/share/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/share/config.site"
+    echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/etc/config.site"
+    echo ok
 fi
 
-which pkg-config > /dev/null
-if [ $? -eq 0 ]; then
-    PKG_CONFIG=pkg-config
-else
-    PKG_CONFIG=false
-fi
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
 
 # Simple uname based platform detection
 # $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
 printf "detect platform... "
-if [ $OS = SunOS ]; then
+if [ "$OS" = "SunOS" ]; then
     PLATFORM="solaris sunos unix svr4"
-fi
-if [ $OS = Linux ]; then
+elif [ "$OS" = "Linux" ]; then
     PLATFORM="linux unix"
-fi
-if [ $OS = FreeBSD ]; then
+elif [ "$OS" = "FreeBSD" ]; then
     PLATFORM="freebsd bsd unix"
-fi
-if [ $OS = Darwin ]; then
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
     PLATFORM="macos osx bsd unix"
-fi
-echo $OS | grep "MINGW" > /dev/null
-if [ $? -eq 0 ]; then
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
     PLATFORM="windows mingw"
 fi
-
-if [ -z "$PLATFORM" ]; then
-    PLATFORM="unix"
-fi
+: ${PLATFORM:="unix"}
 
-for p in $PLATFORM
-do
-	PLATFORM_NAME=$p
-	break
-done
-echo $PLATFORM_NAME
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
 
 isplatform()
 {
     for p in $PLATFORM
     do
-        if [ $p = $1 ]; then
+        if [ "$p" = "$1" ]; then
             return 0
         fi
     done
     return 1
 }
-isnotplatform()
+notisplatform()
 {
     for p in $PLATFORM
     do
-        if [ $p = $1 ]; then
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
             return 1
         fi
     done
     return 0
 }
-
-# generate config.mk and config.h
-cat > $TEMP_DIR/config.mk << __EOF__
-#
-# config.mk generated by configure
-#
-
-# general vars
-#foreach( $var in $vars )
-${var.name}=$${var.name}
-#end
+]]#
+## End of unparsed content **
 
-#if ( ! $project.hasVar("PREFIX") )
-PREFIX=$PREFIX
-#end
-#if ( ! $project.hasVar("EPREFIX") )
-EPREFIX=$EPREFIX
-#end
-
-#if ( ! $project.hasVar("BINDIR") )
-BINDIR=$BINDIR
-#end
-#if ( ! $project.hasVar("SBINDIR") )
-SBINDIR=$SBINDIR
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
+#foreach( $var in $vars )
+${var.varName}=${D}${var.varName}
 #end
-#if ( ! $project.hasVar("LIBDIR") )
-LIBDIR=$LIBDIR
-#end
-#if ( ! $project.hasVar("LIBEXECDIR") )
-LIBEXECDIR=$LIBEXECDIR
-#end
-#if ( ! $project.hasVar("DATADIR") )
-DATADIR=$DATADIR
-#end
-#if ( ! $project.hasVar("SYSCONFDIR") )
-SYSCONFDIR=$SYSCONFDIR
-#end
-#if ( ! $project.hasVar("SHAREDSTATEDIR") )
-SHAREDSTATEDIR=$SHAREDSTATEDIR
-#end
-#if ( ! $project.hasVar("LOCALSTATEDIR") )
-LOCALSTATEDIR=$LOCALSTATEDIR
-#end
-#if ( ! $project.hasVar("INCLUDEDIR") )
-INCLUDEDIR=$INCLUDEDIR
-#end
-#if ( ! $project.hasVar("INFODIR") )
-INFODIR=$INFODIR
-#end
-#if ( ! $project.hasVar("MANDIR") )
-MANDIR=$MANDIR
-#end
-
 __EOF__
 
-echo > $TEMP_DIR/make.mk
-
-ENV_CFLAGS=$CFLAGS
-ENV_LDFLAGS=$LDFLAGS
-ENV_CXXFLAGS=$CXXFLAGS
-
-# Toolchain detection
-# this will insert make vars to config.mk
+# toolchain detection utilities
 . make/toolchain.sh
 
-# add user specified flags to config.mk
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${ENV_CFLAGS}" ]; then
-    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
-fi
-if [ ! -z "${ENV_CXXFLAGS}" ]; then
-    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
-fi
-if [ ! -z "${ENV_LDFLAGS}" ]; then
-    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
-fi
-
 #
 # DEPENDENCIES
 #
 
-#foreach( $dependency in $namedDependencies )
-dependency_${dependency.name}()
+# check languages
+lang_c=
+lang_cpp=
+#foreach( $lang in $languages )
+if detect_${lang}_compiler ; then
+    lang_${lang}=1
+fi
+#end
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
 {
-    printf "checking for ${dependency.name}... "
-    #foreach( $sub in $dependency.getSubdependencies() )
-    # dependency $sub.name $sub.getPlatformString()
+    if "$PKG_CONFIG" --exists "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+    else return 1 ; fi
+    return 0
+}
+
+print_check_msg()
+{
+    if [ -z "$1" ]; then
+        shift
+        printf "$@"
+    fi
+}
+
+#foreach( $dependency in $namedDependencies )
+dependency_error_${dependency.id}()
+{
+    print_check_msg "${D}dep_checked_${dependency.id}" "checking for ${dependency.name}... "
+    #foreach( $sub in $dependency.subdependencies )
+    # dependency $sub.fullName
     while true
     do
-    	#if( $sub.platform )
-    	if isnotplatform "${sub.platform}"; then
+        #if( $sub.platform )
+        if notisplatform "${sub.platform}"; then
+            break
+        fi
+        #end
+        #if( $sub.toolchain )
+        if notistoolchain "${sub.toolchain}"; then
+            break
+        fi
+        #end
+        #foreach( $np in $sub.notList )
+        if isplatform "${np}" || istoolchain "${np}"; then
             break
         fi
-    	#end
-		#foreach( $not in $sub.getNotList() )
-		if isplatform "${not}"; then
+        #end
+        #foreach( $lang in $sub.lang )
+        if [ -z "$lang_${lang}" ] ; then
             break
         fi
-		#end
+        #end
         #if( $sub.pkgconfig.size() > 0 )
         if [ -z "$PKG_CONFIG" ]; then
-        	break
+            break
+        fi
+        #end
+        #foreach( $test in $sub.tests )
+        if $test > /dev/null ; then
+            :
+        else
+            break
         fi
         #end
         #foreach( $pkg in $sub.pkgconfig )
-		$PKG_CONFIG $pkg.getPkgConfigParam()
-        if [ $? -ne 0 ] ; then
+        if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+        else
             break
         fi
-        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
-        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
         #end
         #foreach( $flags in $sub.flags )
         #if( $flags.exec )
-        $flags.value > /dev/null
-        if [ $? -eq 0 ]; then
-            $flags.varName="$$flags.varName `$flags.value`"
+        if tmp_flags=`$flags.value` ; then
+            TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
         else
             break
         fi
         #else
-        $flags.varName="$$flags.varName $flags.value"    
+        TEMP_$flags.varName="$TEMP_$flags.varName $flags.value"
         #end
         #end
-        #foreach( $test in $sub.tests )
-        $test > /dev/null
-        if [ $? -ne 0 ]; then
-        	break
-        fi
-        #end
-		#if ( $sub.make.length() > 0 )
-		cat >> $TEMP_DIR/make.mk << __EOF__
-# Dependency: $dependency.name		
+        #if ( $sub.make.length() > 0 )
+        cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name
 $sub.make
 __EOF__
         #end
-		echo yes
-        return 0
+        print_check_msg "${D}dep_checked_${dependency.id}" "yes\n"
+        dep_checked_${dependency.id}=1
+        return 1
     done
-	
-	#end
-	echo no
-	return 1
+
+    #end
+    print_check_msg "${D}dep_checked_${dependency.id}" "no\n"
+    dep_checked_${dependency.id}=1
+    return 0
 }
 #end
 
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
+
 DEPENDENCIES_FAILED=
 ERROR=0
 #if( $dependencies.size() > 0 )
-# general dependencies
-CFLAGS=
-LDFLAGS=
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
 #foreach( $dependency in $dependencies )
 while true
 do
-	#if( $dependency.platform )
-    if isnotplatform "${dependency.platform}"; then
+    #if( $dependency.platform )
+    if notisplatform "${dependency.platform}"; then
+        break
+    fi
+    #end
+    #if( $dependency.toolchain )
+    if notistoolchain "${dependency.toolchain}"; then
         break
     fi
     #end
-	#foreach( $not in $dependency.getNotList() )
-    if isplatform "${not}"; then
+    #foreach( $np in $dependency.notList )
+    if isplatform "${np}" || istoolchain "${np}"; then
         break
     fi
-	#end
+    #end
     while true
     do
+        #foreach( $lang in $dependency.lang )
+        if [ -z "$lang_${lang}" ] ; then
+            ERROR=1
+            break
+        fi
+        #end
         #if( $dependency.pkgconfig.size() > 0 )
         if [ -z "$PKG_CONFIG" ]; then
             ERROR=1
@@ -412,54 +430,54 @@
         fi
         #end
         #foreach( $pkg in $dependency.pkgconfig )
-        printf "checking for pkg-config package $pkg.getPkgConfigParam()... "
-		$PKG_CONFIG $pkg.getPkgConfigParam()
-        if [ $? -ne 0 ]; then
-            echo no
+        print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "checking for pkg-config package $pkg.name... "
+        if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+            print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "yes\n"
+            dep_pkgconfig_checked_${pkg.id}=1
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+        else
+            print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "no\n"
+            dep_pkgconfig_checked_${pkg.id}=1
             ERROR=1
             break
         fi
-        echo yes
-        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
-        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
         #end
-        
+
         #foreach( $flags in $dependency.flags )
         #if( $flags.exec )
         $flags.value > /dev/null
-        if [ $? -ne 0 ]; then
-            $flags.varName="$$flags.varName `$flags.value`"
+        if tmp_flags=`$flags.value` ; then
+            TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
         else
             ERROR=1
             break
         fi
         #else
-        $flags.varName="$$flags.varName $flags.value"    
+        TEMP_$flags.varName="$TEMP_$flags.varName $flags.value"
         #end
         #end
-		#if ( $dependency.make.length() > 0 )
-		cat >> $TEMP_DIR/make.mk << __EOF__
+        #if ( $dependency.make.length() > 0 )
+        cat >> "$TEMP_DIR/make.mk" << __EOF__
 $dependency.make
 __EOF__
         #end
-        
         break
     done
-    
     break
 done
 #end
 
-# add general dependency flags to config.mk
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${CFLAGS}" ]; then
-    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+# add general dependency flags to flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${CXXFLAGS}" ]; then
-    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${LDFLAGS}" ]; then
-    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
 #end
 
@@ -470,25 +488,25 @@
 #foreach( $val in $opt.values )
 ${val.func}()
 {
-	VERR=0
-	#foreach( $dep in $val.dependencies )
-	dependency_$dep
-	if [ $? -ne 0 ]; then
-		VERR=1
-	fi
-	#end
-	if [ $VERR -ne 0 ]; then
-		return 1
-	fi
-	#foreach( $def in $val.defines )
-		CFLAGS="$CFLAGS ${def.toFlags()}"
-	#end
-	#if( $val.hasMake() )
-	cat >> $TEMP_DIR/make.mk << __EOF__
+    VERR=0
+    #foreach( $dep in $val.dependencies )
+    if dependency_error_$dep ; then
+        VERR=1
+    fi
+    #end
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    #foreach( $def in $val.defines )
+        TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}"
+        TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}"
+    #end
+    #if( $val.hasMake() )
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
 $val.make
 __EOF__
-	#end
-	return 0
+    #end
+    return 0
 }
 #end
 #end
@@ -496,120 +514,160 @@
 #
 # TARGETS
 #
-CFLAGS=
-CXXFLAGS=
-LDFLAGS=
 
 #foreach( $target in $targets )
+echo >> "$TEMP_DIR/flags.mk"
 #if ( $target.name )
-# Target: $target.name
+echo "configuring target: $target.name"
+echo "# flags for target $target.name" >> "$TEMP_DIR/flags.mk"
 #else
-# Target
+echo "configuring global target"
+echo "# flags for unnamed target" >> "$TEMP_DIR/flags.mk"
 #end
-CFLAGS=
-LDFLAGS=
-CXXFLAGS=
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
 
 #foreach( $dependency in $target.dependencies )
-dependency_$dependency
-if [ $? -ne 0 ]; then
-	DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
-	ERROR=1
+if dependency_error_$dependency; then
+    DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+    ERROR=1
 fi
 #end
 
 # Features
 #foreach( $feature in $target.features )
-if [ ! -z "$${feature.getVarName()}" ]; then
+if [ -n "${D}${feature.varName}" ]; then
 #foreach( $dependency in $feature.dependencies )
-	# check dependency
-	dependency_$dependency
-	if [ $? -ne 0 ]; then
-		# "auto" features can fail and are just disabled in this case
-		if [ $${feature.getVarName()} != "auto" ]; then
-			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
-			ERROR=1
-		fi
-	fi
+    # check dependency
+    if dependency_error_$dependency ; then
+        # "auto" features can fail and are just disabled in this case
+        if [ "${D}${feature.varName}" = "auto" ]; then
+            DISABLE_${feature.varName}=1
+        else
+            DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+            ERROR=1
+        fi
+    fi
 #end
+    if [ -n "$DISABLE_${feature.varName}" ]; then
+        unset ${feature.varName}
+    fi
 fi
 #end
 
 #foreach( $opt in $target.options )
 # Option: --${opt.argument}
-if [ -z ${D}${opt.getVarName()} ]; then
-	SAVED_ERROR=$ERROR
-	SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
-	ERROR=0
-	while true
-	do
-		#foreach( $optdef in $opt.defaults )
-		#if( $optdef.platform )
-		if isplatform "$optdef.platform"; then
-		#end
-		$optdef.func
-		if [ $? -eq 0 ]; then
-			echo "  ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options
-			ERROR=0
-			break
-		fi
-		#if( $optdef.platform )
-		fi
-		#end
-		#end
-		break
-	done
-	if [ $ERROR -ne 0 ]; then
-		SAVED_ERROR=1
-	fi
-	ERROR=$SAVED_ERROR
-	DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+if [ -z "${D}${opt.varName}" ]; then
+    echo "auto-detecting option '${opt.argument}'"
+    SAVED_ERROR="$ERROR"
+    SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+    ERROR=1
+    while true
+    do
+        #foreach( $optdef in $opt.defaults )
+        #if( $optdef.platform )
+        if isplatform "$optdef.platform"; then
+        #end
+        if $optdef.func ; then
+            echo "  ${opt.argument}: ${optdef.valueName}" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        #if( $optdef.platform )
+        fi
+        #end
+        #end
+        break
+    done
+    if [ $ERROR -ne 0 ]; then
+        SAVED_ERROR=1
+        SAVED_DEPENDENCIES_FAILED="option '${opt.argument}' $SAVED_DEPENDENCIES_FAILED"
+    fi
+    ERROR="$SAVED_ERROR"
+    DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
 else
-	if false; then
-		false
-	#foreach( $optval in $opt.values )
-	elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then
-		echo "  ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options
-		$optval.func
-		if [ $? -ne 0 ]; then
-			ERROR=1
-		fi
-	#end
-	fi
+    echo "checking option ${opt.argument} = ${D}${opt.varName}"
+    if false; then
+        false
+    #foreach( $optval in $opt.values )
+    elif [ "${D}${opt.varName}" = "${optval.value}" ]; then
+        echo "  ${opt.argument}: ${D}${opt.varName}" >> $TEMP_DIR/options
+        if $optval.func ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
+        fi
+    #end
+    fi
 fi
 #end
 
-echo >> $TEMP_DIR/config.mk
-if [ ! -z "${CFLAGS}" ]; then
-    echo "${target.getCFlags()}  += $CFLAGS" >> $TEMP_DIR/config.mk
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "${target.cFlags}  += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "${target.cxxFlags}  += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
-if [ ! -z "${CXXFLAGS}" ]; then
-    echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk
+if [ "$BUILD_TYPE" = "debug" ]; then
+    if [ -n "$lang_c" ]; then
+        echo '${target.cFlags} += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo '${target.cxxFlags} += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
 fi
-if [ ! -z "${LDFLAGS}" ]; then
-    echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk
+if [ "$BUILD_TYPE" = "release" ]; then
+    if [ -n "$lang_c" ]; then
+        echo '${target.cFlags} += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo '${target.cxxFlags} += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "${target.ldFlags} += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
 fi
 
 #end
+
+# final result
 if [ $ERROR -ne 0 ]; then
-	echo
-	echo "Error: Unresolved dependencies"
-	echo $DEPENDENCIES_FAILED
-	rm -Rf $TEMP_DIR
-	exit 1
+    echo
+    echo "Error: Unresolved dependencies"
+    echo "$DEPENDENCIES_FAILED"
+    abort_configure
 fi
 
 echo "configure finished"
 echo
 echo "Build Config:"
-echo "  PREFIX:    $PREFIX"
-echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "  PREFIX:      $prefix"
+echo "  TOOLCHAIN:   $TOOLCHAIN_NAME"
 #if ( $options.size() > 0 )
 echo "Options:"
-cat $TEMP_DIR/options
+cat "$TEMP_DIR/options"
+#end
+#if ( $features.size() > 0 )
+echo "Features:"
+#foreach( $feature in $features )
+if [ -n "${D}${feature.varName}" ]; then
+echo "  $feature.name: on"
+else
+echo "  $feature.name: off"
+fi
+#end
 #end
 echo
-cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
-rm -Rf $TEMP_DIR
 
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
 
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
--- a/make/gcc.mk	Sun May 23 09:44:43 2021 +0200
+++ b/make/gcc.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -2,8 +2,13 @@
 # gcc toolchain config
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -fPIC
 SHLIB_LDFLAGS = -shared
--- a/make/project.xml	Sun May 23 09:44:43 2021 +0200
+++ b/make/project.xml	Sat Jan 04 16:38:48 2025 +0100
@@ -1,12 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project>
-	<!--
-	<dependency name="gtk4">
-		<pkgconfig>gtk+-4.0</pkgconfig>
-		<cflags>-DUI_GTK3</cflags>
+<project version="0.3" xmlns="http://unixwork.de/uwproj">
+	<dependency>
+		<lang>c</lang>
+	</dependency>
+	
+	<dependency name="libadwaita">
+		<pkgconfig>libadwaita-1</pkgconfig>
+		<cflags>-DUI_GTK4 -DUI_LIBADWAITA</cflags>
 		<ldflags>-lpthread</ldflags>
 	</dependency>
-	-->
+	<dependency name="gtk4">
+		<pkgconfig>gtk4</pkgconfig>
+		<cflags>-DUI_GTK4</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
 	<dependency name="gtk3">
 		<pkgconfig>gtk+-3.0</pkgconfig>
 		<cflags>-DUI_GTK3</cflags>
@@ -23,50 +30,66 @@
 		<cflags>-DUI_GTK2 -DUI_GTK2LEGACY</cflags>
 		<ldflags>-lpthread</ldflags>
 	</dependency>
-	<dependency name="wpf" platform="windows">
-		<cflags>-DUI_WPF</cflags>
+	<dependency name="winui" platform="windows">
+		<cflags>-DUI_WINUI</cflags>
 	</dependency>
+	<!--
 	<dependency name="qt4">
 		<test>which qmake-qt4</test>
-		<cflags type="exec">qmake-qt4 -o - /dev/null | grep DEFINES\ </cflags>
-		<cflags type="exec">qmake-qt4 -o - /dev/null | grep INCPATH\ </cflags>
-		<ldflags type="exec"><cflags type="exec">qmake-qt4 -o - /dev/null | grep LIBS\ </cflags></ldflags>
+		<cflags exec="true">qmake-qt4 -o - /dev/null | grep DEFINES\ </cflags>
+		<cflags exec="true">qmake-qt4 -o - /dev/null | grep INCPATH\ </cflags>
+		<ldflags exec="true">qmake-qt4 -o - /dev/null | grep LIBS\ </ldflags>
 	</dependency>
 	<dependency name="qt5">
 		<test>which qmake-qt5</test>
-		<cflags type="exec">qmake-qt5 -o - /dev/null | grep DEFINES\ </cflags>
-		<cflags type="exec">qmake-qt5 -o - /dev/null | grep INCPATH\ </cflags>
-		<ldflags type="exec"><cflags type="exec">qmake-qt5 -o - /dev/null | grep LIBS\ </cflags></ldflags>
+		<cflags exec="true">qmake-qt5 -o - /dev/null | grep DEFINES\ </cflags>
+		<cflags exec="true">qmake-qt5 -o - /dev/null | grep INCPATH\ </cflags>
+		<ldflags exec="true">qmake-qt5 -o - /dev/null | grep LIBS\ </ldflags>
 	</dependency>
+	-->
 	<dependency name="cocoa" platform="macos">
 		<cflags>-DUI_COCOA</cflags>
 		<ldflags>-lobjc -framework Cocoa</ldflags>
 	</dependency>
+	
+	<dependency name="motif" platform="bsd">
+		<cflags>-DUI_MOTIF -I/usr/local/include/X11</cflags>
+		<ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+	</dependency>
+	
 	<dependency name="motif">
 		<cflags>-DUI_MOTIF</cflags>
 		<ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
 	</dependency>
 	
 	<dependency platform="macos">
-		<make>OBJ_EXT = o</make>
-		<make>LIB_EXT = a</make>
+		<make>OBJ_EXT = .o</make>
+		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_osx.sh</make>
 	</dependency>
 	<dependency platform="unix" not="macos">
-		<make>OBJ_EXT = o</make>
-		<make>LIB_EXT = a</make>
+		<make>OBJ_EXT = .o</make>
+		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_unix.sh</make>
 	</dependency>
 	
+	<dependency platform="bsd">
+		<cflags>-I/usr/local/include</cflags>
+		<ldflags>-L/usr/local/lib</ldflags>
+	</dependency>
+	
 	<target name="tk">
 		<option arg="toolkit">
-			<!--
+			<value str="libadwaita">
+				<dependencies>libadwaita</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_cairo.o</make>
+			</value>
 			<value str="gtk4">
 				<dependencies>gtk4</dependencies>
 				<make>TOOLKIT = gtk</make>
 				<make>GTKOBJ = draw_cairo.o</make>
 			</value>
-			-->
 			<value str="gtk3">
 				<dependencies>gtk3</dependencies>
 				<make>TOOLKIT = gtk</make>
@@ -92,14 +115,19 @@
 				<make>TOOLKIT = qt</make>
 				<make>LD = $(CXX)</make>
 			</value>
+			<value str="cocoa">
+				<dependencies>cocoa</dependencies>
+				<make>TOOLKIT = cocoa</make>
+			</value>
 			<value str="motif">
 				<dependencies>motif</dependencies>
 				<make>TOOLKIT = motif</make>
 			</value>
-			<default value="wpf" platform="windows" />
+			<default value="winui" platform="windows" />
 			<default value="cocoa" platform="macos" />
+			<default value="gtk4" />
 			<default value="gtk3" />
-			<default value="qt5" />
+			<!--<default value="qt5" />-->
 			<default value="gtk2" />
 			<default value="qt4" />
 			<default value="motif" />
--- a/make/suncc.mk	Sun May 23 09:44:43 2021 +0200
+++ b/make/suncc.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -2,8 +2,13 @@
 # suncc toolchain
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -Kpic
 SHLIB_LDFLAGS = -G
--- a/make/toolchain.sh	Sun May 23 09:44:43 2021 +0200
+++ b/make/toolchain.sh	Sat Jan 04 16:38:48 2025 +0100
@@ -3,179 +3,198 @@
 # toolchain detection
 #
 
-C_COMPILERS="cc gcc clang suncc"
-CPP_COMPILERS="CC g++ clang++ sunCC"
-unset CC_ARG_CHECKED
-unset TOOLCHAIN_DETECTION_ERROR
+if isplatform "bsd" && notisplatform "openbsd"; then
+  C_COMPILERS="clang gcc cc"
+  CPP_COMPILERS="clang++ g++ CC"
+else
+  C_COMPILERS="gcc clang suncc cc"
+  CPP_COMPILERS="g++ clang++ sunCC CC"
+fi
+unset TOOLCHAIN
 unset TOOLCHAIN_NAME
+unset TOOLCHAIN_CC
+unset TOOLCHAIN_CXX
 
 check_c_compiler()
 {
-	cat > $TEMP_DIR/test.c << __EOF__
+  cat > "$TEMP_DIR/test.c" << __EOF__
 /* test file */
 #include <stdio.h>
 int main(int argc, char **argv) {
-#if defined(__clang__)
-	printf("clang\n");
+#if defined(_MSC_VER)
+  printf("msc\n");
+#elif defined(__clang__)
+  printf("clang gnuc\n");
 #elif defined(__GNUC__)
-	printf("gcc\n");
+  printf("gcc gnuc\n");
 #elif defined(__sun)
-	printf("suncc\n");
+  printf("suncc\n");
 #else
-	printf("unknown\n");
+  printf("unknown\n");
 #endif
-	return 0;
+  return 0;
 }
 __EOF__
-	rm -f $TEMP_DIR/checkcc
-	$1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
-	
-	if [ $? -ne 0 ]; then
-		return 1
-	fi
-	return 0
+  rm -f "$TEMP_DIR/checkcc"
+  $1 -o "$TEMP_DIR/checkcc" $CFLAGS $LDFLAGS "$TEMP_DIR/test.c" 2> /dev/null
 }
 
 check_cpp_compiler()
 {
-	cat > $TEMP_DIR/test.cpp << __EOF__
+  cat > "$TEMP_DIR/test.cpp" << __EOF__
 /* test file */
 #include <iostream>
 int main(int argc, char **argv) {
-#if defined(__clang__)
-	std::cout << "clang" << std::endl;
+#if defined(_MSC_VER)
+  std::cout << "msc" << std::endl;
+#elif defined(__clang__)
+  std::cout << "clang gnuc" << std::endl;
 #elif defined(__GNUC__)
-	std::cout << "gcc" << std::endl;
+  std::cout << "gcc gnuc" << std::endl;
 #elif defined(__sun)
-	std::cout << "suncc" << std::endl;
+  std::cout << "suncc" << std::endl;
 #else
-	std::cout << "unknown" << std::endl;
+  std::cout << "cc" << std::endl;
 #endif
-	return 0;
+  return 0;
+}
+__EOF__
+  rm -f "$TEMP_DIR/checkcc"
+  $1 -o "$TEMP_DIR/checkcc" $CXXFLAGS $LDFLAGS "$TEMP_DIR/test.cpp" 2> /dev/null
+}
+
+create_libtest_source()
+{
+  # $1: filename
+  # $2: optional include
+  cat > "$TEMP_DIR/$1" << __EOF__
+/* libtest file */
+int main(int argc, char **argv) {
+  return 0;
 }
 __EOF__
-	rm -f $TEMP_DIR/checkcc
-	$1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
-	
-	if [ $? -ne 0 ]; then
-		return 1
-	fi
-	return 0
+  if [ -n "$2" ]; then
+    echo "#include <$2>" >> "$TEMP_DIR/$1"
+  fi
+}
+
+check_c_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -z "$TOOLCHAIN_CC" ]; then
+    return 1
+  fi
+  create_libtest_source "test.c" "$2"
+  rm -f "$TEMP_DIR/checklib"
+  $TOOLCHAIN_CC -o "$TEMP_DIR/checklib" $CFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.c" 2> /dev/null
+}
+
+check_cpp_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -z "$TOOLCHAIN_CXX" ]; then
+    return 1
+  fi
+  create_libtest_source "test.cpp" "$2"
+  rm -f "$TEMP_DIR/checklib"
+  $TOOLCHAIN_CXX -o "$TEMP_DIR/checklib" $CXXFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.cpp" 2> /dev/null
+}
+
+check_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    check_c_lib "$1" "$2"
+  elif  [ -n "$TOOLCHAIN_CXX" ]; then
+    check_cpp_lib "$1" "$2"
+  fi
 }
 
-printf "detect C compiler... "
+detect_c_compiler()
+{
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    return 0
+  fi
+  printf "detect C compiler... "
+  if [ -n "$CC" ]; then
+    if check_c_compiler "$CC"; then
+      TOOLCHAIN_CC=$CC
+      TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+      TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+      echo "$CC"
+      return 0
+    else
+      echo "$CC is not a working C compiler"
+      return 1
+    fi
+  else
+    for COMP in $C_COMPILERS
+    do
+      if check_c_compiler "$COMP"; then
+        TOOLCHAIN_CC=$COMP
+        TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+        TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+        echo "$COMP"
+        return 0
+      fi
+    done
+    echo "not found"
+    return 1
+  fi
+}
 
-for COMP in $C_COMPILERS
-do
-	check_c_compiler $COMP
-	if [ $? -ne 0 ]; then
-		if [ ! -z "$CC" ]; then
-			if [ $COMP = $CC ]; then
-				echo "$CC is not a working C Compiler"
-				TOOLCHAIN_DETECTION_ERROR="error"
-				break
-			fi
-		fi
-	else
-		TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-		USE_TOOLCHAIN=$TOOLCHAIN_NAME
-		if [ $COMP = "cc" ]; then
-			# we have found a working compiler, but in case
-			# the compiler is gcc or clang, we try to use
-			# these commands and not 'cc'
-			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-			if [ $TOOLCHAIN_NAME = "gcc" ]; then
-				check_c_compiler "gcc"
-				if [ $? -eq 0 ]; then
-					COMP=gcc
-					USE_TOOLCHAIN="gcc"
-				fi
-			fi
-			if [ $TOOLCHAIN_NAME = "clang" ]; then
-				check_c_compiler "clang"
-				if [ $? -eq 0 ]; then
-					COMP=clang
-					USE_TOOLCHAIN="clang"
-				fi
-			fi
-		fi
-		
-		TOOLCHAIN_NAME=$USE_TOOLCHAIN
-		TOOLCHAIN_CC=$COMP
-		echo $COMP
-		break
-	fi
-done
-if [ -z $TOOLCHAIN_CC ]; then
-	echo "not found"
-fi
-
-printf "detect C++ compiler... "
+detect_cpp_compiler()
+{
+  if [ -n "$TOOLCHAIN_CXX" ]; then
+    return 0
+  fi
+  printf "detect C++ compiler... "
 
-for COMP in $CPP_COMPILERS
-do
-	check_cpp_compiler $COMP
-	if [ $? -ne 0 ]; then
-		if [ ! -z "$CXX" ]; then
-			if [ $COMP = $CXX ]; then
-				echo "$CC is not a working C++ Compiler"
-				TOOLCHAIN_DETECTION_ERROR="error"
-				break
-			fi
-		fi
-	else
-		if [ $COMP = "CC" ]; then
-			# we have found a working compiler, but in case
-			# the compiler is gcc or clang, we try to use
-			# these commands and not 'cc'
-			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
-			USE_TOOLCHAIN=$TOOLCHAIN_NAME
-			if [ $TOOLCHAIN_NAME = "gcc" ]; then
-				check_cpp_compiler "g++"
-				if [ $? -eq 0 ]; then
-				   COMP=g++
-				   USE_TOOLCHAIN="gcc"
-				fi
-			fi
-			if [ $TOOLCHAIN_NAME = "clang" ]; then
-				check_cpp_compiler "clang++"
-				if [ $? -eq 0 ]; then
-				   COMP=clang++
-				   USE_TOOLCHAIN="clang"
-				fi
-			fi
-		fi
-		
-		TOOLCHAIN_NAME=$USE_TOOLCHAIN
-		TOOLCHAIN_CXX=$COMP
-		echo $COMP
-		break
-	fi
-done
-if [ -z $TOOLCHAIN_CXX ]; then
-	echo "not found"
-fi
+  if [ -n "$CXX" ]; then
+    if check_cpp_compiler "$CXX"; then
+      TOOLCHAIN_CXX=$CXX
+      TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+      TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+      echo "$CXX"
+      return 0
+    else
+      echo "$CXX is not a working C++ compiler"
+      return 1
+    fi
+  else
+    for COMP in $CPP_COMPILERS
+    do
+      if check_cpp_compiler "$COMP"; then
+        TOOLCHAIN_CXX=$COMP
+        TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+        TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+        echo "$COMP"
+        return 0
+      fi
+    done
+    echo "${TOOLCHAIN_CXX:-"not found"}"
+    return 1
+  fi
+}
 
-TOOLCHAIN_LD=$TOOLCHAIN_CC
-
-if [ -z "$TOOLCHAIN_NAME" ]; then
-	TOOLCHAIN_DETECTION_ERROR="error"
-else
-	cat >> $TEMP_DIR/config.mk << __EOF__
-# toolchain
-__EOF__
-	echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
-	if [ ! -z "$TOOLCHAIN_CXX" ]; then
-		echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
-	fi
-	echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
-	echo >> $TEMP_DIR/config.mk
-	
-	cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
-	if [ $? -eq 0 ]; then 
-		echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
-	else
-		echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
-		echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
-	fi
-fi
+write_toolchain_defaults()
+{
+  echo "# toolchain" >> "$1"
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    echo "CC = ${TOOLCHAIN_CC}" >> "$1"
+  fi
+  if [ -n "$TOOLCHAIN_CXX" ]; then
+    echo "CXX = ${TOOLCHAIN_CXX}" >> "$1"
+  fi
+  echo >> "$1"
+  if [ -f "make/${TOOLCHAIN_NAME}.mk" ]; then
+    cat "make/${TOOLCHAIN_NAME}.mk" >> "$1"
+  elif [ -f "make/cc.mk" ]; then
+    cat "make/cc.mk" >> "$1"
+  else
+    echo "!!! WARNING !!! Default toolchain flags not found. Configuration might be incomplete."
+  fi
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/uwproj.xsd	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns="http://unixwork.de/uwproj"
+           targetNamespace="http://unixwork.de/uwproj"
+           elementFormDefault="qualified"
+           version="0.2"
+>
+    <xs:element name="project" type="ProjectType"/>
+
+    <xs:complexType name="ProjectType">
+        <xs:annotation>
+            <xs:documentation>
+                The root element of an uwproj project.
+                Consists of an optional <code>config</code> element
+                and an arbitrary number of <code>dependency</code>
+                and <code>target</code> elements.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="config" type="ConfigType" minOccurs="0"/>
+            <xs:element name="dependency" type="DependencyType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="target" type="TargetType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="ConfigType">
+        <xs:annotation>
+            <xs:documentation>
+                The configuration section.
+                Consists of an arbitrary number of <code>var</code> elements.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="var" type="ConfigVarType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="ConfigVarType">
+        <xs:annotation>
+            <xs:documentation>
+                The definition of a configuration variable.
+                <p>
+                    Configuration variables are supposed to be used in the configure script and are also
+                    written to the resulting config file (in contrast to make variables, which are only
+                    written to the config file).
+                    The <code>name</code> attribute is mandatory, the value is defined by the text body of the element.
+                    The optional Boolean <code>exec</code> attribute (false by default) controls, whether the entire
+                    definition is automatically executed under command substitution.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="xs:string" use="required"/>
+                <xs:attribute name="exec" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="PkgConfigType">
+        <xs:annotation>
+            <xs:documentation>
+                Instructs configure to invoke <code>pkg-config</code>, if present on the system, to determine
+                compiler and linker flags. The text body of this element defines the package name to search.
+                To constrain the allowed versions, use the attributes <code>atleast, exact, max</code>.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="atleast" type="xs:string"/>
+                <xs:attribute name="exact" type="xs:string"/>
+                <xs:attribute name="max" type="xs:string"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="LangType">
+        <xs:annotation>
+            <xs:documentation>
+                Requests a compiler for the specified language. Allowed values are
+                c, cpp.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="c"/>
+            <xs:enumeration value="cpp"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="DependencyType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a dependency.
+                <p>
+                    If the optional <code>name</code> attribute is omitted, the dependency is global
+                    and must be satisfied, otherwise configuration shall fail.
+                    A <em>named dependency</em> can be referenced by a target (or is implicitly referenced
+                    by the default target, if no targets are specified).
+                    Multiple declarations for the same named dependency may exist, in which case each declaration
+                    is checked one after another, until one block is satisfied. The result of the first satisfied
+                    dependency declaration is supposed to be applied to the config file.
+                </p>
+                <p>
+                    The optional <code>platform</code> attribute may specify a <em>single</em> platform identifier and
+                    the optional <code>toolchain</code> attribute may specify a <em>single</em> toolchain.
+                    The optional <code>not</code> attribute may specify a comma-separated list of platform and/or
+                    toolchain identifiers.
+                    The configure script shall skip this dependency declaration if the detected platform and toolchain
+                    is not matching the filter specification of these attributes.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="lang" type="LangType"/>
+            <xs:element name="cflags" type="FlagsType"/>
+            <xs:element name="cxxflags" type="FlagsType"/>
+            <xs:element name="ldflags" type="FlagsType"/>
+            <xs:element name="pkgconfig" type="PkgConfigType"/>
+            <xs:element name="test" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        Specifies a custom command that shall be executed to test whether this dependency is satisfied.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="make" type="MakeVarType"/>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string"/>
+        <xs:attribute name="platform" type="xs:string"/>
+        <xs:attribute name="toolchain" type="xs:string"/>
+        <xs:attribute name="not" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="FlagsType">
+        <xs:annotation>
+            <xs:documentation>
+                Instructs configure to append the contents of the element's body to the respective flags variable.
+                If the optional <code>exec</code> flag is set to <code>true</code>, the contents are supposed to be
+                executed under command substitution <em>at configuration time</em> before they are applied.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="exec" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="TargetType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a build target that is supposed to be configured.
+                <p>
+                    If no build target is declared explicitly, an implicit default
+                    target is generated, which has the <code>alldependencies</code>
+                    flag set.
+                </p>
+                <p>
+                    The optional <code>name</code> attribute is also used to generate a prefix
+                    for the compiler and linker flags variables.
+                    Furthermore, a target may consist of an arbitrary number of <code>feature</code>,
+                    <code>option</code>, and <code>define</code> elements.
+                    Named dependencies can be listed (separated by comma) in the <code>dependencies</code>
+                    element. If this target shall use <em>all</em> available named dependencies, the empty
+                    element <code>alldependencies</code> can be used as a shortcut.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="feature" type="FeatureType"/>
+            <xs:element name="option" type="OptionType"/>
+            <xs:element name="define" type="DefineType"/>
+            <xs:element name="dependencies" type="DependenciesType"/>
+            <xs:element name="alldependencies">
+                <xs:complexType/>
+            </xs:element>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="FeatureType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares an optional feature, that can be enabled during configuration, if all
+                <code>dependencies</code> are satisfied.
+                If a feature is enabled, all <code>define</code> and <code>make</code> definitions are
+                supposed to be applied to the config file.
+                In case the optional <code>default</code> attribute is set to true, the feature is enabled by default
+                and is supposed to be automatically disabled (without error) when the dependencies are not satisfied.
+                The name that is supposed to be used for the --enable and --disable arguments can be optionally
+                specified with the <code>arg</code> attribute. Otherwise, the <code>name</code> is used by default.
+                Optionally, a description for the help text of the resulting configure script can be specified by
+                adding a <code>desc</code> element.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:group ref="TargetDataGroup"/>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="arg" type="xs:string"/>
+        <xs:attribute name="default" type="xs:boolean" default="false"/>
+        <xs:element name="desc" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a configuration option.
+                The option argument name is specified with the <code>arg</code> attribute.
+                Then, the children of this element specify possible <code>values</code> by defining the conditions
+                (in terms of dependencies) and effects (in terms of defines and make variables) of each value.
+                Finally, a set of <code>default</code>s is specified which supposed to automagically select the most
+                appropriate value for a specific platform under the available dependencies (in case the option is not
+                explicitly specified by using the command line argument).
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="value" type="OptionValueType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="default" type="OptionDefaultType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="arg" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionValueType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a possible value for the option (in the <code>str</code> attribute) and
+                the conditions (<code>dependencies</code>) and effects, the value has.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:group ref="TargetDataGroup"/>
+        </xs:choice>
+        <xs:attribute name="str" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionDefaultType">
+        <xs:annotation>
+            <xs:documentation>
+                Specifies a default value for this option. Multiple default values can be specified, in which case
+                they are checked one after another for availability. With the optional <code>platform</code> attribute,
+                the default value can be constrained to a <em>single</em> specific platform and is supposed to be
+                skipped by configure, when this platform is not detected.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:attribute name="value" type="xs:string" use="required"/>
+        <xs:attribute name="platform" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:group name="TargetDataGroup">
+        <xs:choice>
+            <xs:element name="define" type="DefineType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="dependencies" type="DependenciesType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="make" type="MakeVarType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:choice>
+    </xs:group>
+
+    <xs:complexType name="DefineType">
+        <xs:annotation>
+            <xs:documentation>
+                Specifies C/C++ pre-processor definitions that are supposed to
+                be appended to the compiler flags, if supported.
+                (Note: for example, Fortran also supports C/C++ style pre-processor definitions under
+                certain circumstances)
+            </xs:documentation>
+        </xs:annotation>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="value" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:simpleType name="DependenciesType">
+        <xs:annotation>
+            <xs:documentation>A comma-separated list of named dependencies.</xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string"/>
+    </xs:simpleType>
+
+    <xs:simpleType name="MakeVarType">
+        <xs:annotation>
+            <xs:documentation>
+                The text contents in the body of this element are supposed to be appended literally
+                to the config file without prior processing.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string"/>
+    </xs:simpleType>
+</xs:schema>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/app.manifest	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="test.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. 
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 
+      
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+    </application>
+  </compatibility>
+  
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+</assembly>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/main.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,791 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 <Windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <ui/ui.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ui/ui.h>
+
+
+typedef struct {
+    UiString *str1;
+    UiString *str2;
+    UiString *path;
+    UiText *text;
+    UiDouble *progress;
+    UiList *list;
+    UiList *menulist;
+    UiInteger *radio;
+    UiInteger *tabview;
+    UiGeneric *image;
+} MyDocument;
+
+MyDocument *doc1;
+MyDocument *doc2;
+
+UIWIDGET tabview;
+
+static UiCondVar *cond;
+static int thr_end = 0;
+static int thr_started = 0;
+
+int threadfunc(void *data) {
+    printf("thr wait for data...\n");
+    ui_condvar_wait(cond);
+    printf("thr data received: {%s} [%d]\n", cond->data, cond->intdata);
+    ui_condvar_destroy(cond);
+    cond = NULL;
+
+    return 0;
+}
+
+void action_start_thread(UiEvent *event, void *data) {
+    if(!thr_started) {
+        cond = ui_condvar_create();
+        ui_job(event->obj, threadfunc, NULL, NULL, NULL);
+        thr_started = 1;
+    }
+}
+
+void action_notify_thread(UiEvent *event, void *data) {
+    if(!thr_end) {
+        ui_condvar_signal(cond, "hello thread", 123);
+        thr_end = 1;
+    }
+}
+
+void action_menu(UiEvent *event, void *userdata) {
+
+}
+
+void action_file_selected(UiEvent *event, void *userdata) {
+    UiFileList *files = event->eventdata;
+    MyDocument *doc = event->document;
+    printf("files: %d\n", (int)files->nfiles);
+    if(files->nfiles > 0) {
+        printf("selected file: %s\n", files->files[0]);
+        ui_image_load_file(doc->image, files->files[0]);
+    }
+}
+
+void action_button(UiEvent *event, void *userdata) {
+    ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_SINGLE, action_file_selected, NULL);
+}
+
+void action_switch(UiEvent *event, void *userdata) {
+
+}
+
+void action_toolbar_button(UiEvent *event, void *userdata) {
+    printf("toolbar button\n");
+
+    ui_dialog(event->obj, .title = "Dialog Title", .content = "Content Label", .button1_label = "btn1", .button2_label = "btn2", .input = TRUE, .closebutton_label = "Cancel");
+}
+
+void action_dialog_button(UiEvent *event, void *userdata) {
+    ui_close(event->obj);
+}
+
+void action_toolbar_dialog(UiEvent *event, void *userdata) {
+
+    UiObject *dialog = ui_dialog_window(event->obj, .title  = "Dialog Window", .lbutton1 = "Cancel 1", .lbutton2 = "Btn 2", .rbutton3 = "Btn3", .rbutton4 = "Login 4", .onclick = action_dialog_button, .default_button = 4, .show_closebutton = UI_OFF);
+
+    ui_vbox(dialog, .margin = 10, .spacing = 10) {
+        ui_label(dialog, .label = "Enter password:");
+        ui_passwordfield(dialog, .varname = "password");
+    }
+
+    ui_show(dialog);
+}
+
+void action_toolbar_newwindow(UiEvent *event, void *userdata) {
+    UiObject *obj = ui_simple_window("New Window", NULL);
+
+    ui_headerbar0(obj) {
+        ui_headerbar_start(obj) {
+            ui_button(obj, .label = "Open");
+        }
+        ui_headerbar_end(obj) {
+            ui_button(obj, .label = "Test");
+        }
+    }
+
+    ui_textarea(obj, .varname="text");
+
+    ui_show(obj);
+}
+
+MyDocument* create_doc(void) {
+    MyDocument *doc = ui_document_new(sizeof(MyDocument));
+    UiContext *docctx = ui_document_context(doc);
+    doc->str1 = ui_string_new(docctx, "str1");
+    doc->str1 = ui_string_new(docctx, "str2");
+    doc->path = ui_string_new(docctx, "path");
+    doc->progress = ui_double_new(docctx, "progress");
+    doc->list = ui_list_new(docctx, "list");
+    ui_list_append(doc->list, "test1");
+    ui_list_append(doc->list, "test2");
+    ui_list_append(doc->list, "test3");
+    doc->radio = ui_int_new(docctx, "radio");
+    doc->tabview = ui_int_new(docctx, "tabview");
+    doc->image = ui_generic_new(docctx, "image");
+    //doc->text = ui_text_new(docctx, "text");
+    return doc;
+}
+
+UiIcon *icon = NULL;
+
+static void* list_getvalue(void *elm, int col) {
+    /*
+    if(col == 0) {
+    if(!icon) {
+    icon = ui_icon("folder", 24);
+    }
+    return icon;
+    }
+    */
+
+    char *str = elm;
+    return col == 0 ? str : "x";
+}
+
+static UiList *menu_list;
+int new_item_count = 0;
+
+void action_add_menu_item(UiEvent *event, void *userdata) {
+    char str[64];
+    snprintf(str, 64, "new item %d", new_item_count++);
+
+    ui_list_append(menu_list, strdup(str));
+    ui_list_notify(menu_list);
+}
+
+void action_menu_list(UiEvent *event, void *userdata) {
+    printf("menu list item: %d\n", event->intval);
+}
+
+static int tab_x = 0;
+void action_tab2_button(UiEvent *event, void *userdata) {
+    MyDocument *doc = event->document;
+    printf("current page: %d\n", (int)ui_get(doc->tabview));
+    ui_set(doc->tabview, 0);
+}
+
+
+void action_group1(UiEvent *event, void *userdata) {
+    UiContext *ctx = event->obj->ctx;
+    if(userdata) {
+        ui_unset_group(ctx, 1);
+    } else {
+        ui_set_group(ctx, 1);
+    }
+}
+
+void action_group2(UiEvent *event, void *userdata) {
+    UiContext *ctx = event->obj->ctx;
+    if(userdata) {
+        ui_unset_group(ctx, 2);
+    } else {
+        ui_set_group(ctx, 2);
+    }
+}
+
+void application_startup(UiEvent *event, void *data) {
+    // global list
+    UiContext *global = ui_global_context();
+    menu_list = ui_list_new(global, "menulist");
+    ui_list_append(menu_list, "menu list item 1");
+    ui_list_append(menu_list, "menu list item 2");
+    ui_list_append(menu_list, "menu list item 3");
+
+
+
+    UiObject *obj = ui_window("Test", NULL);
+
+    MyDocument *doc = create_doc();
+    ui_attach_document(obj->ctx, doc);
+
+    ui_tabview(obj, .spacing=10, .margin=10, .tabview = UI_TABVIEW_NAVIGATION_SIDE, .varname="tabview") {
+        ui_tab(obj, "Tab 1") {
+            ui_vbox(obj, .fill = UI_OFF, .margin = 15, .spacing = 15) {
+                ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button);
+                ui_togglebutton(obj, .label = "Toggle");
+                ui_checkbox(obj, .label = "My Checkbox");
+            }
+            ui_grid(obj, .fill = UI_OFF, .columnspacing = 15, .rowspacing = 15, .margin = 15) {
+                ui_button(obj, .label = "Activate Group 1", .hexpand = TRUE, .onclick = action_group1);
+                ui_button(obj, .label = "Disable Group 1", .onclick = action_group1, .onclickdata = "disable");
+                ui_newline(obj);
+                ui_button(obj, .label = "Activate Group 2", .hexpand = TRUE, .onclick = action_group2);
+                ui_button(obj, .label = "Disable Group 2", .onclick = action_group2, .onclickdata = "disable");
+                ui_newline(obj);
+
+                ui_button(obj, .label = "Groups 1,2", .colspan = 2, .groups = UI_GROUPS(1, 2));
+                ui_newline(obj);
+
+                ui_label(obj, .label = "Label Col 1", .align = UI_ALIGN_LEFT);
+                ui_label(obj, .label = "Label Col 2", .style = UI_LABEL_STYLE_TITLE, .align = UI_ALIGN_RIGHT);
+                ui_newline(obj);
+
+                //ui_spinner(obj, .step = 5);
+                //ui_newline(obj);
+
+                ui_progressbar(obj, .colspan = 2, .varname = "progress");
+                ui_set(doc->progress, 0.75);
+                ui_newline(obj);
+
+                ui_textfield(obj, .value = doc->str1);
+                ui_newline(obj);
+
+                //ui_button(obj, .label="Test");
+                ui_path_textfield(obj, .varname = "path");
+                ui_set(doc->path, "/test/path/longdirectoryname/123");
+                ui_newline(obj);
+
+                //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1);
+                //model->getvalue = list_getvalue;
+                ui_combobox(obj, .hexpand = true, .vexpand = false, .colspan = 2, .varname = "list", .getvalue = list_getvalue);
+                ui_newline(obj);
+
+                ui_hbox0(obj) {
+                    ui_radiobutton(obj, .label = "Radio 1", .varname = "radio");
+                    ui_radiobutton(obj, .label = "Radio 2", .varname = "radio");
+                    ui_radiobutton(obj, .label = "Radio 3", .varname = "radio");
+                }
+            }
+        }
+        ui_tab(obj, "Tab 2") {
+            ui_button(obj, .label = "Button 1 Start Thread", .onclick=action_start_thread);
+            ui_button(obj, .label = "Button 2 Notify Thread", .onclick=action_notify_thread);
+            ui_button(obj, .label = "Button 3", .onclick=action_tab2_button);
+            ui_button(obj, .label = "Button 4", .onclick=action_tab2_button);
+            ui_button(obj, .label = "Button 5", .onclick=action_tab2_button);
+            ui_button(obj, .label = "Button 6", .onclick=action_tab2_button);
+        }
+        ui_tab(obj, "Tab 3") {
+            UiTabViewArgs args = {0};
+            UI_CTN(obj, tabview=ui_tabview_create(obj, args)) {
+                UiObject *tab1 = ui_tabview_add(tabview, "Sub 1", -1);
+                ui_button(tab1, .label = "Button 1");
+
+
+                UiObject *tab2 = ui_tabview_add(tabview, "Sub 2", -1);
+                ui_button(tab2, .label = "Button 2");
+            }
+        }
+        ui_tab(obj, "Tab 4") {
+            ui_grid0(obj) {
+                ui_button(obj, .label = "test1");
+                ui_newline(obj);
+                ui_textarea(obj, .varname = "text", .vexpand = TRUE, .hexpand = TRUE);
+            }
+        }
+        ui_tab(obj, "Tab 5") {
+            ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button);
+            ui_imageviewer(obj, .varname = "image", .style_class = "imageviewer");
+        }
+
+        ui_tab(obj, "Tab 6") {
+            ui_scrolledwindow(obj, .fill = UI_ON) {
+                ui_expander(obj, .label = "Expander", .margin = 10, .spacing = 10) {
+                    ui_label(obj, .label = "Test");
+                    ui_button(obj, .label = "Button");
+                }
+
+                ui_frame(obj, .label = "Frame", .margin = 10, .spacing = 10) {
+                    ui_label(obj, .label = "Title", .style = UI_LABEL_STYLE_TITLE);
+                    ui_label(obj, .label = "Sub-Title", .style = UI_LABEL_STYLE_SUBTITLE);
+                    ui_label(obj, .label = "Dim Label", .style = UI_LABEL_STYLE_DIM);
+                    ui_label(obj, .label = "No Style");
+                }
+
+                for(int i=0;i<100;i++) {
+                    char labelstr[32];
+                    snprintf(labelstr, 32, "button %d", i);
+                    ui_button(obj, .label = labelstr);
+                }
+            }
+        }
+    }
+
+    /*
+
+    */
+
+    ui_show(obj);
+}
+
+/*
+typedef struct WindowData {
+    UiInteger* check;
+    UiInteger* toggle;
+    UiInteger* radio;
+    UiString* text;
+    UiString* password;
+    UiList* list;
+    UiString* t1;
+    UiString* t2;
+    UiString* t3;
+    UiString* path;
+    UiList* list2;
+    UiList* list3;
+    UiDouble* progress;
+    UiInteger* spinner;
+} WindowData;
+
+static UiIcon* folder_icon;
+
+UiList* menuList;
+
+void event_mt(UiEvent* event, void* data) {
+    char* mt_str = data;
+
+    printf("%s\n", mt_str);
+}
+
+int test_threadfunc(void *data) {
+    char* str = data;
+    
+    return 0;
+}
+
+void action_thread_test(UiEvent* event, void* data) {
+    ui_job(event->obj, test_threadfunc, "testdata", event_mt, "testdata2");
+}
+
+void action1(UiEvent* event, void* data) {
+    char* action = data;
+    
+    WindowData* wdata = event->window;
+    int64_t is_checked = ui_get(wdata->check);
+    int64_t radio = ui_get(wdata->radio);
+
+    printf("data: %s %d\n", data, is_checked);
+
+    double d = ui_get(wdata->progress);
+    ui_set(wdata->progress, d + 1);
+
+    int spinner_active = ui_get(wdata->spinner);
+    ui_set(wdata->spinner, !spinner_active);
+
+    ui_list_append(menuList, "List Item X");
+    ui_list_append(menuList, "List Item X");
+    ui_notify(menuList->observers, NULL);
+}
+
+void action_set_checkbox(UiEvent* event, void* data) {
+    char* action = data;
+
+    WindowData* wdata = event->window;
+    wdata->check->set(wdata->check, 1);
+}
+
+void action_onchange(UiEvent* event, void* data) {
+    printf("onchange: %d\n", event->intval);
+}
+
+void action_switch(UiEvent* event, void* data) {
+    printf("onchange: %d\n", event->intval);
+}
+
+void action_toolbar_button(UiEvent* event, void *data) {
+    printf("toolbar action\n");
+}
+
+
+void action_listselection_changed(UiEvent* event, void* data) {
+    printf("selection changed\n");
+    UiListSelection* sel = event->eventdata;
+    for (int i = 0; i < sel->count; i++) {
+        int row = sel->rows[i];
+        printf("row: %d\n", row);
+    }
+}
+
+void action_onactivate(UiEvent* event, void* Data) {
+    printf("activate\n");
+    UiListSelection* sel = event->eventdata;
+    for (int i = 0; i < sel->count; i++) {
+        int row = sel->rows[i];
+        printf("row: %d\n", row);
+    }
+}
+
+typedef struct TableData {
+    char* col1;
+    char* col2;
+    char* col3;
+} TableData;
+
+void* table_getvalue(void* data, int i) {
+    TableData* t = data;
+    switch (i) {
+    case 0: return folder_icon;
+    case 1: return t->col1;
+    case 2: return t->col2;
+    case 3: return t->col3;
+    }
+    return NULL;
+}
+
+void action_add(UiEvent* event, void* data) {
+    WindowData* wdata = event->window;
+    char* t1 = wdata->t1->get(wdata->t1);
+    char* t2 = wdata->t2->get(wdata->t2);
+    char* t3 = wdata->t3->get(wdata->t3);
+
+    TableData* tdat = malloc(sizeof(TableData));
+    tdat->col1 = _strdup(t1);
+    tdat->col2 = _strdup(t2);
+    tdat->col3 = _strdup(t3);
+    ui_list_append(wdata->list2, tdat);
+    wdata->list2->update(wdata->list2, 0);
+
+}
+
+void action_breadcrumb(UiEvent* event, void* data) {
+    int i = event->intval;
+    char* c = event->eventdata;
+    printf("index: %d\n", i);
+}
+
+void dragstart(UiEvent* event, void* data) {
+    UiListDnd* ldnd = event->eventdata;
+    ui_selection_settext(ldnd->dnd, "Hello World!", -1);
+}
+
+void dragcomplete(UiEvent* event, void* data) {
+
+}
+
+void dragover(UiEvent* event, void* data) {
+
+}
+
+void drop(UiEvent* event, void* data) {
+
+}
+
+void dialog_result(UiEvent *evt, void *data) {
+    char *str = evt->eventdata;
+    printf("dialog: %d\n", (int)evt->intval);
+}
+
+void btn_dialog(UiEvent *evt, void *data) {
+    ui_dialog(evt->obj, .title = "Title", .input = TRUE, .content = "Hello World", .button1_label = "Yes", .button2_label = "No", .closebutton_label = "Close", .result = dialog_result);
+}
+
+
+
+
+void application_startup(UiEvent* event, void* data) {
+    UiContext* gctx = ui_global_context();
+    menuList = ui_list_new(gctx, "menulist");
+    ui_list_append(menuList, "List Item 1");
+    ui_list_append(menuList, "List Item 2");
+    ui_list_append(menuList, "List Item 3");
+    ui_list_append(menuList, "List Item 4");
+    ui_list_append(menuList, "List Item 5");
+    ui_list_append(menuList, "List Item 6");
+
+    UiObject* obj = ui_window("Test", NULL);
+    WindowData* wdata = ui_malloc(obj->ctx, sizeof(WindowData));
+    obj->window = wdata;
+    wdata->check = ui_int_new(obj->ctx, "check");
+    wdata->toggle = ui_int_new(obj->ctx, "toggle");
+    wdata->radio = ui_int_new(obj->ctx, "radio");
+    wdata->text = ui_string_new(obj->ctx, "text");
+    wdata->password = ui_string_new(obj->ctx, "password");
+    wdata->list = ui_list_new(obj->ctx, "list");
+    wdata->list2 = ui_list_new(obj->ctx, "list2");
+    wdata->list3 = ui_list_new(obj->ctx, "list3");
+    wdata->t1 = ui_string_new(obj->ctx, "t1");
+    wdata->t2 = ui_string_new(obj->ctx, "t2");
+    wdata->t3 = ui_string_new(obj->ctx, "t3");
+    wdata->path = ui_string_new(obj->ctx, "path");
+    wdata->progress = ui_double_new(obj->ctx, "progress");
+    wdata->spinner = ui_int_new(obj->ctx, "spinner");
+
+    ui_list_append(wdata->list, "Hello");
+    ui_list_append(wdata->list, "World");
+    ui_list_append(wdata->list, "Item3");
+    ui_list_append(wdata->list, "Item4");
+    ui_list_append(wdata->list, "Item5");
+    ui_list_append(wdata->list, "Item6");
+
+    ui_list_append(wdata->list3, "usr");
+    ui_list_append(wdata->list3, "share");
+    ui_list_append(wdata->list3, "test");
+    ui_list_append(wdata->list3, "dir");
+
+    //folder_icon = ui_icon("Folder", 32);
+    folder_icon = ui_foldericon(16);
+
+    TableData* td1 = malloc(sizeof(TableData));
+    TableData* td2 = malloc(sizeof(TableData));
+    TableData* td3 = malloc(sizeof(TableData));
+    TableData* td4 = malloc(sizeof(TableData));
+    TableData* td5 = malloc(sizeof(TableData));
+    TableData* td6 = malloc(sizeof(TableData));
+    td1->col1 = "a1";
+    td1->col2 = "b1";
+    td1->col3 = "c1";
+    td2->col1 = "a2";
+    td2->col2 = "b2";
+    td2->col3 = "b3";
+    td3->col1 = "a3";
+    td3->col2 = "b3";
+    td3->col3 = "c3";
+    td4->col1 = "a3";
+    td4->col2 = "b3";
+    td4->col3 = "c3";
+    td5->col1 = "a3";
+    td5->col2 = "b3";
+    td5->col3 = "c3";
+    td6->col1 = "a3";
+    td6->col2 = "b3";
+    td6->col3 = "c3";
+
+    ui_list_append(wdata->list2, td1);
+    ui_list_append(wdata->list2, td2);
+    ui_list_append(wdata->list2, td3);
+    ui_list_append(wdata->list2, td4);
+    ui_list_append(wdata->list2, td5);
+    ui_list_append(wdata->list2, td6);
+
+    ui_scrolledwindow0(obj) {
+        ui_grid(obj, .margin = 10, .columnspacing = 5, .rowspacing = 20) {
+            ui_button(obj, .label = "Thread Test", .onclick = action_thread_test, .onclickdata = "action1");
+            ui_button(obj, .label = "Button2", .icon = "Back", .onclick = action1, .onclickdata = "action2");
+            ui_button(obj, .icon = "Forward", .onclick = action1, .onclickdata = "action3", .hexpand = true);
+            ui_newline(obj);
+
+            ui_button(obj, .label = "Dialog Test", .onclick = btn_dialog, .onclickdata = "action4");
+            ui_button(obj, .label = "Button5", .onclick = action1, .onclickdata = "action5", .colspan = 2);
+            ui_newline(obj);
+
+            ui_button(obj, .label = "Very Long Button Label Text ____________ Test", .onclick = action_set_checkbox);
+            ui_newline(obj);
+
+            ui_checkbox(obj, .label = "Option 1", .value = wdata->check, .onchange = action_onchange);
+            ui_togglebutton(obj, .label = "Option 2", .value = wdata->toggle);
+            ui_newline(obj);
+
+            ui_label(obj, .label = "Progress");
+            ui_progressspinner(obj, .value = wdata->spinner);
+            ui_newline(obj);
+            
+            ui_hbox(obj, .colspan = 3) {
+                ui_radiobutton(obj, .label = "Radio 1", .value = wdata->radio);
+                ui_radiobutton(obj, .label = "Radio 2", .value = wdata->radio);
+                ui_radiobutton(obj, .label = "Radio 3", .value = wdata->radio);
+            }
+            ui_newline(obj);
+            ui_radiobutton(obj, .label = "Radio 4", .value = wdata->radio);
+            ui_switch(obj, .label = "test", .onchange = action_switch);
+            ui_newline(obj);
+
+            //ui_breadcrumbbar(obj, .list = wdata->list3, .onactivate=action_breadcrumb);
+            ui_textfield(obj, .varname = "newtext");
+            ui_path_textfield(obj, .colspan = 2, .value=wdata->path, .onactivate = action_breadcrumb);
+            ui_newline(obj);
+            wdata->path->set(wdata->path, "/usr/path/test");
+            
+            ui_textfield(obj, .value = wdata->text);
+            ui_passwordfield(obj, .value = wdata->password);
+            ui_newline(obj);
+
+            ui_frame(obj, .label = "Test", .colspan = 3) {
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+            }
+            ui_newline(obj);
+            
+            ui_expander(obj, .label = "Expand", .colspan = 3, .margin = 10, .spacing = 5, .isexpanded = false) {
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+            }
+            ui_newline(obj);
+
+            ui_combobox(obj, .list = wdata->list, .onselection= action_listselection_changed, .onactivate= action_onactivate);
+            ui_newline(obj);
+
+            ui_tabview(obj, .colspan = 3, .vexpand = true, .hexpand = true, .tabview = UI_TABVIEW_NAVIGATION_SIDE) {
+                ui_tab(obj, "Tab 1") {
+                    ui_button(obj, .label = "Tab 1 Button");
+                }
+                ui_tab(obj, "Tab 2") {
+                    ui_button(obj, .label = "Tab 2 Button");
+                }
+                ui_tab(obj, "Tab 3") {
+
+                }
+            }
+            ui_newline(obj);
+
+            ui_label(obj, .label = "Test Label");
+            ui_progressbar(obj, .value = wdata->progress, .colspan = 2);
+            ui_newline(obj);
+
+            ui_newline(obj);
+            ui_textfield(obj, .value = wdata->t1);
+            ui_textfield(obj, .value = wdata->t2);
+            ui_textfield(obj, .value = wdata->t3);
+            ui_newline(obj);
+            ui_button(obj, .label = "Add", .onclick = action_add);
+            ui_newline(obj);
+
+
+            ui_newline(obj);
+
+            UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", UI_STRING, "Col 3", -1);
+            model->getvalue = table_getvalue;
+            ui_table(obj,   .colspan = 3, .model = model, .list = wdata->list2, .onactivate = action_onactivate,
+                            .onselection = action_listselection_changed,
+                            .ondragstart = dragstart, .ondragcomplete = dragcomplete, .ondrop = drop);
+            ui_model_free(obj->ctx, model);
+        }
+    }   
+
+    ui_show(obj);
+}
+
+*/
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
+{ 
+    ui_init("app1", NULL, 0);
+    ui_onstartup(application_startup, NULL);
+
+    // menu
+    ui_menu("File") {
+        ui_menuitem(.label = "Test");
+    }
+
+    ui_toolbar_item("Test", .label = "Test", .onclick = action_toolbar_button);
+    ui_toolbar_item("Test2", .label = "New Window", .onclick = action_toolbar_newwindow);
+    ui_toolbar_item("Test3", .label = "Dialog", .onclick = action_toolbar_dialog);
+    ui_toolbar_item("Test4", .label = "Test 4", .onclick = action_toolbar_button);
+    ui_toolbar_item("Test5", .label = "Test 5", .onclick = action_toolbar_button);
+    ui_toolbar_item("Test6", .label = "Test 6", .onclick = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);
+    ui_toolbar_menu("Menu", .label = "Menu") {
+        ui_menuitem("Secondary Test", .onclick = action_toolbar_button, NULL);
+        ui_menu("Secondary Sub") {
+            ui_menuitem("Secondary subitem", NULL, NULL);
+        }
+        ui_menuseparator();
+        ui_menu_itemlist(.varname = "menulist", .onselect=action_menu_list);
+        ui_menuseparator();
+        ui_menuitem("last", .onclick = action_add_menu_item);
+    }
+
+    ui_toolbar_appmenu() {
+        ui_menuitem("New");
+        ui_menuitem("Open");
+        ui_menuitem("Save");
+
+        ui_menuseparator();
+
+        ui_menuitem("Close");
+    }
+
+    ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Test6", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Menu", UI_TOOLBAR_LEFT);
+
+    ui_toolbar_add_default("Test2", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Test3", UI_TOOLBAR_CENTER);
+
+    ui_toolbar_add_default("Test4", UI_TOOLBAR_RIGHT);
+    ui_toolbar_add_default("Test5", UI_TOOLBAR_RIGHT);
+
+    ui_main();
+
+    return (EXIT_SUCCESS);
+    
+    /*
+    ui_init("app1", 0, NULL);
+    ui_onstartup(application_startup, NULL);
+    
+    ui_menu("File") {
+        ui_menuitem(.label = "Item 1");
+        ui_menuitem(.label = "Item 2");
+        ui_menuseparator();
+        ui_menu("File Sub") {
+            ui_menuitem(.label = "Sub Item");
+        }
+
+        ui_menuitem(.label = "Exit");
+    }
+
+    ui_toolbar_item("Test", .label = "Home", .icon = "Home", .onclick = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle2", .label = "Toggle2", .onchange = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle3", .label = "Toggle3", .onchange = action_toolbar_button);
+
+    ui_toolbar_menu("Menu", .label = "Menu") {
+        
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menu_itemlist(.varname = "menulist");
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menu("TB Sub") {
+            ui_menuitem("TB subitem", NULL, NULL);
+        }
+    }
+
+    ui_toolbar_menu(NULL, .label = "Menu") {
+        ui_menuitem("Secondary Test", NULL, NULL);
+        ui_menu("Secondary Sub") {
+            ui_menuitem("Secondary subitem", NULL, NULL);
+        }
+    }
+
+    ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle2", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Toggle3", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Menu", UI_TOOLBAR_RIGHT);
+
+    ui_main();
+
+    return (EXIT_SUCCESS);
+    */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/packages.config	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240122.1" targetFramework="native" />
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.3233" targetFramework="native" />
+  <package id="Microsoft.WindowsAppSDK" version="1.5.241001000" targetFramework="native" />
+</packages>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/testapp.vcxproj	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" />
+  <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{3541f08b-e6cc-4c23-a0d3-51983aab33c6}</ProjectGuid>
+    <RootNamespace>testapp</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
+    <WindowsPackageType>None</WindowsPackageType>
+    <AppxPackage>false</AppxPackage>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>false</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">
+      <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240122.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240122.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
+    <Import Project="..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.3233\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240122.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240122.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/testapp.vcxproj.filters	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/toolkit.sln	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,71 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testapp", "testapp\testapp.vcxproj", "{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|ARM64 = Debug|ARM64
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|ARM64 = Release|ARM64
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|ARM64.ActiveCfg = Debug|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|ARM64.Build.0 = Debug|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x64.ActiveCfg = Debug|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x64.Build.0 = Debug|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x86.ActiveCfg = Debug|Win32
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x86.Build.0 = Debug|Win32
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|ARM64.ActiveCfg = Release|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|ARM64.Build.0 = Release|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x64.ActiveCfg = Release|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x64.Build.0 = Release|x64
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x86.ActiveCfg = Release|Win32
+		{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x86.Build.0 = Release|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.ActiveCfg = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.Build.0 = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.ActiveCfg = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.Build.0 = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.ActiveCfg = Debug|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.Build.0 = Debug|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.ActiveCfg = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.Build.0 = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.ActiveCfg = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.ActiveCfg = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Build.0 = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Deploy.0 = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.ActiveCfg = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Build.0 = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Deploy.0 = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.ActiveCfg = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Build.0 = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Deploy.0 = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.ActiveCfg = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Build.0 = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Deploy.0 = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.ActiveCfg = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Build.0 = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Deploy.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {141CA624-F556-4BE7-9218-8D6EEAB95C95}
+	EndGlobalSection
+EndGlobal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/ucx/ucx.vcxproj	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{27da0164-3475-43e2-a1a4-a5d07d305749}</ProjectGuid>
+    <RootNamespace>ucx</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>false</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ucx\allocator.c" />
+    <ClCompile Include="..\..\..\ucx\array_list.c" />
+    <ClCompile Include="..\..\..\ucx\buffer.c" />
+    <ClCompile Include="..\..\..\ucx\compare.c" />
+    <ClCompile Include="..\..\..\ucx\hash_key.c" />
+    <ClCompile Include="..\..\..\ucx\hash_map.c" />
+    <ClCompile Include="..\..\..\ucx\iterator.c" />
+    <ClCompile Include="..\..\..\ucx\linked_list.c" />
+    <ClCompile Include="..\..\..\ucx\list.c" />
+    <ClCompile Include="..\..\..\ucx\map.c" />
+    <ClCompile Include="..\..\..\ucx\mempool.c" />
+    <ClCompile Include="..\..\..\ucx\printf.c" />
+    <ClCompile Include="..\..\..\ucx\string.c" />
+    <ClCompile Include="..\..\..\ucx\szmul.c" />
+    <ClCompile Include="..\..\..\ucx\tree.c" />
+    <ClCompile Include="..\..\..\ucx\utils.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h" />
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h" />
+    <ClInclude Include="..\..\..\ucx\cx\collection.h" />
+    <ClInclude Include="..\..\..\ucx\cx\common.h" />
+    <ClInclude Include="..\..\..\ucx\cx\compare.h" />
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h" />
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h" />
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h" />
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\map.h" />
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h" />
+    <ClInclude Include="..\..\..\ucx\cx\printf.h" />
+    <ClInclude Include="..\..\..\ucx\cx\string.h" />
+    <ClInclude Include="..\..\..\ucx\cx\test.h" />
+    <ClInclude Include="..\..\..\ucx\cx\tree.h" />
+    <ClInclude Include="..\..\..\ucx\cx\utils.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/ucx/ucx.vcxproj.filters	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ucx\allocator.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\array_list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\buffer.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\compare.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\hash_key.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\hash_map.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\linked_list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\map.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\printf.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\string.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\utils.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\mempool.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\iterator.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\szmul.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\tree.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\collection.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\common.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\compare.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\map.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\printf.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\string.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\utils.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\test.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\tree.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/uicommon/uicommon.vcxproj	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>17.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{8b88698e-c185-4383-99fe-0c34d6deed2e}</ProjectGuid>
+    <RootNamespace>uicommon</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>false</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ui\common\context.c" />
+    <ClCompile Include="..\..\..\ui\common\document.c" />
+    <ClCompile Include="..\..\..\ui\common\menu.c" />
+    <ClCompile Include="..\..\..\ui\common\object.c" />
+    <ClCompile Include="..\..\..\ui\common\properties.c" />
+    <ClCompile Include="..\..\..\ui\common\toolbar.c" />
+    <ClCompile Include="..\..\..\ui\common\types.c" />
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ui\common\context.h" />
+    <ClInclude Include="..\..\..\ui\common\document.h" />
+    <ClInclude Include="..\..\..\ui\common\menu.h" />
+    <ClInclude Include="..\..\..\ui\common\object.h" />
+    <ClInclude Include="..\..\..\ui\common\properties.h" />
+    <ClInclude Include="..\..\..\ui\common\toolbar.h" />
+    <ClInclude Include="..\..\..\ui\common\types.h" />
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/uicommon/uicommon.vcxproj.filters	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien\Source">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ui\common\context.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\document.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\menu.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\object.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\properties.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\toolbar.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\types.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ui\common\context.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\document.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\menu.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\object.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\properties.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\toolbar.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\types.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/uicommon/uicommon.vcxproj.user	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,630 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 77;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		ED6580EE2CFF19F900F5402F /* context.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DD2CFF19F900F5402F /* context.c */; };
+		ED6580EF2CFF19F900F5402F /* menu.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E12CFF19F900F5402F /* menu.c */; };
+		ED6580F02CFF19F900F5402F /* threadpool.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E72CFF19F900F5402F /* threadpool.c */; };
+		ED6580F12CFF19F900F5402F /* condvar.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DB2CFF19F900F5402F /* condvar.c */; };
+		ED6580F22CFF19F900F5402F /* document.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DF2CFF19F900F5402F /* document.c */; };
+		ED6580F32CFF19F900F5402F /* object.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E32CFF19F900F5402F /* object.c */; };
+		ED6580F42CFF19F900F5402F /* toolbar.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E92CFF19F900F5402F /* toolbar.c */; };
+		ED6580F52CFF19F900F5402F /* types.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580EB2CFF19F900F5402F /* types.c */; };
+		ED6580F62CFF19F900F5402F /* properties.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E52CFF19F900F5402F /* properties.c */; };
+		ED6580F72CFF19F900F5402F /* ucx_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580ED2CFF19F900F5402F /* ucx_properties.c */; };
+		ED65811D2CFF1A3000F5402F /* linked_list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581142CFF1A3000F5402F /* linked_list.c */; };
+		ED65811E2CFF1A3000F5402F /* tree.c in Sources */ = {isa = PBXBuildFile; fileRef = ED65811B2CFF1A3000F5402F /* tree.c */; };
+		ED6581202CFF1A3000F5402F /* mempool.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581172CFF1A3000F5402F /* mempool.c */; };
+		ED6581212CFF1A3000F5402F /* map.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581162CFF1A3000F5402F /* map.c */; };
+		ED6581222CFF1A3000F5402F /* hash_map.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581122CFF1A3000F5402F /* hash_map.c */; };
+		ED6581232CFF1A3000F5402F /* array_list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FA2CFF1A3000F5402F /* array_list.c */; };
+		ED6581242CFF1A3000F5402F /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581152CFF1A3000F5402F /* list.c */; };
+		ED6581252CFF1A3000F5402F /* compare.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FC2CFF1A3000F5402F /* compare.c */; };
+		ED6581262CFF1A3000F5402F /* hash_key.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581112CFF1A3000F5402F /* hash_key.c */; };
+		ED6581272CFF1A3000F5402F /* iterator.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581132CFF1A3000F5402F /* iterator.c */; };
+		ED6581282CFF1A3000F5402F /* string.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581192CFF1A3000F5402F /* string.c */; };
+		ED6581292CFF1A3000F5402F /* allocator.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580F92CFF1A3000F5402F /* allocator.c */; };
+		ED65812A2CFF1A3000F5402F /* utils.c in Sources */ = {isa = PBXBuildFile; fileRef = ED65811C2CFF1A3000F5402F /* utils.c */; };
+		ED65812B2CFF1A3000F5402F /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FB2CFF1A3000F5402F /* buffer.c */; };
+		ED65812C2CFF1A3000F5402F /* printf.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581182CFF1A3000F5402F /* printf.c */; };
+		ED65812D2CFF1A3000F5402F /* common.h.orig in Resources */ = {isa = PBXBuildFile; fileRef = ED6581022CFF1A3000F5402F /* common.h.orig */; };
+		ED6581312CFF1A8800F5402F /* toolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581302CFF1A8800F5402F /* toolkit.m */; };
+		ED6581342CFF1F1900F5402F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581332CFF1F1900F5402F /* AppDelegate.m */; };
+		ED6581392CFF287300F5402F /* EventData.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581362CFF287300F5402F /* EventData.m */; };
+		ED65813A2CFF287300F5402F /* UiJob.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581382CFF287300F5402F /* UiJob.m */; };
+		ED6581432CFF3BCE00F5402F /* window.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581422CFF3BCE00F5402F /* window.m */; };
+		ED6581442CFF3BCE00F5402F /* button.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65813C2CFF3BCE00F5402F /* button.m */; };
+		ED6581452CFF3BCE00F5402F /* Container.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65813E2CFF3BCE00F5402F /* Container.m */; };
+		ED6581462CFF3BCE00F5402F /* GridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581402CFF3BCE00F5402F /* GridLayout.m */; };
+		ED65815C2CFF3EE900F5402F /* MainWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65815B2CFF3EE900F5402F /* MainWindow.m */; };
+		ED65815F2CFF4BF200F5402F /* WindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65815E2CFF4BF200F5402F /* WindowManager.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		ED6580AC2CFF122700F5402F /* toolkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = toolkit.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		ED6580DA2CFF19F900F5402F /* condvar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = condvar.h; path = /Users/olaf/Projekte/toolkit/ui/common/condvar.h; sourceTree = "<absolute>"; };
+		ED6580DB2CFF19F900F5402F /* condvar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = condvar.c; path = /Users/olaf/Projekte/toolkit/ui/common/condvar.c; sourceTree = "<absolute>"; };
+		ED6580DC2CFF19F900F5402F /* context.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = context.h; path = /Users/olaf/Projekte/toolkit/ui/common/context.h; sourceTree = "<absolute>"; };
+		ED6580DD2CFF19F900F5402F /* context.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = context.c; path = /Users/olaf/Projekte/toolkit/ui/common/context.c; sourceTree = "<absolute>"; };
+		ED6580DE2CFF19F900F5402F /* document.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = document.h; path = /Users/olaf/Projekte/toolkit/ui/common/document.h; sourceTree = "<absolute>"; };
+		ED6580DF2CFF19F900F5402F /* document.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = document.c; path = /Users/olaf/Projekte/toolkit/ui/common/document.c; sourceTree = "<absolute>"; };
+		ED6580E02CFF19F900F5402F /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = /Users/olaf/Projekte/toolkit/ui/common/menu.h; sourceTree = "<absolute>"; };
+		ED6580E12CFF19F900F5402F /* menu.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = menu.c; path = /Users/olaf/Projekte/toolkit/ui/common/menu.c; sourceTree = "<absolute>"; };
+		ED6580E22CFF19F900F5402F /* object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = object.h; path = /Users/olaf/Projekte/toolkit/ui/common/object.h; sourceTree = "<absolute>"; };
+		ED6580E32CFF19F900F5402F /* object.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = object.c; path = /Users/olaf/Projekte/toolkit/ui/common/object.c; sourceTree = "<absolute>"; };
+		ED6580E42CFF19F900F5402F /* properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = properties.h; path = /Users/olaf/Projekte/toolkit/ui/common/properties.h; sourceTree = "<absolute>"; };
+		ED6580E52CFF19F900F5402F /* properties.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = properties.c; path = /Users/olaf/Projekte/toolkit/ui/common/properties.c; sourceTree = "<absolute>"; };
+		ED6580E62CFF19F900F5402F /* threadpool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = threadpool.h; path = /Users/olaf/Projekte/toolkit/ui/common/threadpool.h; sourceTree = "<absolute>"; };
+		ED6580E72CFF19F900F5402F /* threadpool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = threadpool.c; path = /Users/olaf/Projekte/toolkit/ui/common/threadpool.c; sourceTree = "<absolute>"; };
+		ED6580E82CFF19F900F5402F /* toolbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolbar.h; path = /Users/olaf/Projekte/toolkit/ui/common/toolbar.h; sourceTree = "<absolute>"; };
+		ED6580E92CFF19F900F5402F /* toolbar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = toolbar.c; path = /Users/olaf/Projekte/toolkit/ui/common/toolbar.c; sourceTree = "<absolute>"; };
+		ED6580EA2CFF19F900F5402F /* types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = types.h; path = /Users/olaf/Projekte/toolkit/ui/common/types.h; sourceTree = "<absolute>"; };
+		ED6580EB2CFF19F900F5402F /* types.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = types.c; path = /Users/olaf/Projekte/toolkit/ui/common/types.c; sourceTree = "<absolute>"; };
+		ED6580EC2CFF19F900F5402F /* ucx_properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ucx_properties.h; path = /Users/olaf/Projekte/toolkit/ui/common/ucx_properties.h; sourceTree = "<absolute>"; };
+		ED6580ED2CFF19F900F5402F /* ucx_properties.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ucx_properties.c; path = /Users/olaf/Projekte/toolkit/ui/common/ucx_properties.c; sourceTree = "<absolute>"; };
+		ED6580F92CFF1A3000F5402F /* allocator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = allocator.c; path = /Users/olaf/Projekte/toolkit/ucx/allocator.c; sourceTree = "<absolute>"; };
+		ED6580FA2CFF1A3000F5402F /* array_list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = array_list.c; path = /Users/olaf/Projekte/toolkit/ucx/array_list.c; sourceTree = "<absolute>"; };
+		ED6580FB2CFF1A3000F5402F /* buffer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = buffer.c; path = /Users/olaf/Projekte/toolkit/ucx/buffer.c; sourceTree = "<absolute>"; };
+		ED6580FC2CFF1A3000F5402F /* compare.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = compare.c; path = /Users/olaf/Projekte/toolkit/ucx/compare.c; sourceTree = "<absolute>"; };
+		ED6580FD2CFF1A3000F5402F /* allocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = allocator.h; sourceTree = "<group>"; };
+		ED6580FE2CFF1A3000F5402F /* array_list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = array_list.h; sourceTree = "<group>"; };
+		ED6580FF2CFF1A3000F5402F /* buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = "<group>"; };
+		ED6581002CFF1A3000F5402F /* collection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = collection.h; sourceTree = "<group>"; };
+		ED6581012CFF1A3000F5402F /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
+		ED6581022CFF1A3000F5402F /* common.h.orig */ = {isa = PBXFileReference; lastKnownFileType = text; path = common.h.orig; sourceTree = "<group>"; };
+		ED6581032CFF1A3000F5402F /* compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = compare.h; sourceTree = "<group>"; };
+		ED6581042CFF1A3000F5402F /* hash_key.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hash_key.h; sourceTree = "<group>"; };
+		ED6581052CFF1A3000F5402F /* hash_map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hash_map.h; sourceTree = "<group>"; };
+		ED6581062CFF1A3000F5402F /* iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iterator.h; sourceTree = "<group>"; };
+		ED6581072CFF1A3000F5402F /* linked_list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = linked_list.h; sourceTree = "<group>"; };
+		ED6581082CFF1A3000F5402F /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = "<group>"; };
+		ED6581092CFF1A3000F5402F /* map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = map.h; sourceTree = "<group>"; };
+		ED65810A2CFF1A3000F5402F /* mempool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mempool.h; sourceTree = "<group>"; };
+		ED65810B2CFF1A3000F5402F /* printf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = printf.h; sourceTree = "<group>"; };
+		ED65810C2CFF1A3000F5402F /* string.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = string.h; sourceTree = "<group>"; };
+		ED65810D2CFF1A3000F5402F /* test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test.h; sourceTree = "<group>"; };
+		ED65810E2CFF1A3000F5402F /* tree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tree.h; sourceTree = "<group>"; };
+		ED65810F2CFF1A3000F5402F /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = "<group>"; };
+		ED6581112CFF1A3000F5402F /* hash_key.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = hash_key.c; path = /Users/olaf/Projekte/toolkit/ucx/hash_key.c; sourceTree = "<absolute>"; };
+		ED6581122CFF1A3000F5402F /* hash_map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = hash_map.c; path = /Users/olaf/Projekte/toolkit/ucx/hash_map.c; sourceTree = "<absolute>"; };
+		ED6581132CFF1A3000F5402F /* iterator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = iterator.c; path = /Users/olaf/Projekte/toolkit/ucx/iterator.c; sourceTree = "<absolute>"; };
+		ED6581142CFF1A3000F5402F /* linked_list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = linked_list.c; path = /Users/olaf/Projekte/toolkit/ucx/linked_list.c; sourceTree = "<absolute>"; };
+		ED6581152CFF1A3000F5402F /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = list.c; path = /Users/olaf/Projekte/toolkit/ucx/list.c; sourceTree = "<absolute>"; };
+		ED6581162CFF1A3000F5402F /* map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = map.c; path = /Users/olaf/Projekte/toolkit/ucx/map.c; sourceTree = "<absolute>"; };
+		ED6581172CFF1A3000F5402F /* mempool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = mempool.c; path = /Users/olaf/Projekte/toolkit/ucx/mempool.c; sourceTree = "<absolute>"; };
+		ED6581182CFF1A3000F5402F /* printf.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = printf.c; path = /Users/olaf/Projekte/toolkit/ucx/printf.c; sourceTree = "<absolute>"; };
+		ED6581192CFF1A3000F5402F /* string.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = string.c; path = /Users/olaf/Projekte/toolkit/ucx/string.c; sourceTree = "<absolute>"; };
+		ED65811B2CFF1A3000F5402F /* tree.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = tree.c; path = /Users/olaf/Projekte/toolkit/ucx/tree.c; sourceTree = "<absolute>"; };
+		ED65811C2CFF1A3000F5402F /* utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = utils.c; path = /Users/olaf/Projekte/toolkit/ucx/utils.c; sourceTree = "<absolute>"; };
+		ED65812F2CFF1A8800F5402F /* toolkit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolkit.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/toolkit.h; sourceTree = "<absolute>"; };
+		ED6581302CFF1A8800F5402F /* toolkit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = toolkit.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/toolkit.m; sourceTree = "<absolute>"; };
+		ED6581322CFF1F1900F5402F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/AppDelegate.h; sourceTree = "<absolute>"; };
+		ED6581332CFF1F1900F5402F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/AppDelegate.m; sourceTree = "<absolute>"; };
+		ED6581352CFF287300F5402F /* EventData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EventData.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/EventData.h; sourceTree = "<absolute>"; };
+		ED6581362CFF287300F5402F /* EventData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = EventData.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/EventData.m; sourceTree = "<absolute>"; };
+		ED6581372CFF287300F5402F /* UiJob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = UiJob.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiJob.h; sourceTree = "<absolute>"; };
+		ED6581382CFF287300F5402F /* UiJob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = UiJob.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiJob.m; sourceTree = "<absolute>"; };
+		ED65813B2CFF3BCE00F5402F /* button.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = button.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/button.h; sourceTree = "<absolute>"; };
+		ED65813C2CFF3BCE00F5402F /* button.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = button.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/button.m; sourceTree = "<absolute>"; };
+		ED65813D2CFF3BCE00F5402F /* Container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Container.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Container.h; sourceTree = "<absolute>"; };
+		ED65813E2CFF3BCE00F5402F /* Container.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Container.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Container.m; sourceTree = "<absolute>"; };
+		ED65813F2CFF3BCE00F5402F /* GridLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GridLayout.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/GridLayout.h; sourceTree = "<absolute>"; };
+		ED6581402CFF3BCE00F5402F /* GridLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = GridLayout.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/GridLayout.m; sourceTree = "<absolute>"; };
+		ED6581412CFF3BCE00F5402F /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = window.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/window.h; sourceTree = "<absolute>"; };
+		ED6581422CFF3BCE00F5402F /* window.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = window.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/window.m; sourceTree = "<absolute>"; };
+		ED6581482CFF3CA000F5402F /* button.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = button.h; path = /Users/olaf/Projekte/toolkit/ui/ui/button.h; sourceTree = "<absolute>"; };
+		ED6581492CFF3CA000F5402F /* container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = container.h; path = /Users/olaf/Projekte/toolkit/ui/ui/container.h; sourceTree = "<absolute>"; };
+		ED65814A2CFF3CA000F5402F /* display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = display.h; path = /Users/olaf/Projekte/toolkit/ui/ui/display.h; sourceTree = "<absolute>"; };
+		ED65814B2CFF3CA000F5402F /* dnd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dnd.h; path = /Users/olaf/Projekte/toolkit/ui/ui/dnd.h; sourceTree = "<absolute>"; };
+		ED65814C2CFF3CA000F5402F /* entry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = entry.h; path = /Users/olaf/Projekte/toolkit/ui/ui/entry.h; sourceTree = "<absolute>"; };
+		ED65814D2CFF3CA000F5402F /* graphics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = graphics.h; path = /Users/olaf/Projekte/toolkit/ui/ui/graphics.h; sourceTree = "<absolute>"; };
+		ED65814E2CFF3CA000F5402F /* icons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = icons.h; path = /Users/olaf/Projekte/toolkit/ui/ui/icons.h; sourceTree = "<absolute>"; };
+		ED65814F2CFF3CA000F5402F /* image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = image.h; path = /Users/olaf/Projekte/toolkit/ui/ui/image.h; sourceTree = "<absolute>"; };
+		ED6581502CFF3CA000F5402F /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = /Users/olaf/Projekte/toolkit/ui/ui/menu.h; sourceTree = "<absolute>"; };
+		ED6581512CFF3CA000F5402F /* properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = properties.h; path = /Users/olaf/Projekte/toolkit/ui/ui/properties.h; sourceTree = "<absolute>"; };
+		ED6581522CFF3CA000F5402F /* range.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = range.h; path = /Users/olaf/Projekte/toolkit/ui/ui/range.h; sourceTree = "<absolute>"; };
+		ED6581532CFF3CA000F5402F /* stock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stock.h; path = /Users/olaf/Projekte/toolkit/ui/ui/stock.h; sourceTree = "<absolute>"; };
+		ED6581542CFF3CA000F5402F /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = /Users/olaf/Projekte/toolkit/ui/ui/text.h; sourceTree = "<absolute>"; };
+		ED6581552CFF3CA000F5402F /* toolbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolbar.h; path = /Users/olaf/Projekte/toolkit/ui/ui/toolbar.h; sourceTree = "<absolute>"; };
+		ED6581562CFF3CA000F5402F /* toolkit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolkit.h; path = /Users/olaf/Projekte/toolkit/ui/ui/toolkit.h; sourceTree = "<absolute>"; };
+		ED6581572CFF3CA000F5402F /* tree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tree.h; path = /Users/olaf/Projekte/toolkit/ui/ui/tree.h; sourceTree = "<absolute>"; };
+		ED6581582CFF3CA000F5402F /* ui.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ui.h; path = /Users/olaf/Projekte/toolkit/ui/ui/ui.h; sourceTree = "<absolute>"; };
+		ED6581592CFF3CA000F5402F /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = window.h; path = /Users/olaf/Projekte/toolkit/ui/ui/window.h; sourceTree = "<absolute>"; };
+		ED65815A2CFF3EE900F5402F /* MainWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainWindow.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/MainWindow.h; sourceTree = "<absolute>"; };
+		ED65815B2CFF3EE900F5402F /* MainWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MainWindow.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/MainWindow.m; sourceTree = "<absolute>"; };
+		ED65815D2CFF4BF200F5402F /* WindowManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WindowManager.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/WindowManager.h; sourceTree = "<absolute>"; };
+		ED65815E2CFF4BF200F5402F /* WindowManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WindowManager.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/WindowManager.m; sourceTree = "<absolute>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+		ED6580AE2CFF122700F5402F /* toolkit */ = {
+			isa = PBXFileSystemSynchronizedRootGroup;
+			path = toolkit;
+			sourceTree = "<group>";
+		};
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		ED6580A92CFF122700F5402F /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		ED6580A32CFF122700F5402F = {
+			isa = PBXGroup;
+			children = (
+				ED6581472CFF3C8300F5402F /* public */,
+				ED65812E2CFF1A7200F5402F /* cocoa */,
+				ED6580F82CFF1A1200F5402F /* ucx */,
+				ED6580D92CFF19DB00F5402F /* common */,
+				ED6580AE2CFF122700F5402F /* toolkit */,
+				ED6580AD2CFF122700F5402F /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		ED6580AD2CFF122700F5402F /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				ED6580AC2CFF122700F5402F /* toolkit.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		ED6580D92CFF19DB00F5402F /* common */ = {
+			isa = PBXGroup;
+			children = (
+				ED6580DA2CFF19F900F5402F /* condvar.h */,
+				ED6580DB2CFF19F900F5402F /* condvar.c */,
+				ED6580DC2CFF19F900F5402F /* context.h */,
+				ED6580DD2CFF19F900F5402F /* context.c */,
+				ED6580DE2CFF19F900F5402F /* document.h */,
+				ED6580DF2CFF19F900F5402F /* document.c */,
+				ED6580E02CFF19F900F5402F /* menu.h */,
+				ED6580E12CFF19F900F5402F /* menu.c */,
+				ED6580E22CFF19F900F5402F /* object.h */,
+				ED6580E32CFF19F900F5402F /* object.c */,
+				ED6580E42CFF19F900F5402F /* properties.h */,
+				ED6580E52CFF19F900F5402F /* properties.c */,
+				ED6580E62CFF19F900F5402F /* threadpool.h */,
+				ED6580E72CFF19F900F5402F /* threadpool.c */,
+				ED6580E82CFF19F900F5402F /* toolbar.h */,
+				ED6580E92CFF19F900F5402F /* toolbar.c */,
+				ED6580EA2CFF19F900F5402F /* types.h */,
+				ED6580EB2CFF19F900F5402F /* types.c */,
+				ED6580EC2CFF19F900F5402F /* ucx_properties.h */,
+				ED6580ED2CFF19F900F5402F /* ucx_properties.c */,
+			);
+			path = common;
+			sourceTree = "<group>";
+		};
+		ED6580F82CFF1A1200F5402F /* ucx */ = {
+			isa = PBXGroup;
+			children = (
+				ED6580F92CFF1A3000F5402F /* allocator.c */,
+				ED6580FA2CFF1A3000F5402F /* array_list.c */,
+				ED6580FB2CFF1A3000F5402F /* buffer.c */,
+				ED6580FC2CFF1A3000F5402F /* compare.c */,
+				ED6581102CFF1A3000F5402F /* cx */,
+				ED6581112CFF1A3000F5402F /* hash_key.c */,
+				ED6581122CFF1A3000F5402F /* hash_map.c */,
+				ED6581132CFF1A3000F5402F /* iterator.c */,
+				ED6581142CFF1A3000F5402F /* linked_list.c */,
+				ED6581152CFF1A3000F5402F /* list.c */,
+				ED6581162CFF1A3000F5402F /* map.c */,
+				ED6581172CFF1A3000F5402F /* mempool.c */,
+				ED6581182CFF1A3000F5402F /* printf.c */,
+				ED6581192CFF1A3000F5402F /* string.c */,
+				ED65811B2CFF1A3000F5402F /* tree.c */,
+				ED65811C2CFF1A3000F5402F /* utils.c */,
+			);
+			path = ucx;
+			sourceTree = "<group>";
+		};
+		ED6581102CFF1A3000F5402F /* cx */ = {
+			isa = PBXGroup;
+			children = (
+				ED6580FD2CFF1A3000F5402F /* allocator.h */,
+				ED6580FE2CFF1A3000F5402F /* array_list.h */,
+				ED6580FF2CFF1A3000F5402F /* buffer.h */,
+				ED6581002CFF1A3000F5402F /* collection.h */,
+				ED6581012CFF1A3000F5402F /* common.h */,
+				ED6581022CFF1A3000F5402F /* common.h.orig */,
+				ED6581032CFF1A3000F5402F /* compare.h */,
+				ED6581042CFF1A3000F5402F /* hash_key.h */,
+				ED6581052CFF1A3000F5402F /* hash_map.h */,
+				ED6581062CFF1A3000F5402F /* iterator.h */,
+				ED6581072CFF1A3000F5402F /* linked_list.h */,
+				ED6581082CFF1A3000F5402F /* list.h */,
+				ED6581092CFF1A3000F5402F /* map.h */,
+				ED65810A2CFF1A3000F5402F /* mempool.h */,
+				ED65810B2CFF1A3000F5402F /* printf.h */,
+				ED65810C2CFF1A3000F5402F /* string.h */,
+				ED65810D2CFF1A3000F5402F /* test.h */,
+				ED65810E2CFF1A3000F5402F /* tree.h */,
+				ED65810F2CFF1A3000F5402F /* utils.h */,
+			);
+			name = cx;
+			path = /Users/olaf/Projekte/toolkit/ucx/cx;
+			sourceTree = "<absolute>";
+		};
+		ED65812E2CFF1A7200F5402F /* cocoa */ = {
+			isa = PBXGroup;
+			children = (
+				ED65815D2CFF4BF200F5402F /* WindowManager.h */,
+				ED65815E2CFF4BF200F5402F /* WindowManager.m */,
+				ED65815A2CFF3EE900F5402F /* MainWindow.h */,
+				ED65815B2CFF3EE900F5402F /* MainWindow.m */,
+				ED65813B2CFF3BCE00F5402F /* button.h */,
+				ED65813C2CFF3BCE00F5402F /* button.m */,
+				ED65813D2CFF3BCE00F5402F /* Container.h */,
+				ED65813E2CFF3BCE00F5402F /* Container.m */,
+				ED65813F2CFF3BCE00F5402F /* GridLayout.h */,
+				ED6581402CFF3BCE00F5402F /* GridLayout.m */,
+				ED6581412CFF3BCE00F5402F /* window.h */,
+				ED6581422CFF3BCE00F5402F /* window.m */,
+				ED6581352CFF287300F5402F /* EventData.h */,
+				ED6581362CFF287300F5402F /* EventData.m */,
+				ED6581372CFF287300F5402F /* UiJob.h */,
+				ED6581382CFF287300F5402F /* UiJob.m */,
+				ED6581322CFF1F1900F5402F /* AppDelegate.h */,
+				ED6581332CFF1F1900F5402F /* AppDelegate.m */,
+				ED65812F2CFF1A8800F5402F /* toolkit.h */,
+				ED6581302CFF1A8800F5402F /* toolkit.m */,
+			);
+			path = cocoa;
+			sourceTree = "<group>";
+		};
+		ED6581472CFF3C8300F5402F /* public */ = {
+			isa = PBXGroup;
+			children = (
+				ED6581482CFF3CA000F5402F /* button.h */,
+				ED6581492CFF3CA000F5402F /* container.h */,
+				ED65814A2CFF3CA000F5402F /* display.h */,
+				ED65814B2CFF3CA000F5402F /* dnd.h */,
+				ED65814C2CFF3CA000F5402F /* entry.h */,
+				ED65814D2CFF3CA000F5402F /* graphics.h */,
+				ED65814E2CFF3CA000F5402F /* icons.h */,
+				ED65814F2CFF3CA000F5402F /* image.h */,
+				ED6581502CFF3CA000F5402F /* menu.h */,
+				ED6581512CFF3CA000F5402F /* properties.h */,
+				ED6581522CFF3CA000F5402F /* range.h */,
+				ED6581532CFF3CA000F5402F /* stock.h */,
+				ED6581542CFF3CA000F5402F /* text.h */,
+				ED6581552CFF3CA000F5402F /* toolbar.h */,
+				ED6581562CFF3CA000F5402F /* toolkit.h */,
+				ED6581572CFF3CA000F5402F /* tree.h */,
+				ED6581582CFF3CA000F5402F /* ui.h */,
+				ED6581592CFF3CA000F5402F /* window.h */,
+			);
+			path = public;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		ED6580AB2CFF122700F5402F /* toolkit */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = ED6580BC2CFF122800F5402F /* Build configuration list for PBXNativeTarget "toolkit" */;
+			buildPhases = (
+				ED6580A82CFF122700F5402F /* Sources */,
+				ED6580A92CFF122700F5402F /* Frameworks */,
+				ED6580AA2CFF122700F5402F /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			fileSystemSynchronizedGroups = (
+				ED6580AE2CFF122700F5402F /* toolkit */,
+			);
+			name = toolkit;
+			packageProductDependencies = (
+			);
+			productName = toolkit;
+			productReference = ED6580AC2CFF122700F5402F /* toolkit.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		ED6580A42CFF122700F5402F /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = 1;
+				LastUpgradeCheck = 1610;
+				TargetAttributes = {
+					ED6580AB2CFF122700F5402F = {
+						CreatedOnToolsVersion = 16.1;
+					};
+				};
+			};
+			buildConfigurationList = ED6580A72CFF122700F5402F /* Build configuration list for PBXProject "toolkit" */;
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = ED6580A32CFF122700F5402F;
+			minimizedProjectReferenceProxies = 1;
+			preferredProjectObjectVersion = 77;
+			productRefGroup = ED6580AD2CFF122700F5402F /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				ED6580AB2CFF122700F5402F /* toolkit */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		ED6580AA2CFF122700F5402F /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				ED65812D2CFF1A3000F5402F /* common.h.orig in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		ED6580A82CFF122700F5402F /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				ED6580EE2CFF19F900F5402F /* context.c in Sources */,
+				ED6580EF2CFF19F900F5402F /* menu.c in Sources */,
+				ED6580F02CFF19F900F5402F /* threadpool.c in Sources */,
+				ED6580F12CFF19F900F5402F /* condvar.c in Sources */,
+				ED65815F2CFF4BF200F5402F /* WindowManager.m in Sources */,
+				ED6580F22CFF19F900F5402F /* document.c in Sources */,
+				ED65811D2CFF1A3000F5402F /* linked_list.c in Sources */,
+				ED65811E2CFF1A3000F5402F /* tree.c in Sources */,
+				ED6581202CFF1A3000F5402F /* mempool.c in Sources */,
+				ED6581212CFF1A3000F5402F /* map.c in Sources */,
+				ED6581222CFF1A3000F5402F /* hash_map.c in Sources */,
+				ED6581232CFF1A3000F5402F /* array_list.c in Sources */,
+				ED6581242CFF1A3000F5402F /* list.c in Sources */,
+				ED65815C2CFF3EE900F5402F /* MainWindow.m in Sources */,
+				ED6581252CFF1A3000F5402F /* compare.c in Sources */,
+				ED6581262CFF1A3000F5402F /* hash_key.c in Sources */,
+				ED6581272CFF1A3000F5402F /* iterator.c in Sources */,
+				ED6581282CFF1A3000F5402F /* string.c in Sources */,
+				ED6581312CFF1A8800F5402F /* toolkit.m in Sources */,
+				ED6581342CFF1F1900F5402F /* AppDelegate.m in Sources */,
+				ED6581292CFF1A3000F5402F /* allocator.c in Sources */,
+				ED6581432CFF3BCE00F5402F /* window.m in Sources */,
+				ED6581442CFF3BCE00F5402F /* button.m in Sources */,
+				ED6581452CFF3BCE00F5402F /* Container.m in Sources */,
+				ED6581462CFF3BCE00F5402F /* GridLayout.m in Sources */,
+				ED65812A2CFF1A3000F5402F /* utils.c in Sources */,
+				ED6581392CFF287300F5402F /* EventData.m in Sources */,
+				ED65813A2CFF287300F5402F /* UiJob.m in Sources */,
+				ED65812B2CFF1A3000F5402F /* buffer.c in Sources */,
+				ED65812C2CFF1A3000F5402F /* printf.c in Sources */,
+				ED6580F32CFF19F900F5402F /* object.c in Sources */,
+				ED6580F42CFF19F900F5402F /* toolbar.c in Sources */,
+				ED6580F52CFF19F900F5402F /* types.c in Sources */,
+				ED6580F62CFF19F900F5402F /* properties.c in Sources */,
+				ED6580F72CFF19F900F5402F /* ucx_properties.c in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		ED6580BA2CFF122800F5402F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = ../../../ucx;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		ED6580BB2CFF122800F5402F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = ../../../ucx;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		ED6580BD2CFF122800F5402F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = toolkit/toolkit.entitlements;
+				CODE_SIGN_STYLE = Manual;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = "";
+				GENERATE_INFOPLIST_FILE = YES;
+				HEADER_SEARCH_PATHS = (
+					../../../ucx,
+					../../../ui,
+				);
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INFOPLIST_KEY_NSMainNibFile = MainMenu;
+				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				OTHER_CFLAGS = "-DUI_COCOA";
+				PRODUCT_BUNDLE_IDENTIFIER = de.unixwork.toolkit;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_EMIT_LOC_STRINGS = YES;
+			};
+			name = Debug;
+		};
+		ED6580BE2CFF122800F5402F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = toolkit/toolkit.entitlements;
+				CODE_SIGN_STYLE = Manual;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = "";
+				GENERATE_INFOPLIST_FILE = YES;
+				HEADER_SEARCH_PATHS = (
+					../../../ucx,
+					../../../ui,
+				);
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INFOPLIST_KEY_NSMainNibFile = MainMenu;
+				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				OTHER_CFLAGS = "-DUI_COCOA";
+				PRODUCT_BUNDLE_IDENTIFIER = de.unixwork.toolkit;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_EMIT_LOC_STRINGS = YES;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		ED6580A72CFF122700F5402F /* Build configuration list for PBXProject "toolkit" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				ED6580BA2CFF122800F5402F /* Debug */,
+				ED6580BB2CFF122800F5402F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		ED6580BC2CFF122800F5402F /* Build configuration list for PBXNativeTarget "toolkit" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				ED6580BD2CFF122800F5402F /* Debug */,
+				ED6580BE2CFF122800F5402F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = ED6580A42CFF122700F5402F /* Project object */;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit.xcodeproj/xcshareddata/xcschemes/toolkit.xcscheme	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1610"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES"
+      buildArchitectures = "Automatic">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ED6580AB2CFF122700F5402F"
+               BuildableName = "toolkit.app"
+               BlueprintName = "toolkit"
+               ReferencedContainer = "container:toolkit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      shouldAutocreateTestPlan = "YES">
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "ED6580AB2CFF122700F5402F"
+            BuildableName = "toolkit.app"
+            BlueprintName = "toolkit"
+            ReferencedContainer = "container:toolkit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "ED6580AB2CFF122700F5402F"
+            BuildableName = "toolkit.app"
+            BlueprintName = "toolkit"
+            ReferencedContainer = "container:toolkit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/Assets.xcassets/AccentColor.colorset/Contents.json	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,11 @@
+{
+  "colors" : [
+    {
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/Assets.xcassets/AppIcon.appiconset/Contents.json	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,58 @@
+{
+  "images" : [
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "16x16"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "16x16"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "32x32"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "32x32"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "128x128"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "128x128"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "256x256"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "256x256"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "512x512"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "512x512"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/Assets.xcassets/Contents.json	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/Base.lproj/MainMenu.xib	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="toolkit" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="toolkit" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About toolkit" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide toolkit" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit toolkit" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="File" id="dMs-cI-mzQ">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                </menuItem>
+                <menuItem title="Format" id="jxT-CU-nIS">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                </menuItem>
+                <menuItem title="Help" id="wpr-3q-Mcd">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+                        <items>
+                            <menuItem title="toolkit Help" keyEquivalent="?" id="FKE-Sm-Kum">
+                                <connections>
+                                    <action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="200" y="121"/>
+        </menu>
+    </objects>
+</document>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/main.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#include <ui/ui.h>
+
+static void action_button(UiEvent *event, void *userdata) {
+    printf("button click\n");
+}
+
+void application_startup(UiEvent *event, void *data) {
+    UiObject *obj = ui_window("My Window", NULL);
+    
+    
+    ui_button(obj, .label = "Button X1 -------------------------------------------------- END", .onclick = action_button);
+    ui_newline(obj);
+    ui_button(obj, .label = "Button X2");
+    ui_button(obj, .label = "Button X3", .hexpand = TRUE);
+    ui_button(obj, .label = "Button X4");
+    ui_newline(obj);
+    ui_button(obj, .label = "Button X5");
+    
+    
+    ui_show(obj);
+}
+
+int main(int argc, char * argv[]) {
+    ui_init("app1", argc, argv);
+    ui_onstartup(application_startup, NULL);
+    ui_main();
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/xcode/toolkit/toolkit/toolkit.entitlements	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<false/>
+	<key>com.apple.security.files.user-selected.read-only</key>
+	<true/>
+</dict>
+</plist>
--- a/resource/template.app/Contents/Info.plist	Sun May 23 09:44:43 2021 +0200
+++ b/resource/template.app/Contents/Info.plist	Sat Jan 04 16:38:48 2025 +0100
@@ -3,13 +3,13 @@
 <plist version="1.0">
 <dict>
 	<key>BuildMachineOSBuild</key>
-	<string>10K549</string>
+	<string>24B91</string>
 	<key>CFBundleDevelopmentRegion</key>
-	<string>de_DE</string>
+	<string>en</string>
 	<key>CFBundleExecutable</key>
 	<string>mk12</string>
 	<key>CFBundleIdentifier</key>
-	<string>com.yourcompany.toolkit</string>
+	<string>de.unixwork.toolkit</string>
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
@@ -18,53 +18,32 @@
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.0</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
 	<key>CFBundleVersion</key>
 	<string>1</string>
 	<key>DTCompiler</key>
-	<string></string>
+	<string>com.apple.compilers.llvm.clang.1_0</string>
 	<key>DTPlatformBuild</key>
-	<string>10M2518</string>
+	<string>24B75</string>
+	<key>DTPlatformName</key>
+	<string>macosx</string>
 	<key>DTPlatformVersion</key>
-	<string>PG</string>
+	<string>15.1</string>
 	<key>DTSDKBuild</key>
-	<string>10M2518</string>
+	<string>24B75</string>
 	<key>DTSDKName</key>
-	<string>macosx10.6</string>
+	<string>macosx15.1</string>
 	<key>DTXcode</key>
-	<string>0400</string>
+	<string>1610</string>
 	<key>DTXcodeBuild</key>
-	<string>10M2518</string>
+	<string>16B40</string>
 	<key>LSMinimumSystemVersion</key>
-	<string>10.7</string>
+	<string>15.1</string>
 	<key>NSMainNibFile</key>
 	<string>MainMenu</string>
-	<key>CFBundleDisplayName</key>
-	<string></string>
-	<key>CFBundleGetInfoString</key>
-	<string></string>
-	<key>LSApplicationCategoryType</key>
-	<string></string>
-	<key>CFBundleDocumentTypes</key>
-	<array>
-		<dict>
-			<key>LSItemContentTypes</key>
-			<array>
-				<string>public.data</string>
-			</array>
-			<key>CFBundleTypeIconFile</key>
-			<string></string>
-			<key>CFBundleTypeName</key>
-			<string>DocumentType</string>
-			<key>CFBundleTypeRole</key>
-			<string>Editor</string>
-<!--
-			<key>NSDocumentClass</key>
-			<string>Document</string>
--->
-		</dict>
-	</array>
 	<key>NSPrincipalClass</key>
 	<string>NSApplication</string>
 </dict>
Binary file resource/template.app/Contents/Resources/Base.lproj/MainMenu.nib has changed
Binary file resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings has changed
Binary file resource/template.app/Contents/Resources/English.lproj/MainMenu.nib has changed
--- a/ucx/Makefile	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/Makefile	Sat Jan 04 16:38:48 2025 +0100
@@ -30,24 +30,25 @@
 include ../config.mk
 
 # list of source files
-SRC  = utils.c
+SRC  = allocator.c
+SRC += array_list.c
+SRC += mempool.c
+SRC += buffer.c
+SRC += compare.c
+SRC += hash_key.c
+SRC += hash_map.c
+SRC += linked_list.c
 SRC += list.c
 SRC += map.c
-SRC += avl.c
-SRC += properties.c
-SRC += mempool.c
+SRC += printf.c
 SRC += string.c
-SRC += test.c
-SRC += allocator.c
-SRC += logging.c
-SRC += buffer.c
-SRC += stack.c
-SRC += ucx.c
-SRC += array.c
+SRC += utils.c
+SRC += tree.c
+SRC += iterator.c
 
-OBJ   = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT))
+OBJ   = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
 
-UCX_LIB = ../build/lib/libucx.$(LIB_EXT)
+UCX_LIB = ../build/lib/libucx$(LIB_EXT)
 
 all: ../build/ucx $(UCX_LIB)
 
@@ -57,6 +58,6 @@
 ../build/ucx:
 	mkdir -p ../build/ucx
 
-../build/ucx/%.$(OBJ_EXT): %.c
+../build/ucx/%$(OBJ_EXT): %.c
 	$(CC) $(CFLAGS) -o $@ -c $<
 
--- a/ucx/README	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-UCX is a library for common data structures, algorithms and string functions.
-
-More informations at: https://develop.uap-core.de/ucx/
-
--- a/ucx/allocator.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/allocator.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,35 +26,111 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/allocator.h"
-
-#include <stdlib.h>
+#include "cx/allocator.h"
 
-static UcxAllocator default_allocator = {
-    NULL,
-    ucx_default_malloc,
-    ucx_default_calloc,
-    ucx_default_realloc,
-    ucx_default_free
-};
-
-UcxAllocator *ucx_default_allocator() {
-    UcxAllocator *allocator = &default_allocator;
-    return allocator;
-}
-
-void *ucx_default_malloc(void *ignore, size_t n) {
+__attribute__((__malloc__, __alloc_size__(2)))
+static void *cx_malloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        size_t n
+) {
     return malloc(n);
 }
 
-void *ucx_default_calloc(void *ignore, size_t n, size_t size) {
-    return calloc(n, size);
+__attribute__((__warn_unused_result__, __alloc_size__(3)))
+static void *cx_realloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        void *mem,
+        size_t n
+) {
+    return realloc(mem, n);
+}
+
+__attribute__((__malloc__, __alloc_size__(2, 3)))
+static void *cx_calloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        size_t nelem,
+        size_t n
+) {
+    return calloc(nelem, n);
+}
+
+__attribute__((__nonnull__))
+static void cx_free_stdlib(
+        __attribute__((__unused__)) void *d,
+        void *mem
+) {
+    free(mem);
 }
 
-void *ucx_default_realloc(void *ignore, void *data, size_t n) {
-    return realloc(data, n);
+static cx_allocator_class cx_default_allocator_class = {
+        cx_malloc_stdlib,
+        cx_realloc_stdlib,
+        cx_calloc_stdlib,
+        cx_free_stdlib
+};
+
+struct cx_allocator_s cx_default_allocator = {
+        &cx_default_allocator_class,
+        NULL
+};
+CxAllocator *cxDefaultAllocator = &cx_default_allocator;
+
+
+int cx_reallocate(
+        void **mem,
+        size_t n
+) {
+    void *nmem = realloc(*mem, n);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
+// IMPLEMENTATION OF HIGH LEVEL API
+
+void *cxMalloc(
+        const CxAllocator *allocator,
+        size_t n
+) {
+    return allocator->cl->malloc(allocator->data, n);
 }
 
-void ucx_default_free(void *ignore, void *data) {
-    free(data);
+void *cxRealloc(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t n
+) {
+    return allocator->cl->realloc(allocator->data, mem, n);
 }
+
+int cxReallocate(
+        const CxAllocator *allocator,
+        void **mem,
+        size_t n
+) {
+    void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
+void *cxCalloc(
+        const CxAllocator *allocator,
+        size_t nelem,
+        size_t n
+) {
+    return allocator->cl->calloc(allocator->data, nelem, n);
+}
+
+void cxFree(
+        const CxAllocator *allocator,
+        void *mem
+) {
+    allocator->cl->free(allocator->data, mem);
+}
--- a/ucx/array.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2019 Mike Becker, 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.
- */
-
-#define _GNU_SOURCE /* we want to use qsort_r(), if available */
-#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */
-
-
-#include "ucx/array.h"
-#include "ucx/utils.h"
-
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#ifndef UCX_ARRAY_DISABLE_QSORT
-#ifdef __GLIBC__
-#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
-#define ucx_array_sort_impl qsort_r
-#endif /* glibc version >= 2.8 */
-#elif /* not  __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__)
-#define ucx_array_sort_impl ucx_qsort_r
-#define USE_UCX_QSORT_R
-#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun)
-#if __STDC_VERSION__ >= 201112L
-#define ucx_array_sort_impl qsort_s
-#endif
-#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */
-#endif /* UCX_ARRAY_DISABLE_QSORT */
-
-#ifndef ucx_array_sort_impl
-#define ucx_array_sort_impl ucx_mergesort
-#endif
-
-static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) {
-    size_t required_capacity = array->capacity;
-    while (reqcap > required_capacity) {
-        if (required_capacity * 2 < required_capacity)
-            return 1;
-        required_capacity <<= 1;
-    }
-    if (ucx_array_reserve(array, required_capacity)) {
-        return 1;
-    }
-    return 0;
-}
-
-int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
-    size_t elmsize, size_t index, void* data) {
-    
-    if(!alloc || !capacity || !array) {
-        errno = EINVAL;
-        return 1;
-    }
-    
-    size_t newcapacity = *capacity;
-    while(index >= newcapacity) {
-        if(ucx_szmul(newcapacity, 2, &newcapacity)) {
-            errno = EOVERFLOW;
-            return 1;
-        }        
-    }
-
-    size_t memlen, offset;
-    if(ucx_szmul(newcapacity, elmsize, &memlen)) {
-        errno = EOVERFLOW;
-        return 1;
-    }
-    /* we don't need to check index*elmsize - it is smaller than memlen */
-    
-    
-    void* newptr = alrealloc(alloc, *array, memlen);
-    if(newptr == NULL) {
-        errno = ENOMEM; /* we cannot assume that every allocator sets this */
-        return 1;
-    }
-    *array = newptr;
-    *capacity = newcapacity;
-    
-    
-    char* dest = *array;
-    dest += elmsize*index;
-    memcpy(dest, data, elmsize);
-    
-    return 0;
-}
-
-int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
-    size_t index, void* data) {
-    
-    return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*),
-            index, &data);
-}
-
-UcxArray* ucx_array_new(size_t capacity, size_t elemsize) {
-    return ucx_array_new_a(capacity, elemsize, ucx_default_allocator());
-}
-
-UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
-        UcxAllocator* allocator) {
-    UcxArray* array = almalloc(allocator, sizeof(UcxArray));
-    if(array) {
-        ucx_array_init_a(array, capacity, elemsize, allocator);
-    }
-    return array;
-}
-
-void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) {
-    ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator());
-}
-
-void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
-        UcxAllocator* allocator) {
-    
-    array->allocator = allocator;
-    array->elemsize = elemsize;
-    array->size = 0;
-    array->data = alcalloc(allocator, capacity, elemsize);
-    
-    if (array->data) {
-        array->capacity = capacity;
-    } else {
-        array->capacity = 0;
-    }
-}
-
-int ucx_array_clone(UcxArray* dest, UcxArray const* src) {
-    if (ucx_array_ensurecap(dest, src->capacity)) {
-        return 1;
-    }
-    
-    dest->elemsize = src->elemsize;
-    dest->size = src->size;
-    
-    if (dest->data) {
-        memcpy(dest->data, src->data, src->size*src->elemsize);
-    }
-    
-    return 0;
-}
-
-int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
-        cmp_func cmpfnc, void* data) {
-    
-    if (array1->size != array2->size || array1->elemsize != array2->elemsize) {
-        return 0;
-    } else {
-        if (array1->size == 0)
-            return 1;
-        
-        size_t elemsize;
-        if (cmpfnc == NULL) {
-            cmpfnc = ucx_cmp_mem;
-            elemsize = array1->elemsize;
-            data = &elemsize;
-        }
-        
-        for (size_t i = 0 ; i < array1->size ; i++) {
-            int r = cmpfnc(
-                    ucx_array_at(array1, i),
-                    ucx_array_at(array2, i),
-                    data);
-            if (r != 0)
-                return 0;
-        }
-        return 1;
-    }
-}
-
-void ucx_array_destroy(UcxArray *array) {
-    if(array->data)
-        alfree(array->allocator, array->data);
-    array->data = NULL;
-    array->capacity = array->size = 0;
-}
-
-void ucx_array_free(UcxArray *array) {
-    ucx_array_destroy(array);
-    alfree(array->allocator, array);
-}
-
-int ucx_array_append_from(UcxArray *array, void *data, size_t count) {
-    if (ucx_array_ensurecap(array, array->size + count))
-        return 1;
-    
-    void* dest = ucx_array_at(array, array->size);
-    if (data) {
-        memcpy(dest, data, array->elemsize*count);
-    } else {
-        memset(dest, 0, array->elemsize*count);
-    }
-    array->size += count;
-    
-    return 0;
-}
-
-int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) {
-    if (ucx_array_ensurecap(array, array->size + count))
-        return 1;
-    
-    if (array->size > 0) {
-        void *dest = ucx_array_at(array, count);
-        memmove(dest, array->data, array->elemsize*array->size);
-    }
-    
-    if (data) {
-        memcpy(array->data, data, array->elemsize*count);
-    } else {
-        memset(array->data, 0, array->elemsize*count);
-    }
-    array->size += count;
-        
-    return 0;
-}
-
-int ucx_array_set_from(UcxArray *array, size_t index,
-        void *data, size_t count) {
-    if (ucx_array_ensurecap(array, index + count))
-        return 1;
-    
-    if (index+count > array->size) {
-        array->size = index+count;
-    }
-    
-    void *dest = ucx_array_at(array, index);
-    if (data) {
-        memcpy(dest, data, array->elemsize*count);
-    } else {
-        memset(dest, 0, array->elemsize*count);
-    }
-    
-    return 0;
-}
-
-int ucx_array_concat(UcxArray *array1, const UcxArray *array2) {
-    
-    if (array1->elemsize != array2->elemsize)
-        return 1;
-    
-    size_t capacity = array1->capacity+array2->capacity;
-        
-    if (array1->capacity < capacity) {
-        if (ucx_array_reserve(array1, capacity)) {
-            return 1;
-        }
-    }
-    
-    void* dest = ucx_array_at(array1, array1->size);
-    memcpy(dest, array2->data, array2->size*array2->elemsize);
-    
-    array1->size += array2->size;
-    
-    return 0;
-}
-
-void *ucx_array_at(UcxArray const *array, size_t index) {
-    char* memory = array->data;
-    char* loc = memory + index*array->elemsize;
-    return loc;
-}
-
-size_t ucx_array_find(UcxArray const *array, void *elem,
-        cmp_func cmpfnc, void *data) {
-    
-    size_t elemsize;
-    if (cmpfnc == NULL) {
-        cmpfnc = ucx_cmp_mem;
-        elemsize = array->elemsize;
-        data = &elemsize;
-    }
-
-    if (array->size > 0) {
-        for (size_t i = 0 ; i < array->size ; i++) {
-            void* ptr = ucx_array_at(array, i);
-            if (cmpfnc(ptr, elem, data) == 0) {
-                return i;
-            }
-        }
-        return array->size;
-    } else {
-        return 0;
-    }
-}
-
-int ucx_array_contains(UcxArray const *array, void *elem,
-        cmp_func cmpfnc, void *data) {
-    return ucx_array_find(array, elem, cmpfnc, data) != array->size;
-}
-
-static void ucx_mergesort_merge(void *arrdata,size_t elemsize,
-        cmp_func cmpfnc, void *data,
-        size_t start, size_t mid, size_t end) { 
-    
-    char* array = arrdata;
-    
-    size_t rightstart = mid + 1; 
-  
-    if (cmpfnc(array + mid*elemsize,
-            array + rightstart*elemsize, data) <= 0) {
-        /* already sorted */
-        return;
-    }
-  
-    /* we need memory for one element */
-    void *value = malloc(elemsize);
-    
-    while (start <= mid && rightstart <= end) { 
-        if (cmpfnc(array + start*elemsize,
-                array + rightstart*elemsize, data) <= 0) { 
-            start++; 
-        } else {
-            /* save the value from the right */
-            memcpy(value, array + rightstart*elemsize, elemsize);
-                        
-            /* shift all left elements one element to the right */
-            size_t shiftcount = rightstart-start;
-            void *startptr = array + start*elemsize;
-            void *dest = array + (start+1)*elemsize;
-            memmove(dest, startptr, shiftcount*elemsize);
-            
-            /* bring the first value from the right to the left */
-            memcpy(startptr, value, elemsize);
-  
-            start++; 
-            mid++; 
-            rightstart++; 
-        }
-    }
-    
-    /* free the temporary memory */
-    free(value);
-} 
-  
-static void ucx_mergesort_impl(void *arrdata, size_t elemsize,
-        cmp_func cmpfnc, void *data, size_t l, size_t r) { 
-    if (l < r) {
-        size_t m = l + (r - l) / 2; 
-  
-        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); 
-        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); 
-        ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r);
-    } 
-}
-
-static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize,
-        cmp_func cmpfnc, void *data) {
-    
-    ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1);
-}
-
-#ifdef USE_UCX_QSORT_R
-struct cmpfnc_swapargs_info {
-    cmp_func func;
-    void *data;
-};
-
-static int cmp_func_swap_args(void *data, const void *x, const void *y) {
-    struct cmpfnc_swapargs_info* info = data;
-    return info->func(x, y, info->data);
-}
-
-static void ucx_qsort_r(void *array, size_t count, size_t elemsize,
-		     cmp_func cmpfnc, void *data) {
-    struct cmpfnc_swapargs_info info;
-    info.func = cmpfnc;
-    info.data = data;
-    qsort_r(array, count, elemsize, &info, cmp_func_swap_args);
-}
-#endif /* USE_UCX_QSORT_R */
-
-void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) {
-    ucx_array_sort_impl(array->data, array->size, array->elemsize,
-            cmpfnc, data);
-}
-
-void ucx_array_remove(UcxArray *array, size_t index) {
-    array->size--;
-    if (index < array->size) {
-        void* dest = ucx_array_at(array, index);
-        void* src = ucx_array_at(array, index+1);
-        memmove(dest, src, (array->size - index)*array->elemsize);
-    }
-}
-
-void ucx_array_remove_fast(UcxArray *array, size_t index) {
-    array->size--;
-    if (index < array->size) {       
-        void* dest = ucx_array_at(array, index);
-        void* src = ucx_array_at(array, array->size);
-        memcpy(dest, src, array->elemsize);
-    }
-}
-
-int ucx_array_shrink(UcxArray* array) {
-    void* newptr = alrealloc(array->allocator, array->data,
-                array->size*array->elemsize);
-    if (newptr) {
-        array->data = newptr;
-        array->capacity = array->size;
-        return 0;
-    } else {
-        return 1;
-    }
-}
-
-int ucx_array_resize(UcxArray* array, size_t capacity) {
-    if (array->capacity >= capacity) {
-        void* newptr = alrealloc(array->allocator, array->data,
-                capacity*array->elemsize);
-        if (newptr) {
-            array->data = newptr;
-            array->capacity = capacity;
-            if (array->size > array->capacity) {
-                array->size = array->capacity;
-            }
-            return 0;
-        } else {
-            return 1;
-        }
-    } else {
-        return ucx_array_reserve(array, capacity);
-    }
-}
-
-int ucx_array_reserve(UcxArray* array, size_t capacity) {
-    if (array->capacity > capacity) {
-        return 0;
-    } else {
-        void* newptr = alrealloc(array->allocator, array->data,
-                capacity*array->elemsize);
-        if (newptr) {
-            array->data = newptr;
-            array->capacity = capacity;
-            return 0;
-        } else {
-            return 1;
-        }
-    }
-}
-
-int ucx_array_grow(UcxArray* array, size_t count) {
-    return ucx_array_reserve(array, array->size+count);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/array_list.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,767 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/array_list.h"
+#include "cx/compare.h"
+#include <assert.h>
+#include <string.h>
+
+// Default array reallocator
+
+static void *cx_array_default_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        __attribute__((__unused__)) struct cx_array_reallocator_s *alloc
+) {
+    return realloc(array, capacity * elem_size);
+}
+
+struct cx_array_reallocator_s cx_array_default_reallocator_impl = {
+        cx_array_default_realloc, NULL, NULL, 0, 0
+};
+
+struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+
+// LOW LEVEL ARRAY LIST FUNCTIONS
+
+enum cx_array_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        const void *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) {
+    // assert pointers
+    assert(target != NULL);
+    assert(size != NULL);
+    assert(src != NULL);
+
+    // determine capacity
+    size_t cap = capacity == NULL ? *size : *capacity;
+
+    // check if resize is required
+    size_t minsize = index + elem_count;
+    size_t newsize = *size < minsize ? minsize : *size;
+    bool needrealloc = newsize > cap;
+
+    // reallocate if possible
+    if (needrealloc) {
+        // a reallocator and a capacity variable must be available
+        if (reallocator == NULL || capacity == NULL) {
+            return CX_ARRAY_REALLOC_NOT_SUPPORTED;
+        }
+
+        // check, if we need to repair the src pointer
+        uintptr_t targetaddr = (uintptr_t) *target;
+        uintptr_t srcaddr = (uintptr_t) src;
+        bool repairsrc = targetaddr <= srcaddr
+                         && srcaddr < targetaddr + cap * elem_size;
+
+        // calculate new capacity (next number divisible by 16)
+        cap = newsize - (newsize % 16) + 16;
+        assert(cap > newsize);
+
+        // perform reallocation
+        void *newmem = reallocator->realloc(
+                *target, cap, elem_size, reallocator
+        );
+        if (newmem == NULL) {
+            return CX_ARRAY_REALLOC_FAILED;
+        }
+
+        // repair src pointer, if necessary
+        if (repairsrc) {
+            src = ((char *) newmem) + (srcaddr - targetaddr);
+        }
+
+        // store new pointer and capacity
+        *target = newmem;
+        *capacity = cap;
+    }
+
+    // determine target pointer
+    char *start = *target;
+    start += index * elem_size;
+
+    // copy elements and set new size
+    memmove(start, src, elem_count * elem_size);
+    *size = newsize;
+
+    // return successfully
+    return CX_ARRAY_SUCCESS;
+}
+
+enum cx_array_result cx_array_insert_sorted(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        cx_compare_func cmp_func,
+        const void *sorted_data,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) {
+    // assert pointers
+    assert(target != NULL);
+    assert(size != NULL);
+    assert(capacity != NULL);
+    assert(cmp_func != NULL);
+    assert(sorted_data != NULL);
+    assert(reallocator != NULL);
+
+    // corner case
+    if (elem_count == 0) return 0;
+
+    // store some counts
+    size_t old_size = *size;
+    size_t needed_capacity = old_size + elem_count;
+
+    // if we need more than we have, try a reallocation
+    if (needed_capacity > *capacity) {
+        size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16;
+        void *new_mem = reallocator->realloc(
+                *target, new_capacity, elem_size, reallocator
+        );
+        if (new_mem == NULL) {
+            // give it up right away, there is no contract
+            // that requires us to insert as much as we can
+            return CX_ARRAY_REALLOC_FAILED;
+        }
+        *target = new_mem;
+        *capacity = new_capacity;
+    }
+
+    // now we have guaranteed that we can insert everything
+    size_t new_size = old_size + elem_count;
+    *size = new_size;
+
+    // declare the source and destination indices/pointers
+    size_t si = 0, di = 0;
+    const char *src = sorted_data;
+    char *dest = *target;
+
+    // find the first insertion point
+    di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
+    dest += di * elem_size;
+
+    // move the remaining elements in the array completely to the right
+    // we will call it the "buffer" for parked elements
+    size_t buf_size = old_size - di;
+    size_t bi = new_size - buf_size;
+    char *bptr = ((char *) *target) + bi * elem_size;
+    memmove(bptr, dest, buf_size * elem_size);
+
+    // 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
+        size_t copy_len, bytes_copied;
+        copy_len = cx_array_binary_search_sup(
+                src,
+                elem_count - si,
+                elem_size,
+                bptr,
+                cmp_func
+        );
+
+        // copy the source elements
+        bytes_copied = copy_len * elem_size;
+        memcpy(dest, src, bytes_copied);
+        dest += bytes_copied;
+        src += bytes_copied;
+        si += copy_len;
+
+        // when all source elements are in place, we are done
+        if (si >= elem_count) break;
+
+        // determine how many buffered elements need to be restored
+        copy_len = cx_array_binary_search_sup(
+                bptr,
+                new_size - bi,
+                elem_size,
+                src,
+                cmp_func
+        );
+
+        // restore the buffered elements
+        bytes_copied = copy_len * elem_size;
+        memmove(dest, bptr, bytes_copied);
+        dest += bytes_copied;
+        bptr += bytes_copied;
+        bi += copy_len;
+    }
+
+    // still source elements left? simply append them
+    if (si < elem_count) {
+        memcpy(dest, src, elem_size * (elem_count - si));
+    }
+
+    // still buffer elements left?
+    // don't worry, we already moved them to the correct place
+
+    return CX_ARRAY_SUCCESS;
+}
+
+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
+) {
+    // special case: empty array
+    if (size == 0) return 0;
+
+    // declare a variable that will contain the compare results
+    int result;
+
+    // cast the array pointer to something we can use offsets with
+    const char *array = arr;
+
+    // check the first array element
+    result = cmp_func(elem, array);
+    if (result < 0) {
+        return size;
+    } else if (result == 0) {
+        return 0;
+    }
+
+    // check the last array element
+    result = cmp_func(elem, array + elem_size * (size - 1));
+    if (result >= 0) {
+        return size - 1;
+    }
+
+    // the element is now guaranteed to be somewhere in the list
+    // so start the binary search
+    size_t left_index = 1;
+    size_t right_index = size - 1;
+    size_t pivot_index;
+
+    while (left_index <= right_index) {
+        pivot_index = left_index + (right_index - left_index) / 2;
+        const char *arr_elem = array + pivot_index * elem_size;
+        result = cmp_func(elem, arr_elem);
+        if (result == 0) {
+            // found it!
+            return pivot_index;
+        } else if (result < 0) {
+            // element is smaller than pivot, continue search left
+            right_index = pivot_index - 1;
+        } else {
+            // element is larger than pivot, continue search right
+            left_index = pivot_index + 1;
+        }
+    }
+
+    // report the largest upper bound
+    return result < 0 ? (pivot_index - 1) : pivot_index;
+}
+
+#ifndef CX_ARRAY_SWAP_SBO_SIZE
+#define CX_ARRAY_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
+
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+) {
+    assert(arr != NULL);
+
+    // short circuit
+    if (idx1 == idx2) return;
+
+    char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
+    void *tmp;
+
+    // decide if we can use the local buffer
+    if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
+        tmp = malloc(elem_size);
+        // we don't want to enforce error handling
+        if (tmp == NULL) abort();
+    } else {
+        tmp = sbo_mem;
+    }
+
+    // calculate memory locations
+    char *left = arr, *right = arr;
+    left += idx1 * elem_size;
+    right += idx2 * elem_size;
+
+    // three-way swap
+    memcpy(tmp, left, elem_size);
+    memcpy(left, right, elem_size);
+    memcpy(right, tmp, elem_size);
+
+    // free dynamic memory, if it was needed
+    if (tmp != sbo_mem) {
+        free(tmp);
+    }
+}
+
+// HIGH LEVEL ARRAY LIST FUNCTIONS
+
+typedef struct {
+    struct cx_list_s base;
+    void *data;
+    size_t capacity;
+    struct cx_array_reallocator_s reallocator;
+} cx_array_list;
+
+static void *cx_arl_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        struct cx_array_reallocator_s *alloc
+) {
+    // retrieve the pointer to the list allocator
+    const CxAllocator *al = alloc->ptr1;
+
+    // use the list allocator to reallocate the memory
+    return cxRealloc(al, array, capacity * elem_size);
+}
+
+static void cx_arl_destructor(struct cx_list_s *list) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    char *ptr = arl->data;
+
+    if (list->collection.simple_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+    if (list->collection.advanced_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+
+    cxFree(list->collection.allocator, arl->data);
+    cxFree(list->collection.allocator, list);
+}
+
+static size_t cx_arl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    // out of bounds and special case check
+    if (index > list->collection.size || n == 0) return 0;
+
+    // get a correctly typed pointer to the list
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // do we need to move some elements?
+    if (index < list->collection.size) {
+        const char *first_to_move = (const char *) arl->data;
+        first_to_move += index * list->collection.elem_size;
+        size_t elems_to_move = list->collection.size - index;
+        size_t start_of_moved = index + n;
+
+        if (CX_ARRAY_SUCCESS != cx_array_copy(
+                &arl->data,
+                &list->collection.size,
+                &arl->capacity,
+                start_of_moved,
+                first_to_move,
+                list->collection.elem_size,
+                elems_to_move,
+                &arl->reallocator
+        )) {
+            // if moving existing elems is unsuccessful, abort
+            return 0;
+        }
+    }
+
+    // note that if we had to move the elements, the following operation
+    // is guaranteed to succeed, because we have the memory already allocated
+    // therefore, it is impossible to leave this function with an invalid array
+
+    // place the new elements
+    if (CX_ARRAY_SUCCESS == cx_array_copy(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            index,
+            array,
+            list->collection.elem_size,
+            n,
+            &arl->reallocator
+    )) {
+        return n;
+    } else {
+        // array list implementation is "all or nothing"
+        return 0;
+    }
+}
+
+static size_t cx_arl_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    // get a correctly typed pointer to the list
+    cx_array_list *arl = (cx_array_list *) list;
+
+    if (CX_ARRAY_SUCCESS == cx_array_insert_sorted(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            list->collection.cmpfunc,
+            sorted_data,
+            list->collection.elem_size,
+            n,
+            &arl->reallocator
+    )) {
+        return n;
+    } else {
+        // array list implementation is "all or nothing"
+        return 0;
+    }
+}
+
+static int cx_arl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        const void *element
+) {
+    return 1 != cx_arl_insert_array(list, index, element, 1);
+}
+
+static int cx_arl_insert_iter(
+        struct cx_iterator_s *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    if (iter->index < list->collection.size) {
+        int result = cx_arl_insert_element(
+                list,
+                iter->index + 1 - prepend,
+                elem
+        );
+        if (result == 0) {
+            iter->elem_count++;
+            if (prepend != 0) {
+                iter->index++;
+                iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
+            }
+        }
+        return result;
+    } else {
+        int result = cx_arl_insert_element(list, list->collection.size, elem);
+        if (result == 0) {
+            iter->elem_count++;
+            iter->index = list->collection.size;
+        }
+        return result;
+    }
+}
+
+static int cx_arl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // out-of-bounds check
+    if (index >= list->collection.size) {
+        return 1;
+    }
+
+    // content destruction
+    cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size);
+
+    // short-circuit removal of last element
+    if (index == list->collection.size - 1) {
+        list->collection.size--;
+        return 0;
+    }
+
+    // just move the elements starting at index to the left
+    int result = cx_array_copy(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            index,
+            ((char *) arl->data) + (index + 1) * list->collection.elem_size,
+            list->collection.elem_size,
+            list->collection.size - index - 1,
+            &arl->reallocator
+    );
+
+    // cx_array_copy cannot fail, array cannot grow
+    assert(result == 0);
+
+    // decrease the size
+    list->collection.size--;
+
+    return 0;
+}
+
+static void cx_arl_clear(struct cx_list_s *list) {
+    if (list->collection.size == 0) return;
+
+    cx_array_list *arl = (cx_array_list *) list;
+    char *ptr = arl->data;
+
+    if (list->collection.simple_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+    if (list->collection.advanced_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+
+    memset(arl->data, 0, list->collection.size * list->collection.elem_size);
+    list->collection.size = 0;
+}
+
+static int cx_arl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->collection.size || j >= list->collection.size) return 1;
+    cx_array_list *arl = (cx_array_list *) list;
+    cx_array_swap(arl->data, list->collection.elem_size, i, j);
+    return 0;
+}
+
+static void *cx_arl_at(
+        const struct cx_list_s *list,
+        size_t index
+) {
+    if (index < list->collection.size) {
+        const cx_array_list *arl = (const cx_array_list *) list;
+        char *space = arl->data;
+        return space + index * list->collection.elem_size;
+    } else {
+        return NULL;
+    }
+}
+
+static ssize_t cx_arl_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    assert(list->collection.cmpfunc != NULL);
+    assert(list->collection.size < SIZE_MAX / 2);
+    char *cur = ((const cx_array_list *) list)->data;
+
+    for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) {
+        if (0 == list->collection.cmpfunc(elem, cur)) {
+            if (remove) {
+                if (0 == cx_arl_remove(list, i)) {
+                    return i;
+                } else {
+                    return -1;
+                }
+            } else {
+                return i;
+            }
+        }
+        cur += list->collection.elem_size;
+    }
+
+    return -1;
+}
+
+static void cx_arl_sort(struct cx_list_s *list) {
+    assert(list->collection.cmpfunc != NULL);
+    qsort(((cx_array_list *) list)->data,
+          list->collection.size,
+          list->collection.elem_size,
+          list->collection.cmpfunc
+    );
+}
+
+static int cx_arl_compare(
+        const struct cx_list_s *list,
+        const struct cx_list_s *other
+) {
+    assert(list->collection.cmpfunc != NULL);
+    if (list->collection.size == other->collection.size) {
+        const char *left = ((const cx_array_list *) list)->data;
+        const char *right = ((const cx_array_list *) other)->data;
+        for (size_t i = 0; i < list->collection.size; i++) {
+            int d = list->collection.cmpfunc(left, right);
+            if (d != 0) {
+                return d;
+            }
+            left += list->collection.elem_size;
+            right += other->collection.elem_size;
+        }
+        return 0;
+    } else {
+        return list->collection.size < other->collection.size ? -1 : 1;
+    }
+}
+
+static void cx_arl_reverse(struct cx_list_s *list) {
+    if (list->collection.size < 2) return;
+    void *data = ((const cx_array_list *) list)->data;
+    size_t half = list->collection.size / 2;
+    for (size_t i = 0; i < half; i++) {
+        cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i);
+    }
+}
+
+static bool cx_arl_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    const struct cx_list_s *list = iter->src_handle.c;
+    return iter->index < list->collection.size;
+}
+
+static void *cx_arl_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_arl_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        cx_arl_remove(iter->src_handle.m, iter->index);
+    } else {
+        iter->index++;
+        iter->elem_handle =
+                ((char *) iter->elem_handle)
+                + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
+    }
+}
+
+static void cx_arl_iter_prev(void *it) {
+    struct cx_iterator_s *iter = it;
+    const cx_array_list *list = iter->src_handle.c;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        cx_arl_remove(iter->src_handle.m, iter->index);
+    }
+    iter->index--;
+    if (iter->index < list->base.collection.size) {
+        iter->elem_handle = ((char *) list->data)
+                            + iter->index * list->base.collection.elem_size;
+    }
+}
+
+
+static struct cx_iterator_s cx_arl_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter;
+
+    iter.index = index;
+    iter.src_handle.c = list;
+    iter.elem_handle = cx_arl_at(list, index);
+    iter.elem_size = list->collection.elem_size;
+    iter.elem_count = list->collection.size;
+    iter.base.valid = cx_arl_iter_valid;
+    iter.base.current = cx_arl_iter_current;
+    iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    return iter;
+}
+
+static cx_list_class cx_array_list_class = {
+        cx_arl_destructor,
+        cx_arl_insert_element,
+        cx_arl_insert_array,
+        cx_arl_insert_sorted,
+        cx_arl_insert_iter,
+        cx_arl_remove,
+        cx_arl_clear,
+        cx_arl_swap,
+        cx_arl_at,
+        cx_arl_find_remove,
+        cx_arl_sort,
+        cx_arl_compare,
+        cx_arl_reverse,
+        cx_arl_iterator,
+};
+
+CxList *cxArrayListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size,
+        size_t initial_capacity
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_array_list_class;
+    list->base.collection.allocator = allocator;
+    list->capacity = initial_capacity;
+
+    if (elem_size > 0) {
+        list->base.collection.elem_size = elem_size;
+        list->base.collection.cmpfunc = comparator;
+    } else {
+        elem_size = sizeof(void *);
+        list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+        cxListStorePointers((CxList *) list);
+    }
+
+    // allocate the array after the real elem_size is known
+    list->data = cxCalloc(allocator, initial_capacity, elem_size);
+    if (list->data == NULL) {
+        cxFree(allocator, list);
+        return NULL;
+    }
+
+    // configure the reallocator
+    list->reallocator.realloc = cx_arl_realloc;
+    list->reallocator.ptr1 = (void *) allocator;
+
+    return (CxList *) list;
+}
--- a/ucx/avl.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,373 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/avl.h"
-
-#include <limits.h>
-
-#define ptrcast(ptr) ((void*)(ptr))
-#define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree))
-#define alloc_node(al) (UcxAVLNode*) almalloc((al), sizeof(UcxAVLNode))
-
-static void ucx_avl_connect(UcxAVLTree *tree,
-        UcxAVLNode *node, UcxAVLNode *child, intptr_t nullkey) {
-    if (child) {
-        child->parent = node;
-    }
-    // if child is NULL, nullkey decides if left or right pointer is cleared
-    if (tree->cmpfunc(
-        ptrcast(child ? child->key : nullkey),
-        ptrcast(node->key), tree->userdata) > 0) {
-      node->right = child;
-    } else {
-      node->left = child;
-    }
-    size_t lh = node->left ? node->left->height : 0;
-    size_t rh = node->right ? node->right->height : 0;
-    node->height = 1 + (lh > rh ? lh : rh);
-}
-
-#define avlheight(node) ((node) ? (node)->height : 0)
-
-static UcxAVLNode* avl_rotright(UcxAVLTree *tree, UcxAVLNode *l0) {
-    UcxAVLNode *p = l0->parent;
-    UcxAVLNode *l1 = l0->left;
-    if (p) {
-        ucx_avl_connect(tree, p, l1, 0);
-    } else {
-        l1->parent = NULL;
-    }
-    ucx_avl_connect(tree, l0, l1->right, l1->key);
-    ucx_avl_connect(tree, l1, l0, 0);
-    return l1;
-}
-
-static UcxAVLNode* avl_rotleft(UcxAVLTree *tree, UcxAVLNode *l0) {
-    UcxAVLNode *p = l0->parent;
-    UcxAVLNode *l1 = l0->right;
-    if (p) {
-        ucx_avl_connect(tree, p, l1, 0);
-    } else {
-        l1->parent = NULL;
-    }
-    ucx_avl_connect(tree, l0, l1->left, l1->key);
-    ucx_avl_connect(tree, l1, l0, 0);
-    return l1;
-}
-
-static void ucx_avl_balance(UcxAVLTree *tree, UcxAVLNode *n) {
-    int lh = avlheight(n->left);
-    int rh = avlheight(n->right);
-    n->height = 1 + (lh > rh ? lh : rh);
-    
-    if (lh - rh == 2) {
-      UcxAVLNode *c = n->left;
-      if (avlheight(c->right) - avlheight(c->left) == 1) {
-        avl_rotleft(tree, c);
-      }
-      n = avl_rotright(tree, n);
-    } else if (rh - lh == 2) {  
-      UcxAVLNode *c = n->right;
-      if (avlheight(c->left) - avlheight(c->right) == 1) {
-        avl_rotright(tree, c);
-      }
-      n = avl_rotleft(tree, n);
-    }
-
-    if (n->parent) {
-      ucx_avl_balance(tree, n->parent);
-    } else {
-      tree->root = n;
-    }
-}
-
-UcxAVLTree *ucx_avl_new(cmp_func cmpfunc) {
-    return ucx_avl_new_a(cmpfunc, ucx_default_allocator());
-}
-
-UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator) {
-    UcxAVLTree* tree = alloc_tree(allocator);
-    if (tree) {
-        tree->allocator = allocator;
-        tree->cmpfunc = cmpfunc;
-        tree->root = NULL;
-        tree->userdata = NULL;
-    }
-    
-    return tree;
-}
-
-static void ucx_avl_free_node(UcxAllocator *al, UcxAVLNode *node) {
-    if (node) {
-        ucx_avl_free_node(al, node->left);
-        ucx_avl_free_node(al, node->right);
-        alfree(al, node);
-    }
-}
-
-void ucx_avl_free(UcxAVLTree *tree) {
-    UcxAllocator *al = tree->allocator;
-    ucx_avl_free_node(al, tree->root);
-    alfree(al, tree);
-}
-
-static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node,
-        ucx_destructor destr) {
-    if (node) {
-        ucx_avl_free_content_node(al, node->left, destr);
-        ucx_avl_free_content_node(al, node->right, destr);
-        if (destr) {
-            destr(node->value);
-        } else {
-            alfree(al, node->value);
-        }
-    }
-}
-
-void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) {
-    ucx_avl_free_content_node(tree->allocator, tree->root, destr);
-}
-
-UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) {
-    UcxAVLNode *n = tree->root;
-    int cmpresult;
-    while (n && (cmpresult = tree->cmpfunc(
-            ptrcast(key), ptrcast(n->key), tree->userdata))) {
-        n = cmpresult > 0 ? n->right : n->left;
-    }
-    return n;
-}
-
-void *ucx_avl_get(UcxAVLTree *tree, intptr_t key) {
-    UcxAVLNode *n = ucx_avl_get_node(tree, key);
-    return n ? n->value : NULL;
-}
-
-UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
-        distance_func dfnc, int mode) {
-    UcxAVLNode *n = tree->root;
-    UcxAVLNode *closest = NULL;
-
-    intmax_t cmpresult;
-    intmax_t closest_dist;
-    closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX;
-    
-    while (n && (cmpresult = dfnc(
-            ptrcast(key), ptrcast(n->key), tree->userdata))) {
-        if (mode == UCX_AVL_FIND_CLOSEST) {
-            intmax_t dist = cmpresult;
-            if (dist < 0) dist *= -1;
-            if (dist < closest_dist) {
-                closest_dist = dist;
-                closest = n;
-            }
-        } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) {
-            if (cmpresult > closest_dist) {
-                closest_dist = cmpresult;
-                closest = n;
-            }
-        } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) {
-            if (cmpresult < closest_dist) {
-                closest_dist = cmpresult;
-                closest = n;
-            }
-        }
-        n = cmpresult > 0 ? n->right : n->left;
-    }
-    return n ? n : closest;
-}
-
-void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
-        distance_func dfnc, int mode) {
-    UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode);
-    return n ? n->value : NULL;
-}
-
-int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) {
-    return ucx_avl_put_s(tree, key, value, NULL);
-}
-
-int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value,
-        void **oldvalue) {
-    if (tree->root) {
-        UcxAVLNode *n = tree->root;
-        int cmpresult;
-        while ((cmpresult = tree->cmpfunc(
-                ptrcast(key), ptrcast(n->key), tree->userdata))) {
-            UcxAVLNode *m = cmpresult > 0 ? n->right : n->left;
-            if (m) {
-                n = m;
-            } else {
-                break;
-            }
-        }
-
-        if (cmpresult) {
-            UcxAVLNode* e = alloc_node(tree->allocator);
-            if (e) {
-                e->key = key; e->value = value; e->height = 1;
-                e->parent = e->left = e->right = NULL;
-                ucx_avl_connect(tree, n, e, 0);
-                ucx_avl_balance(tree, n);
-                return 0;
-            } else {
-                return 1;
-            }
-        } else {
-            if (oldvalue) {
-                *oldvalue = n->value;
-            }
-            n->value = value;
-            return 0;
-        }
-    } else {
-        tree->root = alloc_node(tree->allocator);
-        if (tree->root) {
-            tree->root->key = key; tree->root->value = value;
-            tree->root->height = 1;
-            tree->root->parent = tree->root->left = tree->root->right = NULL;
-            
-            if (oldvalue) {
-                *oldvalue = NULL;
-            }
-            
-            return 0;
-        } else {
-            return 1;
-        }
-    }
-}
-
-int ucx_avl_remove(UcxAVLTree *tree, intptr_t key) {
-    return ucx_avl_remove_s(tree, key, NULL, NULL);
-}
-    
-int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node) {
-    return ucx_avl_remove_s(tree, node->key, NULL, NULL);
-}
-
-int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
-        intptr_t *oldkey, void **oldvalue) {
-    
-    UcxAVLNode *n = tree->root;
-    int cmpresult;
-    while (n && (cmpresult = tree->cmpfunc(
-            ptrcast(key), ptrcast(n->key), tree->userdata))) {
-        n = cmpresult > 0 ? n->right : n->left;
-    }
-    if (n) {
-        if (oldkey) {
-            *oldkey = n->key;
-        }
-        if (oldvalue) {
-            *oldvalue = n->value;
-        }
-        
-        UcxAVLNode *p = n->parent;
-        if (n->left && n->right) {
-            UcxAVLNode *s = n->right;
-            while (s->left) {
-                s = s->left;
-            }
-            ucx_avl_connect(tree, s->parent, s->right, s->key);
-            n->key = s->key; n->value = s->value;
-            p = s->parent;
-            alfree(tree->allocator, s);
-        } else {
-            if (p) {
-                ucx_avl_connect(tree, p, n->right ? n->right:n->left, n->key);
-            } else {
-                tree->root = n->right ? n->right : n->left;
-                if (tree->root) {
-                    tree->root->parent = NULL;
-                }
-            }
-            alfree(tree->allocator, n);
-        }
-
-        if (p) {
-            ucx_avl_balance(tree, p);
-        }
-        
-        return 0;
-    } else {
-        return 1;
-    }
-}
-
-static size_t ucx_avl_countn(UcxAVLNode *node) {
-    if (node) {
-        return 1 + ucx_avl_countn(node->left) + ucx_avl_countn(node->right);
-    } else {
-        return 0;
-    }
-}
-
-size_t ucx_avl_count(UcxAVLTree *tree) {
-    return ucx_avl_countn(tree->root);
-}
-
-UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) {
-    if (node->left) {
-        UcxAVLNode* n = node->left;
-        while (n->right) {
-            n = n->right;
-        }
-        return n;
-    } else {
-        UcxAVLNode* n = node;
-        while (n->parent) {
-            if (n->parent->right == n) {
-                return n->parent;
-            } else {
-                n = n->parent;
-            }
-        }
-        return NULL;
-    }
-}
-
-UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) {
-    if (node->right) {
-        UcxAVLNode* n = node->right;
-        while (n->left) {
-            n = n->left;
-        }
-        return n;
-    } else {
-        UcxAVLNode* n = node;
-        while (n->parent) {
-            if (n->parent->left == n) {
-                return n->parent;
-            } else {
-                n = n->parent;
-            }
-        }
-        return NULL;
-    }
-}
--- a/ucx/buffer.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/buffer.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,90 +26,100 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/buffer.h"
+#include "cx/buffer.h"
+#include "cx/utils.h"
 
-#include <stdarg.h>
-#include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 
-UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags) {
-    UcxBuffer *buffer = (UcxBuffer*) malloc(sizeof(UcxBuffer));
-    if (buffer) {
-        buffer->flags = flags;
-        if (!space) {
-            buffer->space = (char*)malloc(capacity);
-            if (!buffer->space) {
-                free(buffer);
-                return NULL;
-            }
-            memset(buffer->space, 0, capacity);
-            buffer->flags |= UCX_BUFFER_AUTOFREE;
-        } else {
-            buffer->space = (char*)space;
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        const CxAllocator *allocator,
+        int flags
+) {
+    if (allocator == NULL) allocator = cxDefaultAllocator;
+    buffer->allocator = allocator;
+    buffer->flags = flags;
+    if (!space) {
+        buffer->bytes = cxMalloc(allocator, capacity);
+        if (buffer->bytes == NULL) {
+            return 1;
         }
-        buffer->capacity = capacity;
-        buffer->size = 0;
-
-        buffer->pos = 0;
+        buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+    } else {
+        buffer->bytes = space;
     }
+    buffer->capacity = capacity;
+    buffer->size = 0;
+    buffer->pos = 0;
 
-    return buffer;
-}
+    buffer->flush_func = NULL;
+    buffer->flush_target = NULL;
+    buffer->flush_blkmax = 0;
+    buffer->flush_blksize = 4096;
+    buffer->flush_threshold = SIZE_MAX;
 
-void ucx_buffer_free(UcxBuffer *buffer) {
-    if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) {
-        free(buffer->space);
-    }
-    free(buffer);
+    return 0;
 }
 
-UcxBuffer* ucx_buffer_extract(
-        UcxBuffer *src, size_t start, size_t length, int flags) {
-    if (src->size == 0 || length == 0 ||
-        ((size_t)-1) - start < length || start+length > src->capacity)
-    {
+void cxBufferDestroy(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
+    }
+}
+
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        const CxAllocator *allocator,
+        int flags
+) {
+    CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
+    if (buf == NULL) return NULL;
+    if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
+        return buf;
+    } else {
+        cxFree(allocator, buf);
         return NULL;
     }
+}
 
-    UcxBuffer *dst = (UcxBuffer*) malloc(sizeof(UcxBuffer));
-    if (dst) {
-        dst->space = (char*)malloc(length);
-        if (!dst->space) {
-            free(dst);
-            return NULL;
-        }
-        dst->capacity = length;
-        dst->size = length;
-        dst->flags = flags | UCX_BUFFER_AUTOFREE;
-        dst->pos = 0;
-        memcpy(dst->space, src->space+start, length);
+void cxBufferFree(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
     }
-    return dst;
+    cxFree(buffer->allocator, buffer);
 }
 
-int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) {
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+) {
     size_t npos;
     switch (whence) {
-    case SEEK_CUR:
-        npos = buffer->pos;
-        break;
-    case SEEK_END:
-        npos = buffer->size;
-        break;
-    case SEEK_SET:
-        npos = 0;
-        break;
-    default:
-        return -1;
+        case SEEK_CUR:
+            npos = buffer->pos;
+            break;
+        case SEEK_END:
+            npos = buffer->size;
+            break;
+        case SEEK_SET:
+            npos = 0;
+            break;
+        default:
+            return -1;
     }
 
     size_t opos = npos;
     npos += offset;
-    
+
     if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
         return -1;
     }
-    
+
     if (npos >= buffer->size) {
         return -1;
     } else {
@@ -119,135 +129,242 @@
 
 }
 
-int ucx_buffer_eof(UcxBuffer *buffer) {
+void cxBufferClear(CxBuffer *buffer) {
+    memset(buffer->bytes, 0, buffer->size);
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
+void cxBufferReset(CxBuffer *buffer) {
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
+int cxBufferEof(const CxBuffer *buffer) {
     return buffer->pos >= buffer->size;
 }
 
-int ucx_buffer_extend(UcxBuffer *buffer, size_t len) {
-    size_t newcap = buffer->capacity;
-    
-    if (buffer->capacity + len < buffer->capacity) {
-        return -1;
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t newcap
+) {
+    if (newcap <= buffer->capacity) {
+        return 0;
     }
-    
-    while (buffer->capacity + len > newcap) {
-        newcap <<= 1;
-        if (newcap < buffer->capacity) {
-            return -1;
-        }
-    }
-    
-    char *newspace = (char*)realloc(buffer->space, newcap);
-    if (newspace) {
-        memset(newspace+buffer->size, 0, newcap-buffer->size);
-        buffer->space = newspace;
+
+    if (cxReallocate(buffer->allocator,
+                     (void **) &buffer->bytes, newcap) == 0) {
         buffer->capacity = newcap;
+        return 0;
     } else {
         return -1;
     }
-    
-    return 0;
 }
 
-size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer) {
+/**
+ * Helps flushing data to the flush target of a buffer.
+ *
+ * @param buffer the buffer containing the config
+ * @param space the data to flush
+ * @param size the element size
+ * @param nitems the number of items
+ * @return the number of items flushed
+ */
+static size_t cx_buffer_write_flush_helper(
+        CxBuffer *buffer,
+        const unsigned char *space,
+        size_t size,
+        size_t nitems
+) {
+    size_t pos = 0;
+    size_t remaining = nitems;
+    size_t max_items = buffer->flush_blksize / size;
+    while (remaining > 0) {
+        size_t items = remaining > max_items ? max_items : remaining;
+        size_t flushed = buffer->flush_func(
+                space + pos,
+                size, items,
+                buffer->flush_target);
+        if (flushed > 0) {
+            pos += (flushed * size);
+            remaining -= flushed;
+        } else {
+            // if no bytes can be flushed out anymore, we give up
+            break;
+        }
+    }
+    return nitems - remaining;
+}
+
+size_t cxBufferWrite(
+        const void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    // optimize for easy case
+    if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+        memcpy(buffer->bytes + buffer->pos, ptr, nitems);
+        buffer->pos += nitems;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems;
+    }
+
     size_t len;
-    if(ucx_szmul(size, nitems, &len)) {
+    size_t nitems_out = nitems;
+    if (cx_szmul(size, nitems, &len)) {
         return 0;
     }
     size_t required = buffer->pos + len;
     if (buffer->pos > required) {
         return 0;
     }
-    
+
+    bool perform_flush = false;
     if (required > buffer->capacity) {
-        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
-            if (ucx_buffer_extend(buffer, required - buffer->capacity)) {
-                return 0;
+        if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) {
+            if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+                perform_flush = true;
+            } else {
+                if (cxBufferMinimumCapacity(buffer, required)) {
+                    return 0;
+                }
             }
         } else {
-            len = buffer->capacity - buffer->pos;
-            if (size > 1) {
-                len -= len%size;
+            if (buffer->flush_blkmax > 0) {
+                perform_flush = true;
+            } else {
+                // truncate data to be written, if we can neither extend nor flush
+                len = buffer->capacity - buffer->pos;
+                if (size > 1) {
+                    len -= len % size;
+                }
+                nitems_out = len / size;
             }
         }
     }
-    
+
     if (len == 0) {
         return len;
     }
-    
-    memcpy(buffer->space + buffer->pos, ptr, len);
-    buffer->pos += len;
-    if(buffer->pos > buffer->size) {
-        buffer->size = buffer->pos;
+
+    if (perform_flush) {
+        size_t flush_max;
+        if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
+            return 0;
+        }
+        size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
+                           ? buffer->pos
+                           : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
+        if (flush_pos == buffer->pos) {
+            // entire buffer has been flushed, we can reset
+            buffer->size = buffer->pos = 0;
+
+            size_t items_flush; // how many items can also be directly flushed
+            size_t items_keep; // how many items have to be written to the buffer
+
+            items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
+            if (items_flush > 0) {
+                items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
+                // in case we could not flush everything, keep the rest
+            }
+            items_keep = nitems - items_flush;
+            if (items_keep > 0) {
+                // try again with the remaining stuff
+                const unsigned char *new_ptr = ptr;
+                new_ptr += items_flush * size;
+                // report the directly flushed items as written plus the remaining stuff
+                return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
+            } else {
+                // all items have been flushed - report them as written
+                return nitems;
+            }
+        } else if (flush_pos == 0) {
+            // nothing could be flushed at all, we immediately give up without writing any data
+            return 0;
+        } else {
+            // we were partially successful, we shift left and try again
+            cxBufferShiftLeft(buffer, flush_pos);
+            return cxBufferWrite(ptr, size, nitems, buffer);
+        }
+    } else {
+        memcpy(buffer->bytes + buffer->pos, ptr, len);
+        buffer->pos += len;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems_out;
     }
-    
-    return len / size;
+
 }
 
-size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer) {
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+) {
+    c &= 0xFF;
+    unsigned char const ch = c;
+    if (cxBufferWrite(&ch, 1, 1, buffer) == 1) {
+        return c;
+    } else {
+        return EOF;
+    }
+}
+
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+) {
+    return cxBufferWrite(str, 1, strlen(str), buffer);
+}
+
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
     size_t len;
-    if(ucx_szmul(size, nitems, &len)) {
+    if (cx_szmul(size, nitems, &len)) {
         return 0;
     }
     if (buffer->pos + len > buffer->size) {
         len = buffer->size - buffer->pos;
-        if (size > 1) len -= len%size;
+        if (size > 1) len -= len % size;
     }
-    
+
     if (len <= 0) {
         return len;
     }
-    
-    memcpy(ptr, buffer->space + buffer->pos, len);
+
+    memcpy(ptr, buffer->bytes + buffer->pos, len);
     buffer->pos += len;
-    
+
     return len / size;
 }
 
-int ucx_buffer_putc(UcxBuffer *buffer, int c) {
-    if(buffer->pos >= buffer->capacity) {
-        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
-            if(ucx_buffer_extend(buffer, 1)) {
-                return EOF;
-            }
-        } else {
-            return EOF;
-        }
-    }
-    
-    c &= 0xFF;
-    buffer->space[buffer->pos] = (char) c;
-    buffer->pos++;
-    if(buffer->pos > buffer->size) {
-        buffer->size = buffer->pos;
-    }
-    return c;
-}
-
-int ucx_buffer_getc(UcxBuffer *buffer) {
-    if (ucx_buffer_eof(buffer)) {
+int cxBufferGet(CxBuffer *buffer) {
+    if (cxBufferEof(buffer)) {
         return EOF;
     } else {
-        int c = ((unsigned char*)buffer->space)[buffer->pos];
+        int c = buffer->bytes[buffer->pos];
         buffer->pos++;
         return c;
     }
 }
 
-size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) {
-    return ucx_buffer_write((const void*)str, 1, strlen(str), buffer);
-}
-
-int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) {
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+) {
     if (shift >= buffer->size) {
         buffer->pos = buffer->size = 0;
     } else {
-        memmove(buffer->space, buffer->space + shift, buffer->size - shift);
+        memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
         buffer->size -= shift;
-        
+
         if (buffer->pos >= shift) {
             buffer->pos -= shift;
         } else {
@@ -257,14 +374,17 @@
     return 0;
 }
 
-int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) {
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+) {
     size_t req_capacity = buffer->size + shift;
     size_t movebytes;
-    
+
     // auto extend buffer, if required and enabled
     if (buffer->capacity < req_capacity) {
-        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
-            if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) {
+        if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+            if (cxBufferMinimumCapacity(buffer, req_capacity)) {
                 return 1;
             }
             movebytes = buffer->size;
@@ -274,23 +394,26 @@
     } else {
         movebytes = buffer->size;
     }
-    
-    memmove(buffer->space + shift, buffer->space, movebytes);
-    buffer->size = shift+movebytes;
-    
+
+    memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+    buffer->size = shift + movebytes;
+
     buffer->pos += shift;
     if (buffer->pos > buffer->size) {
         buffer->pos = buffer->size;
     }
-    
+
     return 0;
 }
 
-int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) {
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+) {
     if (shift < 0) {
-        return ucx_buffer_shift_left(buffer, (size_t) (-shift));
+        return cxBufferShiftLeft(buffer, (size_t) (-shift));
     } else if (shift > 0) {
-        return ucx_buffer_shift_right(buffer, (size_t) shift);
+        return cxBufferShiftRight(buffer, (size_t) shift);
     } else {
         return 0;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/compare.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,213 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/compare.h"
+
+#include <math.h>
+
+int cx_cmp_int(const void *i1, const void *i2) {
+    int a = *((const int *) i1);
+    int b = *((const int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longint(const void *i1, const void *i2) {
+    long int a = *((const long int *) i1);
+    long int b = *((const long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longlong(const void *i1, const void *i2) {
+    long long a = *((const long long *) i1);
+    long long b = *((const long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int16(const void *i1, const void *i2) {
+    int16_t a = *((const int16_t *) i1);
+    int16_t b = *((const int16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int32(const void *i1, const void *i2) {
+    int32_t a = *((const int32_t *) i1);
+    int32_t b = *((const int32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int64(const void *i1, const void *i2) {
+    int64_t a = *((const int64_t *) i1);
+    int64_t b = *((const int64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint(const void *i1, const void *i2) {
+    unsigned int a = *((const unsigned int *) i1);
+    unsigned int b = *((const unsigned int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulongint(const void *i1, const void *i2) {
+    unsigned long int a = *((const unsigned long int *) i1);
+    unsigned long int b = *((const unsigned long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulonglong(const void *i1, const void *i2) {
+    unsigned long long a = *((const unsigned long long *) i1);
+    unsigned long long b = *((const unsigned long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint16(const void *i1, const void *i2) {
+    uint16_t a = *((const uint16_t *) i1);
+    uint16_t b = *((const uint16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint32(const void *i1, const void *i2) {
+    uint32_t a = *((const uint32_t *) i1);
+    uint32_t b = *((const uint32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint64(const void *i1, const void *i2) {
+    uint64_t a = *((const uint64_t *) i1);
+    uint64_t b = *((const uint64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_float(const void *f1, const void *f2) {
+    float a = *((const float *) f1);
+    float b = *((const float *) f2);
+    if (fabsf(a - b) < 1e-6f) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_double(
+        const void *d1,
+        const void *d2
+) {
+    double a = *((const double *) d1);
+    double b = *((const double *) d2);
+    if (fabs(a - b) < 1e-14) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_intptr(
+        const void *ptr1,
+        const void *ptr2
+) {
+    intptr_t p1 = *(const intptr_t *) ptr1;
+    intptr_t p2 = *(const intptr_t *) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
+
+int cx_cmp_uintptr(
+        const void *ptr1,
+        const void *ptr2
+) {
+    uintptr_t p1 = *(const uintptr_t *) ptr1;
+    uintptr_t p2 = *(const uintptr_t *) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
+
+int cx_cmp_ptr(
+        const void *ptr1,
+        const void *ptr2
+) {
+    uintptr_t p1 = (uintptr_t) ptr1;
+    uintptr_t p2 = (uintptr_t) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/allocator.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,242 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file allocator.h
+ * Interface for custom allocators.
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define UCX_ALLOCATOR_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The class definition for an allocator.
+ */
+typedef struct {
+    /**
+     * The allocator's malloc() implementation.
+     */
+    void *(*malloc)(
+            void *data,
+            size_t n
+    );
+
+    /**
+     * The allocator's realloc() implementation.
+     */
+    __attribute__((__warn_unused_result__))
+    void *(*realloc)(
+            void *data,
+            void *mem,
+            size_t n
+    );
+
+    /**
+     * The allocator's calloc() implementation.
+     */
+    void *(*calloc)(
+            void *data,
+            size_t nelem,
+            size_t n
+    );
+
+    /**
+     * The allocator's free() implementation.
+     */
+    __attribute__((__nonnull__))
+    void (*free)(
+            void *data,
+            void *mem
+    );
+} cx_allocator_class;
+
+/**
+ * Structure holding the data for an allocator.
+ */
+struct cx_allocator_s {
+    /**
+     * A pointer to the instance of the allocator class.
+     */
+    cx_allocator_class *cl;
+    /**
+     * A pointer to the data this allocator uses.
+     */
+    void *data;
+};
+
+/**
+ * High-Level type alias for the allocator type.
+ */
+typedef struct cx_allocator_s CxAllocator;
+
+/**
+ * A default allocator using standard library malloc() etc.
+ */
+extern CxAllocator *cxDefaultAllocator;
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param memory a pointer to the object to destruct
+  */
+__attribute__((__nonnull__))
+typedef void (*cx_destructor_func)(void *memory);
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param data an optional pointer to custom data
+ * @param memory a pointer to the object to destruct
+  */
+__attribute__((__nonnull__(2)))
+typedef void (*cx_destructor_func2)(
+        void *data,
+        void *memory
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ *
+ * \par Error handling
+ * \c errno will be set by realloc() on failure.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cx_reallocate(
+        void **mem,
+        size_t n
+);
+
+/**
+ * Allocate \p n bytes of memory.
+ *
+ * @param allocator the allocator
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2)))
+void *cxMalloc(
+        const CxAllocator *allocator,
+        size_t n
+);
+
+/**
+ * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving the memory
+ * was not necessary.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param n the new size in bytes
+ * @return a pointer to the re-allocated memory
+ */
+__attribute__((__warn_unused_result__))
+__attribute__((__alloc_size__(3)))
+void *cxRealloc(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t n
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxReallocate(
+        const CxAllocator *allocator,
+        void **mem,
+        size_t n
+);
+
+/**
+ * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
+ *
+ * @param allocator the allocator
+ * @param nelem the number of elements
+ * @param n the size of each element in bytes
+ * @return a pointer to the allocated memory
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2, 3)))
+void *cxCalloc(
+        const CxAllocator *allocator,
+        size_t nelem,
+        size_t n
+);
+
+/**
+ * Free a block allocated by this allocator.
+ *
+ * \note Freeing a block of a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem a pointer to the block to free
+ */
+__attribute__((__nonnull__))
+void cxFree(
+        const CxAllocator *allocator,
+        void *mem
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ALLOCATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/array_list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,461 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file array_list.h
+ * \brief Array list implementation.
+ * \details Also provides several low-level functions for custom array list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_ARRAY_LIST_H
+#define UCX_ARRAY_LIST_H
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum item size in an array list that fits into stack buffer when swapped.
+ */
+extern unsigned cx_array_swap_sbo_size;
+
+/**
+ * Declares variables for an array that can be used with the convenience macros.
+ *
+ * @see cx_array_simple_add()
+ * @see cx_array_simple_copy()
+ * @see cx_array_initialize()
+ * @see cx_array_simple_add_sorted()
+ * @see cx_array_simple_insert_sorted()
+ */
+#define CX_ARRAY_DECLARE(type, name) \
+    type * name;                     \
+    size_t name##_size;              \
+    size_t name##_capacity
+
+/**
+ * Initializes an array declared with CX_ARRAY_DECLARE().
+ *
+ * The memory for the array is allocated with stdlib malloc().
+ * @param array the array
+ * @param capacity the initial capacity
+ */
+#define cx_array_initialize(array, capacity) \
+        array##_capacity = capacity; \
+        array##_size = 0; \
+        array = malloc(sizeof(array[0]) * capacity)
+
+/**
+ * Defines a reallocation mechanism for arrays.
+ */
+struct cx_array_reallocator_s {
+    /**
+     * Reallocates space for the given array.
+     *
+     * Implementations are not required to free the original array.
+     * This allows reallocation of static memory by allocating heap memory
+     * and copying the array contents. The information in the custom fields of
+     * the referenced allocator can be used to track the state of the memory
+     * or to transport other additional data.
+     *
+     * @param array the array to reallocate
+     * @param capacity the new capacity (number of elements)
+     * @param elem_size the size of each element
+     * @param alloc a reference to this allocator
+     * @return a pointer to the reallocated memory or \c NULL on failure
+     */
+    void *(*realloc)(
+            void *array,
+            size_t capacity,
+            size_t elem_size,
+            struct cx_array_reallocator_s *alloc
+    );
+
+    /**
+     * Custom data pointer.
+     */
+    void *ptr1;
+    /**
+     * Custom data pointer.
+     */
+    void *ptr2;
+    /**
+     * Custom data integer.
+     */
+    size_t int1;
+    /**
+     * Custom data integer.
+     */
+    size_t int2;
+};
+
+/**
+ * A default stdlib-based array reallocator.
+ */
+extern struct cx_array_reallocator_s *cx_array_default_reallocator;
+
+/**
+ * Return codes for array functions.
+ */
+enum cx_array_result {
+    CX_ARRAY_SUCCESS,
+    CX_ARRAY_REALLOC_NOT_SUPPORTED,
+    CX_ARRAY_REALLOC_FAILED,
+};
+
+/**
+ * Copies elements from one array to another.
+ *
+ * The elements are copied to the \p target array at the specified \p index,
+ * overwriting possible elements. The \p index does not need to be in range of
+ * the current array \p size. If the new index plus the number of elements added
+ * would extend the array's size, and \p capacity is not \c NULL, the remaining
+ * capacity is used.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made, unless the \p reallocator is set to \c NULL, in which case
+ * this function ultimately returns a failure.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity -
+ * \c NULL if only the size shall be used to bound the array (reallocations
+ * will NOT be supported in that case)
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to copy
+ * @param reallocator the array reallocator to use, or \c NULL
+ * if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__(1, 2, 5)))
+enum cx_array_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        const void *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param count the number of elements to copy
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_copy(array, index, src, count) \
+    cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
+    index, src, sizeof((array)[0]), count, cx_array_default_reallocator)
+
+/**
+ * Adds an element to an array with the possibility of allocating more space.
+ *
+ * The element \p elem is added to the end of the \p target array which containing
+ * \p size elements, already. The \p capacity must not be \c NULL and point a
+ * variable holding the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, and the optional
+ * \p reallocator is not \c NULL, an attempt increase the \p capacity is made
+ * and the new capacity is written back.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity - must not be \c NULL
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
+    cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro that uses cx_array_add() with a default layout and
+ * the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add(array, elem) \
+    cx_array_simple_copy(array, array##_size, &(elem), 1)
+
+
+/**
+ * Inserts a sorted array into another sorted array.
+ *
+ * If either the target or the source array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param cmp_func the compare function for the elements
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to insert
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__))
+enum cx_array_result cx_array_insert_sorted(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        cx_compare_func cmp_func,
+        const void *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Inserts an element into a sorted array.
+ *
+ * If the target array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
+    cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro for cx_array_add_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add_sorted(array, elem, cmp_func) \
+    cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
+        sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator)
+
+/**
+ * Convenience macro for cx_array_insert_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param src pointer to the source array
+ * @param n number of elements in the source array
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
+    cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
+        cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator)
+
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * 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.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the largest lower bound, or \p size
+ */
+__attribute__((__nonnull__))
+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
+);
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the element in the array, or \p size if the element
+ * cannot be found
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search(
+        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(
+            arr, size, elem_size, elem, cmp_func
+    );
+    if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
+        return index;
+    } else {
+        return size;
+    }
+}
+
+/**
+ * Searches the smallest upper bound in a sorted array.
+ *
+ * In other words, this function returns the index of the smallest element
+ * 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.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the smallest upper bound, or \p size
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search_sup(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+) {
+    size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func);
+    if (inf == size) {
+        // no infimum means, first element is supremum
+        return 0;
+    } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
+        return inf;
+    } else {
+        return inf + 1;
+    }
+}
+
+/**
+ * Swaps two array elements.
+ *
+ * @param arr the array
+ * @param elem_size the element size
+ * @param idx1 index of first element
+ * @param idx2 index of second element
+ */
+__attribute__((__nonnull__))
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @param allocator the allocator for allocating the list memory
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+CxList *cxArrayListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size,
+        size_t initial_capacity
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_size bytes each.
+ *
+ * The list will use the cxDefaultAllocator and \em NO compare function.
+ * If you want to call functions that need a compare function, you have to
+ * set it immediately after creation or use cxArrayListCreate().
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+#define cxArrayListCreateSimple(elem_size, initial_capacity) \
+    cxArrayListCreate(NULL, NULL, elem_size, initial_capacity)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ARRAY_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/buffer.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,464 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file buffer.h
+ *
+ * \brief Advanced buffer implementation.
+ *
+ * Instances of CxBuffer can be used to read from or to write to like one
+ * would do with a stream.
+ *
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_BUFFER_H
+#define UCX_BUFFER_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define CX_BUFFER_DEFAULT 0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ */
+#define CX_BUFFER_FREE_CONTENTS 0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define CX_BUFFER_AUTO_EXTEND 0x02
+
+/** Structure for the UCX buffer data. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    union {
+        /**
+         * Data is interpreted as text.
+         */
+        char *space;
+        /**
+         * Data is interpreted as binary.
+         */
+        unsigned char *bytes;
+    };
+    /** The allocator to use for automatic memory management. */
+    const CxAllocator *allocator;
+    /** Current position of the buffer. */
+    size_t pos;
+    /** Current capacity (i.e. maximum size) of the buffer. */
+    size_t capacity;
+    /** Current size of the buffer content. */
+    size_t size;
+    /**
+     * The buffer may not extend beyond this threshold before starting to flush.
+     * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
+     */
+    size_t flush_threshold;
+    /**
+     * The block size for the elements to flush.
+     * Default is 4096 bytes.
+     */
+    size_t flush_blksize;
+    /**
+     * The maximum number of blocks to flush in one cycle.
+     * Zero disables flushing entirely (this is the default).
+     * Set this to \c SIZE_MAX to flush the entire buffer.
+     *
+     * @attention if the maximum number of blocks multiplied with the block size
+     * is smaller than the expected contents written to this buffer within one write
+     * operation, multiple flush cycles are performed after that write.
+     * That means the total number of blocks flushed after one write to this buffer may
+     * be larger than \c flush_blkmax.
+     */
+    size_t flush_blkmax;
+
+    /**
+     * The write function used for flushing.
+     * If NULL, the flushed content gets discarded.
+     */
+    cx_write_func flush_func;
+
+    /**
+     * The target for \c flush_func.
+     */
+    void *flush_target;
+
+    /**
+     * Flag register for buffer features.
+     * @see #CX_BUFFER_DEFAULT
+     * @see #CX_BUFFER_FREE_CONTENTS
+     * @see #CX_BUFFER_AUTO_EXTEND
+     */
+    int flags;
+} cx_buffer_s;
+
+/**
+ * UCX buffer.
+ */
+typedef cx_buffer_s CxBuffer;
+
+/**
+ * Initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param buffer the buffer to initialize
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator this buffer shall use for automatic
+ * memory management. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return zero on success, non-zero if a required allocation failed
+ */
+__attribute__((__nonnull__(1)))
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        const CxAllocator *allocator,
+        int flags
+);
+
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return a pointer to the buffer on success, \c NULL if a required allocation failed
+ */
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        const CxAllocator *allocator,
+        int flags
+);
+
+/**
+ * Destroys the buffer contents.
+ *
+ * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled.
+ * If you want to free the memory of the entire buffer, use cxBufferFree().
+ *
+ * @param buffer the buffer which contents shall be destroyed
+ * @see cxBufferInit()
+ */
+__attribute__((__nonnull__))
+void cxBufferDestroy(CxBuffer *buffer);
+
+/**
+ * Deallocates the buffer.
+ *
+ * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
+ * the contents. If you \em only want to destroy the contents, use cxBufferDestroy().
+ *
+ * @param buffer the buffer to deallocate
+ * @see cxBufferCreate()
+ */
+__attribute__((__nonnull__))
+void cxBufferFree(CxBuffer *buffer);
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ *
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ *
+ * If the offset is negative, the contents are shifted to the left where the
+ * first \p shift bytes are discarded.
+ * The new size of the buffer is the old size minus the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ *
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ *
+ * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ *
+ * \attention
+ * Security Note: The shifting operation does \em not erase the previously occupied memory cells.
+ * But you can easily do that manually, e.g. by calling
+ * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
+ * for a left shift.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+__attribute__((__nonnull__))
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+);
+
+/**
+ * Shifts the buffer to the right.
+ * See cxBufferShift() for details.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+/**
+ * Shifts the buffer to the left.
+ * See cxBufferShift() for details.
+ *
+ * \note Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ *
+ * @param buffer the buffer
+ * @param shift the positive shift offset
+ * @return always zero
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+
+/**
+ * Moves the position of the buffer.
+ *
+ * The new position is relative to the \p whence argument.
+ *
+ * \li \c SEEK_SET marks the start of the buffer.
+ * \li \c SEEK_CUR marks the current position.
+ * \li \c SEEK_END marks the end of the buffer.
+ *
+ * With an offset of zero, this function sets the buffer position to zero
+ * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position
+ * unchanged (\c SEEK_CUR).
+ *
+ * @param buffer the buffer
+ * @param offset position offset relative to \p whence
+ * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+__attribute__((__nonnull__))
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ *
+ * The data is deleted by zeroing it with a call to memset().
+ * If you do not need that, you can use the faster cxBufferReset().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferReset()
+ */
+__attribute__((__nonnull__))
+void cxBufferClear(CxBuffer *buffer);
+
+/**
+ * Resets the buffer by resetting the position and size to zero.
+ *
+ * The data in the buffer is not deleted. If you need a safe
+ * reset of the buffer, use cxBufferClear().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferClear()
+ */
+__attribute__((__nonnull__))
+void cxBufferReset(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer size.
+ *
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * byte of the buffer's contents.
+ */
+__attribute__((__nonnull__))
+int cxBufferEof(const CxBuffer *buffer);
+
+
+/**
+ * Ensures that the buffer has a minimum capacity.
+ *
+ * If the current capacity is not sufficient, the buffer will be extended.
+ *
+ * @param buffer the buffer
+ * @param capacity the minimum required capacity for this buffer
+ * @return 0 on success or a non-zero value on failure
+ */
+__attribute__((__nonnull__))
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t capacity
+);
+
+/**
+ * Writes data to a CxBuffer.
+ *
+ * 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.
+ * The number returned by this function is the total number of elements that
+ * could be written during the process. It does not necessarily mean that those
+ * elements are still in this buffer, because some of them could have also be
+ * flushed already.
+ *
+ * If automatic flushing is not enabled, the position of the buffer is increased
+ * by the number of bytes written.
+ *
+ * \note The signature is compatible with the fwrite() family of functions.
+ *
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to write to
+ * @return the total count of elements written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferWrite(
+        const void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+);
+
+/**
+ * Reads data from a CxBuffer.
+ *
+ * The position of the buffer is increased by the number of bytes read.
+ *
+ * \note The signature is compatible with the fread() family of functions.
+ *
+ * @param ptr a pointer to the memory area where to store the read data
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to read from
+ * @return the total number of elements read
+ */
+__attribute__((__nonnull__))
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+);
+
+/**
+ * Writes a character to a buffer.
+ *
+ * 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 buffer extension fails, \c EOF is returned.
+ *
+ * On successful write, the position of the buffer is increased.
+ *
+ * @param buffer the buffer to write to
+ * @param c the character to write
+ * @return the byte that has bean written or \c EOF when the end of the stream is
+ * reached and automatic extension is not enabled or not possible
+ */
+__attribute__((__nonnull__))
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+);
+
+/**
+ * Writes a string to a buffer.
+ *
+ * @param buffer the buffer
+ * @param str the zero-terminated string
+ * @return the number of bytes written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+);
+
+/**
+ * Gets a character from a buffer.
+ *
+ * The current position of the buffer is increased after a successful read.
+ *
+ * @param buffer the buffer to read from
+ * @return the character or \c EOF, if the end of the buffer is reached
+ */
+__attribute__((__nonnull__))
+int cxBufferGet(CxBuffer *buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_BUFFER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/collection.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,164 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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.
+ */
+/**
+ * \file collection.h
+ * \brief Common definitions for various collection implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COLLECTION_H
+#define UCX_COLLECTION_H
+
+#include "allocator.h"
+#include "iterator.h"
+#include "compare.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Special constant used for creating collections that are storing pointers.
+ */
+#define CX_STORE_POINTERS 0
+
+/**
+ * Base attributes of a collection.
+ */
+struct cx_collection_s {
+    /**
+     * The allocator to use.
+     */
+    const CxAllocator *allocator;
+    /**
+     * The comparator function for the elements.
+     */
+    cx_compare_func cmpfunc;
+    /**
+     * The size of each element.
+     */
+    size_t elem_size;
+    /**
+     * The number of currently stored elements.
+     */
+    size_t size;
+    /**
+     * An optional simple destructor for the collection's elements.
+     *
+     * @attention Read the documentation of the particular collection implementation
+     * whether this destructor shall only destroy the contents or also free the memory.
+     */
+    cx_destructor_func simple_destructor;
+    /**
+     * An optional advanced destructor for the collection's elements.
+     *
+     * @attention Read the documentation of the particular collection implementation
+     * whether this destructor shall only destroy the contents or also free the memory.
+     */
+    cx_destructor_func2 advanced_destructor;
+    /**
+     * The pointer to additional data that is passed to the advanced destructor.
+     */
+    void *destructor_data;
+    /**
+     * Indicates if this list is supposed to store pointers
+     * instead of copies of the actual objects.
+     */
+    bool store_pointer;
+};
+
+/**
+ * Use this macro to declare common members for a collection structure.
+ */
+#define CX_COLLECTION_BASE struct cx_collection_s collection
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineDestructor(c, destr) \
+    (c)->collection.simple_destructor = (cx_destructor_func) destr
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineAdvancedDestructor(c, destr, data) \
+    (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \
+    (c)->collection.destructor_data = data
+
+/**
+ * Invokes the simple destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_simple_destructor(c, e) \
+    (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e))
+
+/**
+ * Invokes the advanced destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_advanced_destructor(c, e) \
+    (c)->collection.advanced_destructor((c)->collection.destructor_data, \
+    (c)->collection.store_pointer ? (*((void **) (e))) : (e))
+
+
+/**
+ * Invokes all available destructor functions for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_destructor(c, e) \
+    if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
+    if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_COLLECTION_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/common.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,142 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, 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 UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   1
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+// Common Includes
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifndef UCX_TEST_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        const void *,
+        size_t,
+        size_t,
+        void *
+);
+#endif // UCX_TEST_H
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+        void *,
+        size_t,
+        size_t,
+        void *
+);
+
+
+// Compiler specific stuff
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#ifdef _MSC_VER
+
+// fix missing ssize_t definition
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+
+// fix missing _Thread_local support
+#define _Thread_local __declspec(thread)
+
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/common.h.orig	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, 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 UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   0
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+#define __attribute__(...) 
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        void const *,
+        size_t,
+        size_t,
+        void *
+);
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+        void *,
+        size_t,
+        size_t,
+        void *
+);
+
+#ifdef _WIN32
+
+#ifdef __MINGW32__
+#include <sys/types.h>
+#endif // __MINGW32__
+
+#else // !_WIN32
+
+#include <sys/types.h>
+
+#endif // _WIN32
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/compare.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,243 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file compare.h
+ * \brief A collection of simple compare functions.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COMPARE_H
+#define UCX_COMPARE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef CX_COMPARE_FUNC_DEFINED
+#define CX_COMPARE_FUNC_DEFINED
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+        const void *left,
+        const void *right
+);
+#endif // CX_COMPARE_FUNC_DEFINED
+
+/**
+ * Compares two integers of type int.
+ *
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long int.
+ *
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long long.
+ *
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longlong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int64(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned int.
+ *
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long int.
+ *
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulongint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long long.
+ *
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulonglong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint64(const void *i1, const void *i2);
+
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int cx_cmp_float(const void *f1, const void *f2);
+
+/**
+ * Compares two real numbers of type double with precision 1e-14.
+ *
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int cx_cmp_double(
+        const void *d1,
+        const void *d2
+);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const intptr_t*)
+ * @param ptr2 pointer to pointer two (const intptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_intptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const uintptr_t*)
+ * @param ptr2 pointer to pointer two (const uintptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_uintptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+/**
+ * Compares the pointers specified in the arguments without de-referencing.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int cx_cmp_ptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_COMPARE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_key.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,128 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file hash_key.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_HASH_KEY_H
+#define UCX_HASH_KEY_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for a key within a hash map. */
+struct cx_hash_key_s {
+    /** The key data. */
+    const void *data;
+    /**
+     * The key data length.
+     */
+    size_t len;
+    /** The hash value of the key data. */
+    unsigned hash;
+};
+
+/**
+ * Type for a hash key.
+ */
+typedef struct cx_hash_key_s CxHashKey;
+
+/**
+ * Computes a murmur2 32 bit hash.
+ *
+ * You need to initialize \c data and \c len in the key struct.
+ * The hash is then directly written to that struct.
+ *
+ * \note If \c data is \c NULL, the hash is defined as 1574210520.
+ *
+ * @param key the key, the hash shall be computed for
+ */
+void cx_hash_murmur(CxHashKey *key);
+
+/**
+ * Computes a hash key from a string.
+ *
+ * The string needs to be zero-terminated.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_str(const char *str);
+
+/**
+ * Computes a hash key from a byte array.
+ *
+ * @param bytes the array
+ * @param len the length
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_bytes(
+        const unsigned char *bytes,
+        size_t len
+);
+
+/**
+ * Computes a hash key for an arbitrary object.
+ *
+ * The computation uses the in-memory representation that might not be
+ * the same on different platforms. Therefore, this hash should not be
+ * used for data exchange with different machines.
+ *
+ * @param obj a pointer to an arbitrary object
+ * @param len the length of object in memory
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key(
+        const void *obj,
+        size_t len
+);
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_KEY_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_map.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,133 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file hash_map.h
+ * \brief Hash map implementation.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_HASH_MAP_H
+#define UCX_HASH_MAP_H
+
+#include "map.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for an element of a hash map. */
+struct cx_hash_map_element_s;
+
+/**
+ * Internal structure for a hash map.
+ */
+struct cx_hash_map_s {
+    /**
+     * Base structure for maps.
+     */
+    struct cx_map_s base;
+    /**
+     * The buckets of this map, each containing a linked list of elements.
+     */
+    struct cx_hash_map_element_s **buckets;
+    /**
+     * The number of buckets.
+     */
+    size_t bucket_count;
+};
+
+
+/**
+ * Creates a new hash map with the specified number of buckets.
+ *
+ * If \p buckets is zero, an implementation defined default will be used.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param allocator the allocator to use
+ * @param itemsize the size of one element
+ * @param buckets the initial number of buckets in this hash map
+ * @return a pointer to the new hash map
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMap *cxHashMapCreate(
+        const CxAllocator *allocator,
+        size_t itemsize,
+        size_t buckets
+);
+
+/**
+ * Creates a new hash map with a default number of buckets.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param itemsize the size of one element
+ * @return a pointer to the new hash map
+ */
+#define cxHashMapCreateSimple(itemsize) \
+    cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+
+/**
+ * Increases the number of buckets, if necessary.
+ *
+ * The load threshold is \c 0.75*buckets. If the element count exceeds the load
+ * threshold, the map will be rehashed. Otherwise, no action is performed and
+ * this function simply returns 0.
+ *
+ * The rehashing process ensures, that the number of buckets is at least
+ * 2.5 times the element count. So there is enough room for additional
+ * elements without the need of another soon rehashing.
+ *
+ * You can use this function after filling a map to increase access performance.
+ *
+ * @note If the specified map is not a hash map, the behavior is undefined.
+ *
+ * @param map the map to rehash
+ * @return zero on success, non-zero if a memory allocation error occurred
+ */
+__attribute__((__nonnull__))
+int cxMapRehash(CxMap *map);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/iterator.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,265 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file iterator.h
+ * \brief Interface for iterator implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_ITERATOR_H
+#define UCX_ITERATOR_H
+
+#include "common.h"
+
+struct cx_iterator_base_s {
+    /**
+     * True iff the iterator points to valid data.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*valid)(const void *);
+
+    /**
+     * Returns a pointer to the current element.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current)(const void *);
+
+    /**
+     * Original implementation in case the function needs to be wrapped.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current_impl)(const void *);
+
+    /**
+     * Advances the iterator.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void (*next)(void *);
+    /**
+     * Indicates whether this iterator may remove elements.
+     */
+    bool mutating;
+    /**
+     * Internal flag for removing the current element when advancing.
+     */
+    bool remove;
+};
+
+/**
+ * Declares base attributes for an iterator.
+ * Must be the first member of an iterator structure.
+ */
+#define CX_ITERATOR_BASE struct cx_iterator_base_s base
+
+/**
+ * Internal iterator struct - use CxIterator.
+ */
+struct cx_iterator_s {
+    CX_ITERATOR_BASE;
+
+    /**
+     * Handle for the current element.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    union {
+        /**
+         * Access for mutating iterators.
+         */
+        void *m;
+        /**
+         * Access for normal iterators.
+         */
+        const void *c;
+    } src_handle;
+
+    /**
+     * Field for storing a key-value pair.
+     * May be used by iterators that iterate over k/v-collections.
+     */
+    struct {
+        /**
+         * A pointer to the key.
+         */
+        const void *key;
+        /**
+         * A pointer to the value.
+         */
+        void *value;
+    } kv_data;
+
+    /**
+     * Field for storing a slot number.
+     * May be used by iterators that iterate over multi-bucket collections.
+     */
+    size_t slot;
+
+    /**
+     * If the iterator is position-aware, contains the index of the element in the underlying collection.
+     * Otherwise, this field is usually uninitialized.
+     */
+    size_t index;
+
+    /**
+     * The size of an individual element.
+     */
+    size_t elem_size;
+
+    /**
+     * May contain the total number of elements, if known.
+     * Shall be set to \c SIZE_MAX when the total number is unknown during iteration.
+     */
+    size_t elem_count;
+};
+
+/**
+ * Iterator type.
+ *
+ * An iterator points to a certain element in a (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
+ * any concurrent mutation of the collection other than by this iterator makes this iterator invalid
+ * and it must not be used anymore.
+ */
+typedef struct cx_iterator_s CxIterator;
+
+/**
+ * Checks if the iterator points to valid data.
+ *
+ * This is especially false for past-the-end iterators.
+ *
+ * @param iter the iterator
+ * @return true iff the iterator points to valid data
+ */
+#define cxIteratorValid(iter) (iter).base.valid(&(iter))
+
+/**
+ * Returns a pointer to the current element.
+ *
+ * The behavior is undefined if this iterator is invalid.
+ *
+ * @param iter the iterator
+ * @return a pointer to the current element
+ */
+#define cxIteratorCurrent(iter) (iter).base.current(&iter)
+
+/**
+ * Advances the iterator to the next element.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorNext(iter) (iter).base.next(&iter)
+
+/**
+ * Flags the current element for removal, if this iterator is mutating.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
+
+/**
+ * Obtains a reference to an arbitrary iterator.
+ *
+ * This is useful for APIs that expect some iterator as an argument.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorRef(iter) &((iter).base)
+
+/**
+ * Loops over an iterator.
+ * @param type the type of the elements
+ * @param elem the name of the iteration variable
+ * @param iter the iterator
+ */
+#define cx_foreach(type, elem, iter) \
+for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter))
+
+
+/**
+ * Creates an iterator for the specified plain array.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxIterator(
+        const void *array,
+        size_t elem_size,
+        size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain array.
+ *
+ * While the iterator is in use, the array may only be altered by removing
+ * elements through #cxIteratorFlagRemoval(). Every other change to the array
+ * will bring this iterator to an undefined state.
+ *
+ * When \p remove_keeps_order is set to \c false, removing an element will only
+ * move the last element to the position of the removed element, instead of
+ * moving all subsequent elements by one. Usually, when the order of elements is
+ * not important, this parameter should be set to \c false.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order \c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxMutIterator(
+        void *array,
+        size_t elem_size,
+        size_t elem_count,
+        bool remove_keeps_order
+);
+
+#endif // UCX_ITERATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/linked_list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,506 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file linked_list.h
+ * \brief Linked list implementation.
+ * \details Also provides several low-level functions for custom linked list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LINKED_LIST_H
+#define UCX_LINKED_LIST_H
+
+#include "common.h"
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum item size that uses SBO swap instead of relinking.
+ */
+extern unsigned cx_linked_list_swap_sbo_size;
+
+/**
+ * Allocates a linked list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @param allocator the allocator for allocating the list nodes
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+CxList *cxLinkedListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size
+);
+
+/**
+ * Allocates a linked list for storing elements with \p elem_size bytes each.
+ *
+ * The list will use cxDefaultAllocator and no comparator function. If you want
+ * to call functions that need a comparator, you must either set one immediately
+ * after list creation or use cxLinkedListCreate().
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+#define cxLinkedListCreateSimple(elem_size) \
+    cxLinkedListCreate(NULL, NULL, elem_size)
+
+/**
+ * Finds the node at a certain index.
+ *
+ * This function can be used to start at an arbitrary position within the list.
+ * If the search index is large than the start index, \p loc_advance must denote
+ * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * But it is also possible that the search index is smaller than the start index
+ * (e.g. in cases where traversing a list backwards is faster) in which case
+ * \p loc_advance must denote the location of some sort of \c prev pointer
+ * (i.e. a pointer to the previous node).
+ *
+ * @param start a pointer to the start node
+ * @param start_index the start index
+ * @param loc_advance the location of the pointer to advance
+ * @param index the search index
+ * @return the node found at the specified index
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_at(
+        const void *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+);
+
+/**
+ * Finds the index of an element within a linked list.
+ *
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find(
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+);
+
+/**
+ * Finds the node containing an element within a linked list.
+ *
+ * @param result a pointer to the memory where the node pointer (or \c NULL if the element
+ * could not be found) shall be stored to
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find_node(
+        void **result,
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+);
+
+/**
+ * Finds the first node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a prev pointer whose location within the node struct is
+ * denoted by \p loc_prev.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_prev the location of the \c prev pointer
+ * @return a pointer to the first node
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_first(
+        const void *node,
+        ptrdiff_t loc_prev
+);
+
+/**
+ * Finds the last node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a next pointer whose location within the node struct is
+ * denoted by \p loc_next.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_next the location of the \c next pointer
+ * @return a pointer to the last node
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_last(
+        const void *node,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Finds the predecessor of a node in case it is not linked.
+ *
+ * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ *
+ * @param begin the node where to start the search
+ * @param loc_next the location of the \c next pointer
+ * @param node the successor of the node to find
+ * @return the node or \c NULL if \p node has no predecessor
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_prev(
+        const void *begin,
+        ptrdiff_t loc_next,
+        const void *node
+);
+
+/**
+ * Adds a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be appended
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+);
+
+/**
+ * Prepends a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be prepended
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+);
+
+/**
+ * Links two nodes.
+ *
+ * @param left the new predecessor of \p right
+ * @param right the new successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks two nodes.
+ *
+ * If right is not the successor of left, the behavior is undefined.
+ *
+ * @param left the predecessor of \p right
+ * @param right the successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Inserts a new node after a given node of a linked list.
+ * The new node must not be part of any list already.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
+ * @param new_node a pointer to the node that shall be inserted
+ */
+__attribute__((__nonnull__(6)))
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+);
+
+/**
+ * Inserts a chain of nodes after a given node of a linked list.
+ * The chain must not be part of any list already.
+ *
+ * If you do not explicitly specify the end of the chain, it will be determined by traversing
+ * the \c next pointer.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need
+ * to provide a valid \p loc_prev location.
+ * Then the chain will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL to prepend the chain to the list)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
+ */
+__attribute__((__nonnull__(6)))
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+);
+
+/**
+ * Inserts a node into a sorted linked list.
+ * The new node must not be part of any list already.
+ *
+ * If the list starting with the node pointed to by \p begin is not sorted
+ * already, the behavior is undefined.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node,
+        cx_compare_func cmp_func
+);
+
+/**
+ * Inserts a chain of nodes into a sorted linked list.
+ * The chain must not be part of any list already.
+ *
+ * If either the list starting with the node pointed to by \p begin or the list
+ * starting with \p insert_begin is not sorted, the behavior is undefined.
+ *
+ * \attention In contrast to cx_linked_list_insert_chain(), the source chain
+ * will be broken and inserted into the target list so that the resulting list
+ * will be sorted according to \p cmp_func. That means, each node in the source
+ * chain may be re-linked with nodes from the target list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *insert_begin,
+        cx_compare_func cmp_func
+);
+
+/**
+ * Removes a node from the linked list.
+ *
+ * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * addresses are provided, the pointers are adjusted accordingly.
+ *
+ * The following combinations of arguments are valid (more arguments are optional):
+ * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ *
+ * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used
+ * to traverse to a former adjacent node in the list.
+ *
+ * @param begin a pointer to the begin node pointer (optional)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node to remove
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+);
+
+
+/**
+ * Determines the size of a linked list starting with \p node.
+ * @param node the first node
+ * @param loc_next the location of the \c next pointer within the node struct
+ * @return the size of the list or zero if \p node is \c NULL
+ */
+size_t cx_linked_list_size(
+        const void *node,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Sorts a linked list based on a comparison function.
+ *
+ * This function can work with linked lists of the following structure:
+ * \code
+ * typedef struct node node;
+ * struct node {
+ *   node* prev;
+ *   node* next;
+ *   my_payload data;
+ * }
+ * \endcode
+ *
+ * @note This is a recursive function with at most logarithmic recursion depth.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the compare function defining the sort order
+ */
+__attribute__((__nonnull__(1, 6)))
+void cx_linked_list_sort(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+);
+
+
+/**
+ * Compares two lists element wise.
+ *
+ * \note Both list must have the same structure.
+ *
+ * @param begin_left the begin of the left list (\c NULL denotes an empty list)
+ * @param begin_right the begin of the right list  (\c NULL denotes an empty list)
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the function to compare the elements
+ * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the
+ * right list, positive if the left list is larger than the right list, zero if both lists are equal.
+ */
+__attribute__((__nonnull__(5)))
+int cx_linked_list_compare(
+        const void *begin_left,
+        const void *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+);
+
+/**
+ * Reverses the order of the nodes in a linked list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__(1)))
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LINKED_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,800 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file list.h
+ * \brief Interface for list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LIST_H
+#define UCX_LIST_H
+
+#include "common.h"
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * List class type.
+ */
+typedef struct cx_list_class_s cx_list_class;
+
+/**
+ * Structure for holding the base data of a list.
+ */
+struct cx_list_s {
+    CX_COLLECTION_BASE;
+    /**
+     * The list class definition.
+     */
+    const cx_list_class *cl;
+    /**
+     * The actual implementation in case the list class is delegating.
+     */
+    const cx_list_class *climpl;
+};
+
+/**
+ * The class definition for arbitrary lists.
+ */
+struct cx_list_class_s {
+    /**
+     * Destructor function.
+     *
+     * Implementations SHALL invoke the content destructor functions if provided
+     * and SHALL deallocate the list memory.
+     */
+    void (*destructor)(struct cx_list_s *list);
+
+    /**
+     * Member function for inserting a single element.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     */
+    int (*insert_element)(
+            struct cx_list_s *list,
+            size_t index,
+            const void *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     * @see cx_list_default_insert_array()
+     */
+    size_t (*insert_array)(
+            struct cx_list_s *list,
+            size_t index,
+            const void *data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting sorted elements into a sorted list.
+     *
+     * @see cx_list_default_insert_sorted()
+     */
+    size_t (*insert_sorted)(
+            struct cx_list_s *list,
+            const void *sorted_data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting an element relative to an iterator position.
+     */
+    int (*insert_iter)(
+            struct cx_iterator_s *iter,
+            const void *elem,
+            int prepend
+    );
+
+    /**
+     * Member function for removing an element.
+     */
+    int (*remove)(
+            struct cx_list_s *list,
+            size_t index
+    );
+
+    /**
+     * Member function for removing all elements.
+     */
+    void (*clear)(struct cx_list_s *list);
+
+    /**
+     * Member function for swapping two elements.
+     * @see cx_list_default_swap()
+     */
+    int (*swap)(
+            struct cx_list_s *list,
+            size_t i,
+            size_t j
+    );
+
+    /**
+     * Member function for element lookup.
+     */
+    void *(*at)(
+            const struct cx_list_s *list,
+            size_t index
+    );
+
+    /**
+     * Member function for finding and optionally removing an element.
+     */
+    ssize_t (*find_remove)(
+            struct cx_list_s *list,
+            const void *elem,
+            bool remove
+    );
+
+    /**
+     * Member function for sorting the list in-place.
+     * @see cx_list_default_sort()
+     */
+    void (*sort)(struct cx_list_s *list);
+
+    /**
+     * Optional member function for comparing this list
+     * to another list of the same type.
+     * If set to \c NULL, comparison won't be optimized.
+     */
+    int (*compare)(
+            const struct cx_list_s *list,
+            const struct cx_list_s *other
+    );
+
+    /**
+     * Member function for reversing the order of the items.
+     */
+    void (*reverse)(struct cx_list_s *list);
+
+    /**
+     * Member function for returning an iterator pointing to the specified index.
+     */
+    struct cx_iterator_s (*iterator)(
+            const struct cx_list_s *list,
+            size_t index,
+            bool backward
+    );
+};
+
+/**
+ * Default implementation of an array insert.
+ *
+ * This function uses the element insert function for each element of the array.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param index the index where to insert the data
+ * @param data a pointer to the array of data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *data,
+        size_t n
+);
+
+/**
+ * Default implementation of a sorted insert.
+ *
+ * This function uses the array insert function to insert consecutive groups
+ * of sorted data.
+ *
+ * The source data \em must already be sorted wrt. the list's compare function.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param sorted_data a pointer to the array of pre-sorted data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+);
+
+/**
+ * Default unoptimized sort implementation.
+ *
+ * This function will copy all data to an array, sort the array with standard
+ * qsort, and then copy the data back to the list memory.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list that shall be sorted
+ */
+__attribute__((__nonnull__))
+void cx_list_default_sort(struct cx_list_s *list);
+
+/**
+ * Default unoptimized swap implementation.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list in which to swap
+ * @param i index of one element
+ * @param j index of the other element
+ * @return zero on success, non-zero when indices are out of bounds or memory
+ * allocation for the temporary buffer fails
+ */
+__attribute__((__nonnull__))
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+
+/**
+ * Common type for all list implementations.
+ */
+typedef struct cx_list_s CxList;
+
+/**
+ * Advises the list to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this list will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param list the list
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+void cxListStoreObjects(CxList *list);
+
+/**
+ * Advises the list to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty list that already stores copies of
+ * objects is undefined.
+ *
+ * @param list the list
+ * @see cxListStoreObjects()
+ */
+__attribute__((__nonnull__))
+void cxListStorePointers(CxList *list);
+
+/**
+ * Returns true, if this list is storing pointers instead of the actual data.
+ *
+ * @param list
+ * @return true, if this list is storing pointers
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxListIsStoringPointers(const CxList *list) {
+    return list->collection.store_pointer;
+}
+
+/**
+ * Returns the number of elements currently stored in the list.
+ *
+ * @param list the list
+ * @return the number of currently stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListSize(const CxList *list) {
+    return list->collection.size;
+}
+
+/**
+ * Adds an item to the end of the list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListAddArray()
+ */
+__attribute__((__nonnull__))
+static inline int cxListAdd(
+        CxList *list,
+        const void *elem
+) {
+    return list->cl->insert_element(list, list->collection.size, elem);
+}
+
+/**
+ * Adds multiple items to the end of the list.
+ *
+ * This method is more efficient than invoking cxListAdd() multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListAddArray(
+        CxList *list,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, list->collection.size, array, n);
+}
+
+/**
+ * Inserts an item at the specified index.
+ *
+ * If \p index equals the list \c size, this is effectively cxListAdd().
+ *
+ * @param list the list
+ * @param index the index the element shall have
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * or when the index is out of bounds
+ * @see cxListInsertAfter()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsert(
+        CxList *list,
+        size_t index,
+        const void *elem
+) {
+    return list->cl->insert_element(list, index, elem);
+}
+
+/**
+ * Inserts an item into a sorted list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertSorted(
+        CxList *list,
+        const void *elem
+) {
+    const void *data = list->collection.store_pointer ? &elem : elem;
+    return list->cl->insert_sorted(list, data, 1) == 0;
+}
+
+/**
+ * Inserts multiple items to the list at the specified index.
+ * If \p index equals the list size, this is effectively cxListAddArray().
+ *
+ * This method is usually more efficient than invoking cxListInsert()
+ * multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param index the index where to add the elements
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertArray(
+        CxList *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, index, array, n);
+}
+
+/**
+ * Inserts a sorted array into a sorted list.
+ *
+ * This method is usually more efficient than inserting each element separately,
+ * because consecutive chunks of sorted data are inserted in one pass.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertSortedArray(
+        CxList *list,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_sorted(list, array, n);
+}
+
+/**
+ * Inserts an element after the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertAfter(
+        CxIterator *iter,
+        const void *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 0);
+}
+
+/**
+ * Inserts an element before the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertAfter()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertBefore(
+        CxIterator *iter,
+        const void *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 1);
+}
+
+/**
+ * Removes the element at the specified index.
+ *
+ * If an element destructor function is specified, it is called before
+ * removing the element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return zero on success, non-zero if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListRemove(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->remove(list, index);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If an element destructor function is specified, it is called for each
+ * element before removing them.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListClear(CxList *list) {
+    list->cl->clear(list);
+}
+
+/**
+ * Swaps two items in the list.
+ *
+ * Implementations should only allocate temporary memory for the swap, if
+ * it is necessary.
+ *
+ * @param list the list
+ * @param i the index of the first element
+ * @param j the index of the second element
+ * @return zero on success, non-zero if one of the indices is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListSwap(
+        CxList *list,
+        size_t i,
+        size_t j
+) {
+    return list->cl->swap(list, i, j);
+}
+
+/**
+ * Returns a pointer to the element at the specified index.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return a pointer to the element or \c NULL if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline void *cxListAt(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->at(list, index);
+}
+
+/**
+ * Returns an iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIteratorAt(
+        const CxList *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, false);
+}
+
+/**
+ * Returns a backwards iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIteratorAt(
+        const CxList *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, true);
+}
+
+/**
+ * Returns a mutating iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns a mutating backwards iterator pointing to the item at the
+ * specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns an iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIterator(const CxList *list) {
+    return list->cl->iterator(list, 0, false);
+}
+
+/**
+ * Returns a mutating iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListMutIterator(CxList *list) {
+    return cxListMutIteratorAt(list, 0);
+}
+
+
+/**
+ * Returns a backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIterator(const CxList *list) {
+    return list->cl->iterator(list, list->collection.size - 1, true);
+}
+
+/**
+ * Returns a mutating backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListMutBackwardsIterator(CxList *list) {
+    return cxListMutBackwardsIteratorAt(list, list->collection.size - 1);
+}
+
+/**
+ * Returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find
+ * @return the index of the element or a negative
+ * value when the element is not found
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFind(
+        const CxList *list,
+        const void *elem
+) {
+    return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+/**
+ * Removes and returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find and remove
+ * @return the index of the now removed element or a negative
+ * value when the element is not found or could not be removed
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFindRemove(
+        CxList *list,
+        const void *elem
+) {
+    return list->cl->find_remove(list, elem, true);
+}
+
+/**
+ * Sorts the list in-place.
+ *
+ * \remark The underlying sort algorithm is implementation defined.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListSort(CxList *list) {
+    list->cl->sort(list);
+}
+
+/**
+ * Reverses the order of the items.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListReverse(CxList *list) {
+    list->cl->reverse(list);
+}
+
+/**
+ * Compares a list to another list of the same type.
+ *
+ * First, the list sizes are compared.
+ * If they match, the lists are compared element-wise.
+ *
+ * @param list the list
+ * @param other the list to compare to
+ * @return zero, if both lists are equal element wise,
+ * negative if the first list is smaller, positive if the first list is larger
+ */
+__attribute__((__nonnull__))
+int cxListCompare(
+        const CxList *list,
+        const CxList *other
+);
+
+/**
+ * Deallocates the memory of the specified list structure.
+ *
+ * Also calls content a destructor function, depending on the configuration
+ * in CxList.content_destructor_type.
+ *
+ * This function itself is a destructor function for the CxList.
+ *
+ * @param list the list which shall be destroyed
+ */
+__attribute__((__nonnull__))
+void cxListDestroy(CxList *list);
+
+/**
+ * A shared instance of an empty list.
+ *
+ * Writing to that list is undefined.
+ */
+extern CxList * const cxEmptyList;
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/map.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,1158 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file map.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MAP_H
+#define UCX_MAP_H
+
+#include "common.h"
+#include "collection.h"
+#include "string.h"
+#include "hash_key.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/** Type for the UCX map. */
+typedef struct cx_map_s CxMap;
+
+/** Type for a map entry. */
+typedef struct cx_map_entry_s CxMapEntry;
+
+/** Type for map class definitions. */
+typedef struct cx_map_class_s cx_map_class;
+
+/** Structure for the UCX map. */
+struct cx_map_s {
+    /**
+     * Base attributes.
+     */
+    CX_COLLECTION_BASE;
+    /** The map class definition. */
+    cx_map_class *cl;
+};
+
+/**
+ * The type of iterator for a map.
+ */
+enum cx_map_iterator_type {
+    /**
+     * Iterates over key/value pairs.
+     */
+    CX_MAP_ITERATOR_PAIRS,
+    /**
+     * Iterates over keys only.
+     */
+    CX_MAP_ITERATOR_KEYS,
+    /**
+     * Iterates over values only.
+     */
+    CX_MAP_ITERATOR_VALUES
+};
+
+/**
+ * The class definition for arbitrary maps.
+ */
+struct cx_map_class_s {
+    /**
+     * Deallocates the entire memory.
+     */
+    __attribute__((__nonnull__))
+    void (*destructor)(struct cx_map_s *map);
+
+    /**
+     * Removes all elements.
+     */
+    __attribute__((__nonnull__))
+    void (*clear)(struct cx_map_s *map);
+
+    /**
+     * Add or overwrite an element.
+     */
+    __attribute__((__nonnull__))
+    int (*put)(
+            CxMap *map,
+            CxHashKey key,
+            void *value
+    );
+
+    /**
+     * Returns an element.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    void *(*get)(
+            const CxMap *map,
+            CxHashKey key
+    );
+
+    /**
+     * Removes an element.
+     */
+    __attribute__((__nonnull__))
+    void *(*remove)(
+            CxMap *map,
+            CxHashKey key,
+            bool destroy
+    );
+
+    /**
+     * Creates an iterator for this map.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type);
+};
+
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+    /**
+     * A pointer to the key.
+     */
+    const CxHashKey *key;
+    /**
+     * A pointer to the value.
+     */
+    void *value;
+};
+
+/**
+ * A shared instance of an empty map.
+ *
+ * Writing to that map is undefined.
+ */
+extern CxMap *const cxEmptyMap;
+
+/**
+ * Advises the map to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this map will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param map the map
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStoreObjects(CxMap *map) {
+    map->collection.store_pointer = false;
+}
+
+/**
+ * Advises the map to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty map that already stores copies of
+ * objects is undefined.
+ *
+ * @param map the map
+ * @see cxMapStoreObjects()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStorePointers(CxMap *map) {
+    map->collection.store_pointer = true;
+    map->collection.elem_size = sizeof(void *);
+}
+
+/**
+ * Returns true, if this map is storing pointers instead of the actual data.
+ *
+ * @param map
+ * @return true, if this map is storing pointers
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxMapIsStoringPointers(const CxMap *map) {
+    return map->collection.store_pointer;
+}
+
+/**
+ * Deallocates the memory of the specified map.
+ *
+ * @param map the map to be destroyed
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDestroy(CxMap *map) {
+    map->cl->destructor(map);
+}
+
+
+/**
+ * Clears a map by removing all elements.
+ *
+ * @param map the map to be cleared
+ */
+__attribute__((__nonnull__))
+static inline void cxMapClear(CxMap *map) {
+    map->cl->clear(map);
+}
+
+/**
+ * Returns the number of elements in this map.
+ *
+ * @param map the map
+ * @return the number of stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxMapSize(const CxMap *map) {
+    return map->collection.size;
+}
+
+
+// TODO: set-like map operations (union, intersect, difference)
+
+/**
+ * Creates a value iterator for a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorValues(const CxMap *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+}
+
+/**
+ * Creates a key iterator for a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorKeys(const CxMap *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+}
+
+/**
+ * Creates an iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapIteratorKeys()
+ * @see cxMapIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIterator(const CxMap *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+}
+
+
+/**
+ * Creates a mutating iterator over the values of a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIteratorValues(CxMap *map);
+
+/**
+ * Creates a mutating iterator over the keys of a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIteratorKeys(CxMap *map);
+
+/**
+ * Creates a mutating iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapMutIteratorKeys()
+ * @see cxMapMutIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIterator(CxMap *map);
+
+#ifdef __cplusplus
+} // end the extern "C" block here, because we want to start overloading
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        CxHashKey const &key,
+        void *value
+) {
+    return map->cl->put(map, key, value);
+}
+
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxstring const &key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxmutstr const &key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        const char *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        const CxMap *map,
+        CxHashKey const &key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        const CxMap *map,
+        cxstring const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        const CxMap *map,
+        cxmutstr const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        const CxMap *map,
+        const char *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        CxHashKey const &key
+) {
+    (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        cxstring const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        const char *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        CxHashKey const &key
+) {
+    (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        cxstring const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        const char *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        const char *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer);
+}
+
+#else // __cplusplus
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    return map->cl->put(map, key, value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_cxstr(
+        CxMap *map,
+        cxstring key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_mustr(
+        CxMap *map,
+        cxmutstr key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_str(
+        CxMap *map,
+        const char *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+#define cxMapPut(map, key, value) _Generic((key), \
+    CxHashKey: cx_map_put,                        \
+    cxstring: cx_map_put_cxstr,                   \
+    cxmutstr: cx_map_put_mustr,                   \
+    char*: cx_map_put_str,                        \
+    const char*: cx_map_put_str)                  \
+    (map, key, value)
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get(
+        const CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_cxstr(
+        const CxMap *map,
+        cxstring key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_mustr(
+        const CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_str(
+        const CxMap *map,
+        const char *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+#define cxMapGet(map, key) _Generic((key), \
+    CxHashKey: cx_map_get,                 \
+    cxstring: cx_map_get_cxstr,            \
+    cxmutstr: cx_map_get_mustr,            \
+    char*: cx_map_get_str,                 \
+    const char*: cx_map_get_str)           \
+    (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_str(
+        CxMap *map,
+        const char *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+#define cxMapRemove(map, key) _Generic((key), \
+    CxHashKey: cx_map_remove,                 \
+    cxstring: cx_map_remove_cxstr,            \
+    cxmutstr: cx_map_remove_mustr,            \
+    char*: cx_map_remove_str,                 \
+    const char*: cx_map_remove_str)           \
+    (map, key)
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_str(
+        CxMap *map,
+        const char *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+#define cxMapDetach(map, key) _Generic((key), \
+    CxHashKey: cx_map_detach,                 \
+    cxstring: cx_map_detach_cxstr,            \
+    cxmutstr: cx_map_detach_mustr,            \
+    char*: cx_map_detach_str,                 \
+    const char*: cx_map_detach_str)           \
+    (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_str(
+        CxMap *map,
+        const char *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+#define cxMapRemoveAndGet(map, key) _Generic((key), \
+    CxHashKey: cx_map_remove_and_get,               \
+    cxstring: cx_map_remove_and_get_cxstr,          \
+    cxmutstr: cx_map_remove_and_get_mustr,          \
+    char*: cx_map_remove_and_get_str,               \
+    const char*: cx_map_remove_and_get_str)         \
+    (map, key)
+
+#endif // __cplusplus
+
+#endif // UCX_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/mempool.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,148 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file mempool.h
+ * \brief Interface for memory pool implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define UCX_MEMPOOL_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for pooled memory. */
+struct cx_mempool_memory_s;
+
+/**
+ * The basic structure of a memory pool.
+ * Should be the first member of an actual memory pool implementation.
+ */
+struct cx_mempool_s {
+    /** The provided allocator. */
+    const CxAllocator *allocator;
+
+    /**
+     * A destructor that shall be automatically registered for newly allocated memory.
+     * This destructor MUST NOT free the memory.
+     */
+    cx_destructor_func auto_destr;
+
+    /** Array of pooled memory. */
+    struct cx_mempool_memory_s **data;
+
+    /** Number of pooled memory items. */
+    size_t size;
+
+    /** Memory pool capacity. */
+    size_t capacity;
+};
+
+/**
+ * Common type for all memory pool implementations.
+ */
+typedef struct cx_mempool_s CxMempool;
+
+/**
+ * Creates an array-based memory pool with a shared destructor function.
+ *
+ * This destructor MUST NOT free the memory.
+ *
+ * @param capacity the initial capacity of the pool
+ * @param destr the destructor function to use for allocated memory
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
+
+/**
+ * Creates a basic array-based memory pool.
+ *
+ * @param capacity the initial capacity of the pool
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+static inline CxMempool *cxBasicMempoolCreate(size_t capacity) {
+    return cxMempoolCreate(capacity, NULL);
+}
+
+/**
+ * Destroys a memory pool and frees the managed memory.
+ *
+ * @param pool the memory pool to destroy
+ */
+__attribute__((__nonnull__))
+void cxMempoolDestroy(CxMempool *pool);
+
+/**
+ * Sets the destructor function for a specific allocated memory object.
+ *
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ * @param fnc the destructor function
+ */
+__attribute__((__nonnull__))
+void cxMempoolSetDestructor(
+        void *memory,
+        cx_destructor_func fnc
+);
+
+/**
+ * Registers foreign memory with this pool.
+ *
+ * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ *
+ * A small portion of memory will be allocated to register the information in the pool.
+ * If that allocation fails, this function will return non-zero.
+ *
+ * @param pool the pool
+ * @param memory the object allocated in the pool
+ * @param destr the destructor function
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxMempoolRegister(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func destr
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_MEMPOOL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/printf.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,335 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file printf.h
+ * \brief Wrapper for write functions with a printf-like interface.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PRINTF_H
+#define UCX_PRINTF_H
+
+#include "common.h"
+#include "string.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * The maximum string length that fits into stack memory.
+ */
+extern unsigned const cx_printf_sbo_size;
+
+/**
+ * A \c fprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ */
+__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        const char *fmt,
+        ...
+);
+
+/**
+ * A \c vfprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ap argument list
+ * @return the total number of bytes written
+ * @see cx_fprintf()
+ */
+__attribute__((__nonnull__))
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        const char *fmt,
+        va_list ap
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree_a()
+ */
+__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cxmutstr cx_asprintf_a(
+        const CxAllocator *allocator,
+        const char *fmt,
+        ...
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree()
+ */
+#define cx_asprintf(fmt, ...) \
+    cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf_a()
+ */
+__attribute__((__nonnull__))
+cxmutstr cx_vasprintf_a(
+        const CxAllocator *allocator,
+        const char *fmt,
+        va_list ap
+);
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf()
+ */
+#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap)
+
+/**
+ * A \c printf like function which writes the output to a CxBuffer.
+ *
+ * @param buffer a pointer to the buffer the data is written to
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
+    (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5)))
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... );
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap);
+
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * 
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... );
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_PRINTF_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/string.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,1082 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file string.h
+ * \brief Strings that know their length.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STRING_H
+#define UCX_STRING_H
+
+#include "common.h"
+#include "allocator.h"
+
+/**
+ * The maximum length of the "needle" in cx_strstr() that can use SBO.
+ */
+extern unsigned const cx_strstr_sbo_size;
+
+/**
+ * The UCX string structure.
+ */
+struct cx_mutstr_s {
+    /**
+     * A pointer to the string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * A mutable string.
+ */
+typedef struct cx_mutstr_s cxmutstr;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+struct cx_string_s {
+    /**
+     * A pointer to the immutable string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    const char *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * An immutable string.
+ */
+typedef struct cx_string_s cxstring;
+
+/**
+ * Context for string tokenizing.
+ */
+struct cx_strtok_ctx_s {
+    /**
+     * The string to tokenize.
+     */
+    cxstring str;
+    /**
+     * The primary delimiter.
+     */
+    cxstring delim;
+    /**
+     * Optional array of more delimiters.
+     */
+    const cxstring *delim_more;
+    /**
+     * Length of the array containing more delimiters.
+     */
+    size_t delim_more_count;
+    /**
+     * Position of the currently active token in the source string.
+     */
+    size_t pos;
+    /**
+     * Position of next delimiter in the source string.
+     *
+     * If the tokenizer has not yet returned a token, the content of this field
+     * is undefined. If the tokenizer reached the end of the string, this field
+     * contains the length of the source string.
+     */
+    size_t delim_pos;
+    /**
+     * The position of the next token in the source string.
+     */
+    size_t next_pos;
+    /**
+     * The number of already found tokens.
+     */
+    size_t found;
+    /**
+     * The maximum number of tokens that shall be returned.
+     */
+    size_t limit;
+};
+
+/**
+ * A string tokenizing context.
+ */
+typedef struct cx_strtok_ctx_s CxStrtokCtx;
+
+#ifdef __cplusplus
+extern "C" {
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1}
+
+#else // __cplusplus
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * The argument MUST be a string (const char*) \em literal.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1}
+
+#endif
+
+
+/**
+ * Wraps a mutable string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_str().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_mutstrn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_mutstr(char *cstring);
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_strn().
+ *
+ * @param cstring  the string to wrap (or \c NULL, only if the length is zero)
+ * @param length   the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_mutstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+);
+
+/**
+ * Wraps a string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstr().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_strn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxstring cx_str(const char *cstring);
+
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstrn().
+ *
+ * @param cstring  the string to wrap (or \c NULL, only if the length is zero)
+ * @param length   the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_str()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strn(
+        const char *cstring,
+        size_t length
+);
+
+/**
+* Casts a mutable string to an immutable string.
+*
+* \note This is not seriously a cast. Instead you get a copy
+* of the struct with the desired pointer type. Both structs still
+* point to the same location, though!
+*
+* @param str the mutable string to cast
+* @return an immutable copy of the string pointer
+*/
+__attribute__((__warn_unused_result__))
+cxstring cx_strcast(cxmutstr str);
+
+/**
+ * Passes the pointer in this string to \c free().
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
+ *
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree(cxmutstr *str);
+
+/**
+ * Passes the pointer in this string to the allocators free function.
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
+ *
+ * @param alloc the allocator
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree_a(
+        const CxAllocator *alloc,
+        cxmutstr *str
+);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ *
+ * \attention if the count argument is larger than the number of the
+ * specified strings, the behavior is undefined.
+ *
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the accumulated length of all strings
+ */
+__attribute__((__warn_unused_result__))
+size_t cx_strlen(
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param str   the string the other strings shall be concatenated to
+ * @param count the number of the other following strings to concatenate
+ * @param ...   all other strings
+ * @return the concatenated string
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strcat_ma(
+        const CxAllocator *alloc,
+        cxmutstr str,
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param count the number of the other following strings to concatenate
+ * @param ...   all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_a(alloc, count, ...) \
+cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param count   the number of the other following strings to concatenate
+ * @param ...     all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat(count, ...) \
+cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param str     the string the other strings shall be concatenated to
+ * @param count   the number of the other following strings to concatenate
+ * @param ...     all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_m(str, count, ...) \
+cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               \p needle, or an empty string, if the sequence is not
+ *               contained
+ * @see cx_strstr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               \p needle, or an empty string, if the sequence is not
+ *               contained
+ * @see cx_strstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_a(
+        const CxAllocator *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+);
+
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_ma(
+        const CxAllocator *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+);
+
+/**
+ * Compares two strings.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcasecmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcmp_p(
+        const void *s1,
+        const void *s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcasecmp_p(
+        const void *s1,
+        const void *s2
+);
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strdup_a(
+        const CxAllocator *allocator,
+        cxstring string
+);
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_a()
+ */
+#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_m()
+ */
+#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_ma()
+ */
+#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strtrim(cxstring string);
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strtrim_m(cxmutstr string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Converts the string to lower case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strlower(cxmutstr string);
+
+/**
+ * Converts the string to upper case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strupper(cxmutstr string);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strreplacen_a(
+        const CxAllocator *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplacen(str, pattern, replacement, replmax) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace_a(allocator, str, pattern, replacement) \
+cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace(str, pattern, replacement) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Creates a string tokenization context.
+ *
+ * @param str the string to tokenize
+ * @param delim the delimiter (must not be empty)
+ * @param limit the maximum number of tokens that shall be returned
+ * @return a new string tokenization context
+ */
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+* Creates a string tokenization context for a mutable string.
+*
+* @param str the string to tokenize
+* @param delim the delimiter (must not be empty)
+* @param limit the maximum number of tokens that shall be returned
+* @return a new string tokenization context
+*/
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+ * Returns the next token.
+ *
+ * The token will point to the source string.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+);
+
+/**
+ * Returns the next token of a mutable string.
+ *
+ * The token will point to the source string.
+ * If the context was not initialized over a mutable string, modifying
+ * the data of the returned token is undefined behavior.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+);
+
+/**
+ * Defines an array of more delimiters for the specified tokenization context.
+ *
+ * @param ctx the tokenization context
+ * @param delim array of more delimiters
+ * @param count number of elements in the array
+ */
+__attribute__((__nonnull__))
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        const cxstring *delim,
+        size_t count
+);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_STRING_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/test.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,330 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+ 
+/**
+ * @file: test.h
+ * 
+ * UCX Test Framework.
+ * 
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with CX_TEST_ASSERT()
+ * }
+ * 
+ * CX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #CX_TEST_DO {
+ *     // tests with CX_TEST_ASSERT() and/or
+ *     // calls with CX_TEST_CALL_SUBROUTINE() here
+ *   }
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ * 
+ * @attention Do not call own functions within a test, that use
+ * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define	UCX_TEST_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+//
+#if !defined(__clang__) && __GNUC__ > 3
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+
+#ifndef UCX_COMMON_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        const void *,
+        size_t,
+        size_t,
+        void *
+);
+#endif // UCX_COMMON_H
+
+/** Type for the CxTestSuite. */
+typedef struct CxTestSuite CxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func);
+
+/** Type for the internal list of test cases. */
+typedef struct CxTestSet CxTestSet;
+
+/** Structure for the internal list of test cases. */
+struct CxTestSet {
+    
+    /** Test case. */
+    CxTest test;
+    
+    /** Pointer to the next list element. */
+    CxTestSet *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct CxTestSuite {
+    
+    /** The number of successful tests after the suite has been run. */
+    unsigned int success;
+    
+    /** The number of failed tests after the suite has been run. */
+    unsigned int failure;
+
+    /** The optional name of this test suite. */
+    const char *name;
+    
+    /**
+     * Internal list of test cases.
+     * Use cx_test_register() to add tests to this list.
+     */
+    CxTestSet *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @param name optional name of the suite
+ * @return a new test suite
+ */
+static inline CxTestSuite* cx_test_suite_new(const char *name) {
+    CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
+    if (suite != NULL) {
+        suite->name = name;
+        suite->success = 0;
+        suite->failure = 0;
+        suite->tests = NULL;
+    }
+
+    return suite;
+}
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+static inline void cx_test_suite_free(CxTestSuite* suite) {
+    CxTestSet *l = suite->tests;
+    while (l != NULL) {
+        CxTestSet *e = l;
+        l = l->next;
+        free(e);
+    }
+    free(suite);
+}
+
+/**
+ * Registers a test function with the specified test suite.
+ * 
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return zero on success or non-zero on failure
+ */
+static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+    CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
+    if (t) {
+        t->test = test;
+        t->next = NULL;
+        if (suite->tests == NULL) {
+            suite->tests = t;
+        } else {
+            CxTestSet *last = suite->tests;
+            while (last->next) {
+                last = last->next;
+            }
+            last->next = t;
+        }
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param out_target the target buffer or file to write the output to
+ * @param out_writer the write function writing to \p out_target
+ */
+static inline void cx_test_run(CxTestSuite *suite,
+                               void *out_target, cx_write_func out_writer) {
+    if (suite->name == NULL) {
+        out_writer("*** Test Suite ***\n", 1, 19, out_target);
+    } else {
+        out_writer("*** Test Suite : ", 1, 17, out_target);
+        out_writer(suite->name, 1, strlen(suite->name), out_target);
+        out_writer(" ***\n", 1, 5, out_target);
+    }
+    suite->success = 0;
+    suite->failure = 0;
+    for (CxTestSet *elem = suite->tests; elem; elem = elem->next) {
+        elem->test(suite, out_target, out_writer);
+    }
+    out_writer("\nAll test completed.\n", 1, 21, out_target);
+    char total[80];
+    int len = snprintf(
+            total, 80,
+            "  Total:   %u\n  Success: %u\n  Failure: %u\n\n",
+            suite->success + suite->failure, suite->success, suite->failure
+    );
+    out_writer(total, 1, len, out_target);
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified FILE stream.
+ * @param suite the test suite to run
+ * @param file the target file to write the output to
+ */
+#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
+
+/**
+ * Runs a test suite and writes the test log to stdout.
+ * @param suite the test suite to run
+ */
+#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
+
+/**
+ * Macro for a #CxTest function header.
+ * 
+ * Use this macro to declare and/or define a #CxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_)
+
+/**
+ * Defines the scope of a test.
+ * @attention Any CX_TEST_ASSERT() calls must be performed in scope of
+ * #CX_TEST_DO.
+ */
+#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
+        _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        _writefnc_("... ", 1, 4, _output_);\
+        jmp_buf _env_;\
+        for (unsigned int _cx_test_loop_ = 0 ;\
+             _cx_test_loop_ == 0 && !setjmp(_env_);\
+             _writefnc_("success.\n", 1, 9, _output_),\
+             _suite_->success++, _cx_test_loop_++)
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
+        const char *_assert_msg_ = message; \
+        _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \
+        _writefnc_(".\n", 1, 2, _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    } (void) 0
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ */
+#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * CX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see CX_TEST_CALL_SUBROUTINE()
+ */
+#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\
+        void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see CX_TEST_SUBROUTINE()
+ */
+#define CX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__)
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_TEST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/tree.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,1254 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, 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.
+ */
+/**
+ * \file tree.h
+ * \brief Interface for tree implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_TREE_H
+#define UCX_TREE_H
+
+#include "common.h"
+
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A depth-first tree iterator.
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_iterator_s {
+    /**
+     * Base members.
+     */
+    CX_ITERATOR_BASE;
+    /**
+     * Indicates whether the subtree below the current node shall be skipped.
+     */
+    bool skip;
+    /**
+     * Set to true, when the iterator shall visit a node again
+     * when all it's children have been processed.
+     */
+    bool visit_on_exit;
+    /**
+     * True, if this iterator is currently leaving the node.
+     */
+    bool exiting;
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+    /**
+     * Offset in the node struct for the next pointer.
+     */
+    ptrdiff_t loc_next;
+    /**
+     * The total number of distinct nodes that have been passed so far.
+     */
+    size_t counter;
+    /**
+     * The currently observed node.
+     *
+     * This is the same what cxIteratorCurrent() would return.
+     */
+    void *node;
+    /**
+     * Stores a copy of the next pointer of the visited node.
+     * Allows freeing a node on exit without corrupting the iteration.
+     */
+    void *node_next;
+    /**
+     * Internal stack.
+     * Will be automatically freed once the iterator becomes invalid.
+     *
+     * If you want to discard the iterator before, you need to manually
+     * call cxTreeIteratorDispose().
+     */
+    void **stack;
+    /**
+     * Internal capacity of the stack.
+     */
+    size_t stack_capacity;
+    union {
+        /**
+         * Internal stack size.
+         */
+        size_t stack_size;
+        /**
+         * The current depth in the tree.
+         */
+        size_t depth;
+    };
+} CxTreeIterator;
+
+/**
+ * An element in a visitor queue.
+ */
+struct cx_tree_visitor_queue_s {
+    /**
+     * The tree node to visit.
+     */
+    void *node;
+    /**
+     * The depth of the node.
+     */
+    size_t depth;
+    /**
+     * The next element in the queue or \c NULL.
+     */
+    struct cx_tree_visitor_queue_s *next;
+};
+
+/**
+ * A breadth-first tree iterator.
+ *
+ * This iterator needs to maintain a visitor queue that will be automatically
+ * freed once the iterator becomes invalid.
+ * If you want to discard the iterator before, you MUST manually call
+ * cxTreeVisitorDispose().
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_visitor_s {
+    /**
+     * Base members.
+     */
+    CX_ITERATOR_BASE;
+    /**
+     * Indicates whether the subtree below the current node shall be skipped.
+     */
+    bool skip;
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+    /**
+     * Offset in the node struct for the next pointer.
+     */
+    ptrdiff_t loc_next;
+    /**
+     * The total number of distinct nodes that have been passed so far.
+     */
+    size_t counter;
+    /**
+     * The currently observed node.
+     *
+     * This is the same what cxIteratorCurrent() would return.
+     */
+    void *node;
+    /**
+     * The current depth in the tree.
+     */
+    size_t depth;
+    /**
+     * The next element in the visitor queue.
+     */
+    struct cx_tree_visitor_queue_s *queue_next;
+    /**
+     * The last element in the visitor queue.
+     */
+    struct cx_tree_visitor_queue_s *queue_last;
+} CxTreeVisitor;
+
+/**
+ * Releases internal memory of the given tree iterator.
+ * @param iter the iterator
+ */
+ __attribute__((__nonnull__))
+static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
+    free(iter->stack);
+    iter->stack = NULL;
+}
+
+/**
+ * Releases internal memory of the given tree visitor.
+ * @param visitor the visitor
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
+    struct cx_tree_visitor_queue_s *q = visitor->queue_next;
+    while (q != NULL) {
+        struct cx_tree_visitor_queue_s *next = q->next;
+        free(q);
+        q = next;
+    }
+}
+
+/**
+ * Advises the iterator to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param iterator the iterator
+ */
+#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue
+
+/**
+ * Advises the visitor to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param visitor the visitor
+ */
+#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor)
+
+/**
+ * Links a node to a (new) parent.
+ *
+ * If the node has already a parent, it is unlinked, first.
+ * If the parent has children already, the node is \em appended to the list
+ * of all currently existing children.
+ *
+ * @param parent the parent node
+ * @param node the node that shall be linked
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_unlink()
+ */
+__attribute__((__nonnull__))
+void cx_tree_link(
+        void *restrict parent,
+        void *restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks a node from its parent.
+ *
+ * If the node has no parent, this function does nothing.
+ *
+ * @param node the node that shall be unlinked from its parent
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_link()
+ */
+__attribute__((__nonnull__))
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the given \p data or if one of the children might contain
+ * the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param data the data that is searched for
+ *
+ * @return 0 if the node contains the data,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
+
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the same \p data as \p new_node or if one of the children might
+ * contain the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param new_node a new node with the information which is searched
+ *
+ * @return 0 if \p node contains the same data as \p new_node,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
+
+/**
+ * Searches for data in a tree.
+ *
+ * When the data cannot be found exactly, the search function might return a
+ * closest result which might be a good starting point for adding a new node
+ * to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param data the data to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search_data(
+        const void *root,
+        const void *data,
+        cx_tree_search_data_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Searches for a node in a tree.
+ *
+ * When no node with the same data can be found, the search function might
+ * return a closest result which might be a good starting point for adding the
+ * new node to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param node the node to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search(
+        const void *root,
+        const void *node,
+        cx_tree_search_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a depth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree iterator needs to maintain a stack of visited nodes, which is
+ * allocated using stdlib malloc().
+ * When the iterator becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the iterator becomes
+ * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param visit_on_exit set to true, when the iterator shall visit a node again
+ * after processing all children
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree iterator
+ * @see cxTreeIteratorDispose()
+ */
+CxTreeIterator cx_tree_iterator(
+        void *root,
+        bool visit_on_exit,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a breadth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree visitor needs to maintain a queue of to be visited nodes, which
+ * is allocated using stdlib malloc().
+ * When the visitor becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the visitor becomes
+ * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree visitor
+ * @see cxTreeVisitorDispose()
+ */
+CxTreeVisitor cx_tree_visitor(
+        void *root,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Describes a function that creates a tree node from the specified data.
+ * The first argument points to the data the node shall contain and
+ * the second argument may be used for additional data (e.g. an allocator).
+ * Functions of this type shall either return a new pointer to a newly
+ * created node or \c NULL when allocation fails.
+ *
+ * \note the function may leave the node pointers in the struct uninitialized.
+ * The caller is responsible to set them according to the intended use case.
+ */
+typedef void *(*cx_tree_node_create_func)(const void *, void *);
+
+/**
+ * The local search depth for a new subtree when adding multiple elements.
+ * The default value is 3.
+ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to
+ * implement optimized insertion of multiple elements into a tree.
+ */
+extern unsigned int cx_tree_add_look_around_depth;
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, leaving
+ * the iterator in a valid state pointing to the element that could not be
+ * added.
+ * Also, the pointer of the created node will be stored to \p failed.
+ * The integer returned by this function denotes the number of elements obtained
+ * from the \p iter that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param iter a pointer to an arbitrary iterator
+ * @param num the maximum number of elements to obtain from the iterator
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param root the root node of the tree
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of nodes created and added
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 3, 4, 6, 7)))
+size_t cx_tree_add_iter(
+        struct cx_iterator_base_s *iter,
+        size_t num,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, storing
+ * the pointer of the created node to \p failed.
+ * The integer returned by this function denotes the number of elements from
+ * the \p src array that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param src a pointer to the source data array
+ * @param num the number of elements in the \p src array
+ * @param elem_size the size of each element in the \p src array
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of array elements successfully processed
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 4, 5, 7, 8)))
+size_t cx_tree_add_array(
+        const void *src,
+        size_t num,
+        size_t elem_size,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Adds data to a tree.
+ *
+ * An adequate location where to add the new tree node is searched with the
+ * specified \p sfunc.
+ *
+ * When a location is found, the \p cfunc will be invoked with \p cdata.
+ *
+ * The node returned by \p cfunc will be linked into the tree.
+ * When \p sfunc returned a positive integer, the new node will be linked as a
+ * child. The other children (now siblings of the new node) are then checked
+ * with \p sfunc, whether they could be children of the new node and re-linked
+ * accordingly.
+ *
+ * When \p sfunc returned zero and the found node has a parent, the new
+ * node will be added as sibling - otherwise, the new node will be added
+ * as a child.
+ *
+ * When \p sfunc returned a negative value, the new node will not be added to
+ * the tree and this function returns a non-zero value.
+ * The caller should check if \p cnode contains a node pointer and deal with the
+ * node that could not be added.
+ *
+ * This function also returns a non-zero value when \p cfunc tries to allocate
+ * a new node but fails to do so. In that case, the pointer stored to \p cnode
+ * will be \c NULL.
+ *
+ * Multiple elements can be added more efficiently with
+ * #cx_tree_add_array() or #cx_tree_add_iter().
+ *
+ * @param src a pointer to the data
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param cnode the location where a pointer to the new node is stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero when a new node was created and added to the tree,
+ * non-zero otherwise
+ */
+__attribute__((__nonnull__(1, 2, 3, 5, 6)))
+int cx_tree_add(
+        const void *src,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **cnode,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+
+/**
+ * Tree class type.
+ */
+typedef struct cx_tree_class_s cx_tree_class;
+
+/**
+ * Base structure that can be used for tree nodes in a #CxTree.
+ */
+struct cx_tree_node_base_s {
+    /**
+     * Pointer to the parent.
+     */
+    struct cx_tree_node_base_s *parent;
+    /**
+     * Pointer to the first child.
+     */
+    struct cx_tree_node_base_s *children;
+    /**
+     * Pointer to the last child.
+     */
+    struct cx_tree_node_base_s *last_child;
+    /**
+     * Pointer to the previous sibling.
+     */
+    struct cx_tree_node_base_s *prev;
+    /**
+     * Pointer to the next sibling.
+     */
+    struct cx_tree_node_base_s *next;
+};
+
+/**
+ * Structure for holding the base data of a tree.
+ */
+struct cx_tree_s {
+    /**
+     * 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.
+     */
+    void *root;
+
+    /**
+     * A function to create new nodes.
+     *
+     * Invocations to this function will receive a pointer to this tree
+     * structure as second argument.
+     *
+     * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to.
+     */
+    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;
+
+    /**
+     * A function to compare a node with data.
+     */
+    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;
+
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+
+    /**
+     * Optional offset in the node struct for the pointer to the last child
+     * in the linked list (negative if there is no such pointer).
+     */
+    ptrdiff_t loc_last_child;
+
+    /**
+     * Offset in the node struct for the previous sibling pointer.
+     */
+    ptrdiff_t loc_prev;
+
+    /**
+     * Offset in the node struct for the next sibling pointer.
+     */
+    ptrdiff_t loc_next;
+};
+
+/**
+ * Macro to roll out the #cx_tree_node_base_s structure with a custom
+ * node type.
+ */
+#define CX_TREE_NODE_BASE(type) \
+    type *parent; \
+    type *children;\
+    type *last_child;\
+    type *prev;\
+    type *next
+
+/**
+ * Macro for specifying the layout of a base node tree.
+ */
+#define cx_tree_node_base_layout \
+    offsetof(struct cx_tree_node_base_s, parent),\
+    offsetof(struct cx_tree_node_base_s, children),\
+    offsetof(struct cx_tree_node_base_s, last_child),\
+    offsetof(struct cx_tree_node_base_s, prev),  \
+    offsetof(struct cx_tree_node_base_s, next)
+
+/**
+ * Macro for obtaining the node pointer layout for a specific tree.
+ */
+#define cx_tree_node_layout(tree) \
+    (tree)->loc_parent,\
+    (tree)->loc_children,\
+    (tree)->loc_last_child,\
+    (tree)->loc_prev,  \
+    (tree)->loc_next
+
+/**
+ * The class definition for arbitrary trees.
+ */
+struct cx_tree_class_s {
+    /**
+     * Destructor function.
+     *
+     * Implementations SHALL invoke the node destructor functions if provided
+     * and SHALL deallocate the tree memory.
+     */
+    void (*destructor)(struct cx_tree_s *);
+
+    /**
+     * Member function for inserting a single element.
+     *
+     * Implementations SHALL NOT simply invoke \p insert_many as this comes
+     * with too much overhead.
+     */
+    int (*insert_element)(
+            struct cx_tree_s *tree,
+            const void *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     *
+     * Implementations SHALL avoid to perform a full search in the tree for
+     * every element even though the source data MAY be unsorted.
+     */
+    size_t (*insert_many)(
+            struct cx_tree_s *tree,
+            struct cx_iterator_base_s *iter,
+            size_t n
+    );
+
+    /**
+     * Member function for finding a node.
+     */
+    void *(*find)(
+            struct cx_tree_s *tree,
+            const void *subtree,
+            const void *data
+    );
+
+    /**
+     * Member function for creating an iterator for the tree.
+     */
+    CxTreeIterator (*iterator)(
+            struct cx_tree_s *tree,
+            bool visit_on_exit
+    );
+
+    /**
+     * Member function for creating a visitor for the tree.
+     */
+    CxTreeVisitor (*visitor)(struct cx_tree_s *tree);
+};
+
+/**
+ * Common type for all tree implementations.
+ */
+typedef struct cx_tree_s CxTree;
+
+/**
+ * Creates a new tree structure based on the specified layout.
+ *
+ * The specified \p allocator will be used for creating the tree struct
+ * and SHALL be used by \p create_func to allocate memory for the nodes.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreateSimple()
+ * @see cxTreeCreateWrapped()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreate(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a new tree structure based on a default layout.
+ *
+ * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first
+ * member (or at least respect the default offsets specified in the tree
+ * struct) and they MUST be allocated with the specified allocator.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTree *cxTreeCreateSimple(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func
+) {
+    return cxTreeCreate(
+            allocator,
+            create_func,
+            search_func,
+            search_data_func,
+            cx_tree_node_base_layout
+    );
+}
+
+/**
+ * Creates a new tree structure based on an existing tree.
+ *
+ * The specified \p allocator will be used for creating the tree struct.
+ *
+ * \attention This function will create an incompletely defined tree structure
+ * where neither the create function, the search function, nor a destructor
+ * will be set. If you wish to use any of this functionality for the wrapped
+ * tree, you need to specify those functions afterwards.
+ *
+ * @param root the root node of the tree that shall be wrapped
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreateWrapped(
+        const CxAllocator *allocator,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Destroys the tree structure.
+ *
+ * \attention This function will only invoke the destructor functions
+ * on the nodes, if specified.
+ * It will NOT additionally free the nodes with the tree's allocator, because
+ * that would cause a double-free in most scenarios.
+ *
+ * @param tree the tree to destroy
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeDestroy(CxTree *tree) {
+    tree->cl->destructor(tree);
+}
+
+/**
+ * Inserts data into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to insert
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxTreeInsert(
+        CxTree *tree,
+        const void *data
+) {
+    return tree->cl->insert_element(tree, data);
+}
+
+/**
+ * Inserts elements provided by an iterator efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param iter the iterator providing the elements
+ * @param n the maximum number of elements to insert
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertIter(
+        CxTree *tree,
+        struct cx_iterator_base_s *iter,
+        size_t n
+) {
+    return tree->cl->insert_many(tree, iter, n);
+}
+
+/**
+ * Inserts an array of data efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the array of data to insert
+ * @param elem_size the size of each element in the array
+ * @param n the number of elements in the array
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertArray(
+        CxTree *tree,
+        const void *data,
+        size_t elem_size,
+        size_t n
+) {
+    if (n == 0) return 0;
+    if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
+    CxIterator iter = cxIterator(data, elem_size, n);
+    return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
+}
+
+/**
+ * Searches the data in the specified tree.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be available wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFind(
+        CxTree *tree,
+        const void *data
+) {
+    return tree->cl->find(tree, tree->root, data);
+}
+
+/**
+ * Searches the data in the specified subtree.
+ *
+ * \note When \p subtree_root is not part of the \p tree, the behavior is
+ * undefined.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be the case for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @param subtree_root the node where to start
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFindInSubtree(
+        CxTree *tree,
+        const void *data,
+        void *subtree_root
+) {
+    return tree->cl->find(tree, subtree_root, data);
+}
+
+/**
+ * Determines the size of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the number of nodes in the specified subtree
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the tree depth including the \p subtree_root
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the entire tree.
+ *
+ * @param tree the tree
+ * @return the tree depth, counting the root as one
+ */
+__attribute__((__nonnull__))
+size_t cxTreeDepth(CxTree *tree);
+
+/**
+ * Creates a depth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the sub-tree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisitor()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeIterator cxTreeIterator(
+        CxTree *tree,
+        bool visit_on_exit
+) {
+    return tree->cl->iterator(tree, visit_on_exit);
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterator()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) {
+    return tree->cl->visitor(tree);
+}
+
+/**
+ * Adds a new node to the tree.
+ *
+ * \attention The node may be externally created, but MUST obey the same rules
+ * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use
+ * the same allocator).
+ *
+ * @param tree the tree
+ * @param parent the parent of the node to add
+ * @param child the node to add
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeAddChildNode(
+        CxTree *tree,
+        void *parent,
+        void *child) {
+    cx_tree_link(parent, child, cx_tree_node_layout(tree));
+    tree->size++;
+}
+
+/**
+ * Creates a new node and adds it to the tree.
+ *
+ * With this function you can decide where exactly the new node shall be added.
+ * If you specified an appropriate search function, you may want to consider
+ * leaving this task to the tree by using #cxTreeInsert().
+ *
+ * Be aware that adding nodes at arbitrary locations in the tree might cause
+ * wrong or undesired results when subsequently invoking #cxTreeInsert() and
+ * the invariant imposed by the search function does not hold any longer.
+ *
+ * @param tree the tree
+ * @param parent the parent node of the new node
+ * @param data the data that will be submitted to the create function
+ * @return zero when the new node was created, non-zero on allocation failure
+ * @see cxTreeInsert()
+ */
+__attribute__((__nonnull__))
+int cxTreeAddChild(
+        CxTree *tree,
+        void *parent,
+        const void *data
+);
+
+/**
+ * A function that is invoked when a node needs to be re-linked to a new parent.
+ *
+ * When a node is re-linked, sometimes the contents need to be updated.
+ * This callback is invoked by #cxTreeRemove() so that those updates can be
+ * applied when re-linking the children of the removed node.
+ *
+ * @param node the affected node
+ * @param old_parent the old parent of the node
+ * @param new_parent the new parent of the node
+ */
+typedef void (*cx_tree_relink_func)(
+        void *node,
+        const void *old_parent,
+        const void *new_parent
+);
+
+/**
+ * Removes a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed node by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if \p node is the root node of the tree
+ */
+__attribute__((__nonnull__(1,2)))
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+);
+
+/**
+ * Removes a node and it's subtree from the tree.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ */
+__attribute__((__nonnull__))
+void cxTreeRemoveSubtree(CxTree *tree, void *node);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_TREE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/utils.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,194 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file utils.h
+ *
+ * \brief General purpose utility functions.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Convenience macro for a for loop that counts from zero to n-1.
+ */
+#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++)
+
+/**
+ * Convenience macro for swapping two pointers.
+ */
+#ifdef __cplusplus
+#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#else
+#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#endif
+
+// cx_szmul() definition
+
+#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
+#define CX_SZMUL_BUILTIN
+
+/**
+ * Alias for \c __builtin_mul_overflow.
+ *
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
+
+#else // no GNUC or clang bultin
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+  *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t where the result should be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+int cx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif // cx_szmul
+
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
+ * set this to zero to let the implementation decide
+ * @param n the maximum number of bytes that shall be copied.
+ * If this is larger than \p bufsize, the content is copied over multiple
+ * iterations.
+ * @return the total number of bytes copied
+ */
+__attribute__((__nonnull__(1, 2, 3, 4)))
+size_t cx_stream_bncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        char *buf,
+        size_t bufsize,
+        size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
+ * set this to zero to let the implementation decide
+ * @return total number of bytes copied
+ */
+#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \
+    cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX)
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n the maximum number of bytes that shall be copied.
+ * @return total number of bytes copied
+ */
+__attribute__((__nonnull__))
+size_t cx_stream_ncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @return total number of bytes copied
+ */
+#define cx_stream_copy(src, dest, rfnc, wfnc) \
+    cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_UTILS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_key.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/hash_key.h"
+#include <string.h>
+
+void cx_hash_murmur(CxHashKey *key) {
+    const unsigned char *data = key->data;
+    if (data == NULL) {
+        // extension: special value for NULL
+        key->hash = 1574210520u;
+        return;
+    }
+    size_t len = key->len;
+
+    unsigned m = 0x5bd1e995;
+    unsigned r = 24;
+    unsigned h = 25 ^ len;
+    unsigned i = 0;
+    while (len >= 4) {
+        unsigned k = data[i + 0] & 0xFF;
+        k |= (data[i + 1] & 0xFF) << 8;
+        k |= (data[i + 2] & 0xFF) << 16;
+        k |= (data[i + 3] & 0xFF) << 24;
+
+        k *= m;
+        k ^= k >> r;
+        k *= m;
+
+        h *= m;
+        h ^= k;
+
+        i += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+        case 3:
+            h ^= (data[i + 2] & 0xFF) << 16;
+                    __attribute__((__fallthrough__));
+        case 2:
+            h ^= (data[i + 1] & 0xFF) << 8;
+                    __attribute__((__fallthrough__));
+        case 1:
+            h ^= (data[i + 0] & 0xFF);
+            h *= m;
+                    __attribute__((__fallthrough__));
+        default: // do nothing
+            ;
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    key->hash = h;
+}
+
+CxHashKey cx_hash_key_str(const char *str) {
+    CxHashKey key;
+    key.data = str;
+    key.len = str == NULL ? 0 : strlen(str);
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key_bytes(
+        const unsigned char *bytes,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = bytes;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key(
+        const void *obj,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = obj;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_map.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,479 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/hash_map.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <assert.h>
+
+struct cx_hash_map_element_s {
+    /** A pointer to the next element in the current bucket. */
+    struct cx_hash_map_element_s *next;
+
+    /** The corresponding key. */
+    CxHashKey key;
+
+    /** The value data. */
+    char data[];
+};
+
+static void cx_hash_map_clear(struct cx_map_s *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    cx_for_n(i, hash_map->bucket_count) {
+        struct cx_hash_map_element_s *elem = hash_map->buckets[i];
+        if (elem != NULL) {
+            do {
+                struct cx_hash_map_element_s *next = elem->next;
+                // invoke the destructor
+                cx_invoke_destructor(map, elem->data);
+                // free the key data
+                cxFree(map->collection.allocator, (void *) elem->key.data);
+                // free the node
+                cxFree(map->collection.allocator, elem);
+                // proceed
+                elem = next;
+            } while (elem != NULL);
+
+            // do not leave a dangling pointer
+            hash_map->buckets[i] = NULL;
+        }
+    }
+    map->collection.size = 0;
+}
+
+static void cx_hash_map_destructor(struct cx_map_s *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    // free the buckets
+    cx_hash_map_clear(map);
+    cxFree(map->collection.allocator, hash_map->buckets);
+
+    // free the map structure
+    cxFree(map->collection.allocator, map);
+}
+
+static int cx_hash_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    const CxAllocator *allocator = map->collection.allocator;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+
+    while (elm != NULL && elm->key.hash < hash) {
+        prev = elm;
+        elm = elm->next;
+    }
+
+    if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
+        memcmp(elm->key.data, key.data, key.len) == 0) {
+        // overwrite existing element
+        if (map->collection.store_pointer) {
+            memcpy(elm->data, &value, sizeof(void *));
+        } else {
+            memcpy(elm->data, value, map->collection.elem_size);
+        }
+    } else {
+        // allocate new element
+        struct cx_hash_map_element_s *e = cxMalloc(
+                allocator,
+                sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
+        );
+        if (e == NULL) {
+            return -1;
+        }
+
+        // write the value
+        if (map->collection.store_pointer) {
+            memcpy(e->data, &value, sizeof(void *));
+        } else {
+            memcpy(e->data, value, map->collection.elem_size);
+        }
+
+        // copy the key
+        void *kd = cxMalloc(allocator, key.len);
+        if (kd == NULL) {
+            return -1;
+        }
+        memcpy(kd, key.data, key.len);
+        e->key.data = kd;
+        e->key.len = key.len;
+        e->key.hash = hash;
+
+        // insert the element into the linked list
+        if (prev == NULL) {
+            hash_map->buckets[slot] = e;
+        } else {
+            prev->next = e;
+        }
+        e->next = elm;
+
+        // increase the size
+        map->collection.size++;
+    }
+
+    return 0;
+}
+
+static void cx_hash_map_unlink(
+        struct cx_hash_map_s *hash_map,
+        size_t slot,
+        struct cx_hash_map_element_s *prev,
+        struct cx_hash_map_element_s *elm
+) {
+    // unlink
+    if (prev == NULL) {
+        hash_map->buckets[slot] = elm->next;
+    } else {
+        prev->next = elm->next;
+    }
+    // free element
+    cxFree(hash_map->base.collection.allocator, (void *) elm->key.data);
+    cxFree(hash_map->base.collection.allocator, elm);
+    // decrease size
+    hash_map->base.collection.size--;
+}
+
+/**
+ * Helper function to avoid code duplication.
+ *
+ * @param map the map
+ * @param key the key to look up
+ * @param remove flag indicating whether the looked up entry shall be removed
+ * @param destroy flag indicating whether the destructor shall be invoked
+ * @return a pointer to the value corresponding to the key or \c NULL
+ */
+static void *cx_hash_map_get_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool remove,
+        bool destroy
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+    while (elm && elm->key.hash <= hash) {
+        if (elm->key.hash == hash && elm->key.len == key.len) {
+            if (memcmp(elm->key.data, key.data, key.len) == 0) {
+                void *data = NULL;
+                if (destroy) {
+                    cx_invoke_destructor(map, elm->data);
+                } else {
+                    if (map->collection.store_pointer) {
+                        data = *(void **) elm->data;
+                    } else {
+                        data = elm->data;
+                    }
+                }
+                if (remove) {
+                    cx_hash_map_unlink(hash_map, slot, prev, elm);
+                }
+                return data;
+            }
+        }
+        prev = elm;
+        elm = prev->next;
+    }
+
+    return NULL;
+}
+
+static void *cx_hash_map_get(
+        const CxMap *map,
+        CxHashKey key
+) {
+    // we can safely cast, because we know the map stays untouched
+    return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+}
+
+static void *cx_hash_map_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool destroy
+) {
+    return cx_hash_map_get_remove(map, key, true, destroy);
+}
+
+static void *cx_hash_map_iter_current_entry(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    // struct has to have a compatible signature
+    return (struct cx_map_entry_s *) &(iter->kv_data);
+}
+
+static void *cx_hash_map_iter_current_key(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    return &elm->key;
+}
+
+static void *cx_hash_map_iter_current_value(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    const struct cx_hash_map_s *map = iter->src_handle.c;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    if (map->base.collection.store_pointer) {
+        return *(void **) elm->data;
+    } else {
+        return elm->data;
+    }
+}
+
+static bool cx_hash_map_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_hash_map_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    struct cx_hash_map_s *map = iter->src_handle.m;
+
+    // remove current element, if asked
+    if (iter->base.remove) {
+
+        // clear the flag
+        iter->base.remove = false;
+
+        // determine the next element
+        struct cx_hash_map_element_s *next = elm->next;
+
+        // search the previous element
+        struct cx_hash_map_element_s *prev = NULL;
+        if (map->buckets[iter->slot] != elm) {
+            prev = map->buckets[iter->slot];
+            while (prev->next != elm) {
+                prev = prev->next;
+            }
+        }
+
+        // destroy
+        cx_invoke_destructor((struct cx_map_s *) map, elm->data);
+
+        // unlink
+        cx_hash_map_unlink(map, iter->slot, prev, elm);
+
+        // advance
+        elm = next;
+    } else {
+        // just advance
+        elm = elm->next;
+        iter->index++;
+    }
+
+    // search the next bucket, if required
+    while (elm == NULL && ++iter->slot < map->bucket_count) {
+        elm = map->buckets[iter->slot];
+    }
+
+    // fill the struct with the next element
+    iter->elem_handle = elm;
+    if (elm == NULL) {
+        iter->kv_data.key = NULL;
+        iter->kv_data.value = NULL;
+    } else {
+        iter->kv_data.key = &elm->key;
+        if (map->base.collection.store_pointer) {
+            iter->kv_data.value = *(void **) elm->data;
+        } else {
+            iter->kv_data.value = elm->data;
+        }
+    }
+}
+
+static CxIterator cx_hash_map_iterator(
+        const CxMap *map,
+        enum cx_map_iterator_type type
+) {
+    CxIterator iter;
+
+    iter.src_handle.c = map;
+    iter.elem_count = map->collection.size;
+
+    switch (type) {
+        case CX_MAP_ITERATOR_PAIRS:
+            iter.elem_size = sizeof(CxMapEntry);
+            iter.base.current = cx_hash_map_iter_current_entry;
+            break;
+        case CX_MAP_ITERATOR_KEYS:
+            iter.elem_size = sizeof(CxHashKey);
+            iter.base.current = cx_hash_map_iter_current_key;
+            break;
+        case CX_MAP_ITERATOR_VALUES:
+            iter.elem_size = map->collection.elem_size;
+            iter.base.current = cx_hash_map_iter_current_value;
+            break;
+        default:
+            assert(false);
+    }
+
+    iter.base.valid = cx_hash_map_iter_valid;
+    iter.base.next = cx_hash_map_iter_next;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    iter.slot = 0;
+    iter.index = 0;
+
+    if (map->collection.size > 0) {
+        struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+        struct cx_hash_map_element_s *elm = hash_map->buckets[0];
+        while (elm == NULL) {
+            elm = hash_map->buckets[++iter.slot];
+        }
+        iter.elem_handle = elm;
+        iter.kv_data.key = &elm->key;
+        if (map->collection.store_pointer) {
+            iter.kv_data.value = *(void **) elm->data;
+        } else {
+            iter.kv_data.value = elm->data;
+        }
+    } else {
+        iter.elem_handle = NULL;
+        iter.kv_data.key = NULL;
+        iter.kv_data.value = NULL;
+    }
+
+    return iter;
+}
+
+static cx_map_class cx_hash_map_class = {
+        cx_hash_map_destructor,
+        cx_hash_map_clear,
+        cx_hash_map_put,
+        cx_hash_map_get,
+        cx_hash_map_remove,
+        cx_hash_map_iterator,
+};
+
+CxMap *cxHashMapCreate(
+        const CxAllocator *allocator,
+        size_t itemsize,
+        size_t buckets
+) {
+    if (buckets == 0) {
+        // implementation defined default
+        buckets = 16;
+    }
+
+    struct cx_hash_map_s *map = cxCalloc(allocator, 1,
+                                         sizeof(struct cx_hash_map_s));
+    if (map == NULL) return NULL;
+
+    // initialize hash map members
+    map->bucket_count = buckets;
+    map->buckets = cxCalloc(allocator, buckets,
+                            sizeof(struct cx_hash_map_element_s *));
+    if (map->buckets == NULL) {
+        cxFree(allocator, map);
+        return NULL;
+    }
+
+    // initialize base members
+    map->base.cl = &cx_hash_map_class;
+    map->base.collection.allocator = allocator;
+
+    if (itemsize > 0) {
+        map->base.collection.store_pointer = false;
+        map->base.collection.elem_size = itemsize;
+    } else {
+        map->base.collection.store_pointer = true;
+        map->base.collection.elem_size = sizeof(void *);
+    }
+
+    return (CxMap *) map;
+}
+
+int cxMapRehash(CxMap *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
+
+        size_t new_bucket_count = (map->collection.size * 5) >> 1;
+        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;
+        }
+
+        // iterate through the elements and assign them to their new slots
+        cx_for_n(slot, hash_map->bucket_count) {
+            struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+            while (elm != NULL) {
+                struct cx_hash_map_element_s *next = elm->next;
+                size_t new_slot = elm->key.hash % new_bucket_count;
+
+                // find position where to insert
+                struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
+                struct cx_hash_map_element_s *bucket_prev = NULL;
+                while (bucket_next != NULL &&
+                       bucket_next->key.hash < elm->key.hash) {
+                    bucket_prev = bucket_next;
+                    bucket_next = bucket_next->next;
+                }
+
+                // insert
+                if (bucket_prev == NULL) {
+                    elm->next = new_buckets[new_slot];
+                    new_buckets[new_slot] = elm;
+                } else {
+                    bucket_prev->next = elm;
+                    elm->next = bucket_next;
+                }
+
+                // advance
+                elm = next;
+            }
+        }
+
+        // assign result to the map
+        hash_map->bucket_count = new_bucket_count;
+        cxFree(map->collection.allocator, hash_map->buckets);
+        hash_map->buckets = new_buckets;
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/iterator.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, 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 "cx/iterator.h"
+
+#include <string.h>
+
+static bool cx_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->index < iter->elem_count;
+}
+
+static void *cx_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_iter_next_fast(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        iter->elem_count--;
+        // only move the last element when we are not currently aiming
+        // at the last element already
+        if (iter->index < iter->elem_count) {
+            void *last = ((char *) iter->src_handle.m)
+                         + iter->elem_count * iter->elem_size;
+            memcpy(iter->elem_handle, last, iter->elem_size);
+        }
+    } else {
+        iter->index++;
+        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+    }
+}
+
+static void cx_iter_next_slow(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        iter->elem_count--;
+
+        // number of elements to move
+        size_t remaining = iter->elem_count - iter->index;
+        if (remaining > 0) {
+            memmove(
+                    iter->elem_handle,
+                    ((char *) iter->elem_handle) + iter->elem_size,
+                    remaining * iter->elem_size
+            );
+        }
+    } else {
+        iter->index++;
+        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+    }
+}
+
+CxIterator cxMutIterator(
+        void *array,
+        size_t elem_size,
+        size_t elem_count,
+        bool remove_keeps_order
+) {
+    CxIterator iter;
+
+    iter.index = 0;
+    iter.src_handle.m = array;
+    iter.elem_handle = array;
+    iter.elem_size = elem_size;
+    iter.elem_count = array == NULL ? 0 : elem_count;
+    iter.base.valid = cx_iter_valid;
+    iter.base.current = cx_iter_current;
+    iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
+    iter.base.remove = false;
+    iter.base.mutating = true;
+
+    return iter;
+}
+
+CxIterator cxIterator(
+        const void *array,
+        size_t elem_size,
+        size_t elem_count
+) {
+    CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false);
+    iter.base.mutating = false;
+    return iter;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/linked_list.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,1117 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/linked_list.h"
+#include "cx/utils.h"
+#include "cx/compare.h"
+#include <string.h>
+#include <assert.h>
+
+// LOW LEVEL LINKED LIST FUNCTIONS
+
+#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define ll_prev(node) CX_LL_PTR(node, loc_prev)
+#define ll_next(node) CX_LL_PTR(node, loc_next)
+#define ll_advance(node) CX_LL_PTR(node, loc_advance)
+#define ll_data(node) (((char*)(node))+loc_data)
+
+void *cx_linked_list_at(
+        const void *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    size_t i = start_index;
+    const void *cur = start;
+    while (i != index && cur != NULL) {
+        cur = ll_advance(cur);
+        i < index ? i++ : i--;
+    }
+    return (void *) cur;
+}
+
+ssize_t cx_linked_list_find(
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+) {
+    void *dummy;
+    return cx_linked_list_find_node(
+            &dummy, start,
+            loc_advance, loc_data,
+            cmp_func, elem
+    );
+}
+
+ssize_t cx_linked_list_find_node(
+        void **result,
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+) {
+    assert(result != NULL);
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    const void *node = start;
+    ssize_t index = 0;
+    do {
+        void *current = ll_data(node);
+        if (cmp_func(current, elem) == 0) {
+            *result = (void*) node;
+            return index;
+        }
+        node = ll_advance(node);
+        index++;
+    } while (node != NULL);
+    *result = NULL;
+    return -1;
+}
+
+void *cx_linked_list_first(
+        const void *node,
+        ptrdiff_t loc_prev
+) {
+    return cx_linked_list_last(node, loc_prev);
+}
+
+void *cx_linked_list_last(
+        const void *node,
+        ptrdiff_t loc_next
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+
+    const void *cur = node;
+    const void *last;
+    do {
+        last = cur;
+    } while ((cur = ll_next(cur)) != NULL);
+
+    return (void *) last;
+}
+
+void *cx_linked_list_prev(
+        const void *begin,
+        ptrdiff_t loc_next,
+        const void *node
+) {
+    assert(begin != NULL);
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    if (begin == node) return NULL;
+    const void *cur = begin;
+    const void *next;
+    while (1) {
+        next = ll_next(cur);
+        if (next == node) return (void *) cur;
+        cur = next;
+    }
+}
+
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    ll_next(left) = right;
+    if (loc_prev >= 0) {
+        ll_prev(right) = left;
+    }
+}
+
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert (loc_next >= 0);
+    assert(ll_next(left) == right);
+    ll_next(left) = NULL;
+    if (loc_prev >= 0) {
+        assert(ll_prev(right) == left);
+        ll_prev(right) = NULL;
+    }
+}
+
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    void *last;
+    if (end == NULL) {
+        assert(begin != NULL);
+        last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next);
+    } else {
+        last = *end;
+    }
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
+}
+
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node);
+}
+
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
+}
+
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+) {
+    // find the end of the chain, if not specified
+    if (insert_end == NULL) {
+        insert_end = cx_linked_list_last(insert_begin, loc_next);
+    }
+
+    // determine the successor
+    void *successor;
+    if (node == NULL) {
+        assert(begin != NULL || (end != NULL && loc_prev >= 0));
+        if (begin != NULL) {
+            successor = *begin;
+            *begin = insert_begin;
+        } else {
+            successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev);
+        }
+    } else {
+        successor = ll_next(node);
+        cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
+    }
+
+    if (successor == NULL) {
+        // the list ends with the new chain
+        if (end != NULL) {
+            *end = insert_end;
+        }
+    } else {
+        cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
+    }
+}
+
+void cx_linked_list_insert_sorted(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node,
+        cx_compare_func cmp_func
+) {
+    assert(ll_next(new_node) == NULL);
+    cx_linked_list_insert_sorted_chain(
+            begin, end, loc_prev, loc_next, new_node, cmp_func);
+}
+
+void cx_linked_list_insert_sorted_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *insert_begin,
+        cx_compare_func cmp_func
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+    assert(insert_begin != NULL);
+
+    // track currently observed nodes
+    void *dest_prev = NULL;
+    void *dest = *begin;
+    void *src = insert_begin;
+
+    // special case: list is empty
+    if (dest == NULL) {
+        *begin = src;
+        if (end != NULL) {
+            *end = cx_linked_list_last(src, loc_next);
+        }
+        return;
+    }
+
+    // search the list for insertion points
+    while (dest != NULL && src != NULL) {
+        // compare current list node with source node
+        // if less or equal, skip
+        if (cmp_func(dest, src) <= 0) {
+            dest_prev = dest;
+            dest = ll_next(dest);
+            continue;
+        }
+
+        // determine chain of elements that can be inserted
+        void *end_of_chain = src;
+        void *next_in_chain = ll_next(src);
+        while (next_in_chain != NULL) {
+            // once we become larger than the list elem, break
+            if (cmp_func(dest, next_in_chain) <= 0) {
+                break;
+            }
+            // otherwise, we can insert one more
+            end_of_chain = next_in_chain;
+            next_in_chain = ll_next(next_in_chain);
+        }
+
+        // insert the elements
+        if (dest_prev == NULL) {
+            // new begin
+            *begin = src;
+        } else {
+            cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+        }
+        cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next);
+
+        // continue with next
+        src = next_in_chain;
+        dest_prev = dest;
+        dest = ll_next(dest);
+    }
+
+    // insert remaining items
+    if (src != NULL) {
+        cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+    }
+
+    // determine new end of list, if requested
+    if (end != NULL) {
+        *end = cx_linked_list_last(
+                dest != NULL ? dest : dest_prev, loc_next);
+    }
+}
+
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    assert(loc_prev >= 0 || begin != NULL);
+
+    // find adjacent nodes
+    void *next = ll_next(node);
+    void *prev;
+    if (loc_prev >= 0) {
+        prev = ll_prev(node);
+    } else {
+        prev = cx_linked_list_prev(*begin, loc_next, node);
+    }
+
+    // update next pointer of prev node, or set begin
+    if (prev == NULL) {
+        if (begin != NULL) {
+            *begin = next;
+        }
+    } else {
+        ll_next(prev) = next;
+    }
+
+    // update prev pointer of next node, or set end
+    if (next == NULL) {
+        if (end != NULL) {
+            *end = prev;
+        }
+    } else if (loc_prev >= 0) {
+        ll_prev(next) = prev;
+    }
+}
+
+size_t cx_linked_list_size(
+        const void *node,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    size_t size = 0;
+    while (node != NULL) {
+        node = ll_next(node);
+        size++;
+    }
+    return size;
+}
+
+#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
+#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
+#endif
+
+static void cx_linked_list_sort_merge(
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        size_t length,
+        void *ls,
+        void *le,
+        void *re,
+        cx_compare_func cmp_func,
+        void **begin,
+        void **end
+) {
+    void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
+    void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
+                    malloc(sizeof(void *) * length) : sbo;
+    if (sorted == NULL) abort();
+    void *rc, *lc;
+
+    lc = ls;
+    rc = le;
+    size_t n = 0;
+    while (lc && lc != le && rc != re) {
+        if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
+            sorted[n] = lc;
+            lc = ll_next(lc);
+        } else {
+            sorted[n] = rc;
+            rc = ll_next(rc);
+        }
+        n++;
+    }
+    while (lc && lc != le) {
+        sorted[n] = lc;
+        lc = ll_next(lc);
+        n++;
+    }
+    while (rc && rc != re) {
+        sorted[n] = rc;
+        rc = ll_next(rc);
+        n++;
+    }
+
+    // Update pointer
+    if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
+    cx_for_n (i, length - 1) {
+        cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
+    }
+    ll_next(sorted[length - 1]) = NULL;
+
+    *begin = sorted[0];
+    *end = sorted[length-1];
+    if (sorted != sbo) {
+        free(sorted);
+    }
+}
+
+void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    void *lc, *ls, *le, *re;
+
+    // set start node
+    ls = *begin;
+
+    // early exit when this list is empty
+    if (ls == NULL) return;
+
+    // check how many elements are already sorted
+    lc = ls;
+    size_t ln = 1;
+    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
+        lc = ll_next(lc);
+        ln++;
+    }
+    le = ll_next(lc);
+
+    // if first unsorted node is NULL, the list is already completely sorted
+    if (le != NULL) {
+        void *rc;
+        size_t rn = 1;
+        rc = le;
+        // skip already sorted elements
+        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
+            rc = ll_next(rc);
+            rn++;
+        }
+        re = ll_next(rc);
+
+        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+        void *sorted_begin, *sorted_end;
+        cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                  ln + rn, ls, le, re, cmp_func,
+                                  &sorted_begin, &sorted_end);
+
+        // Something left? Sort it!
+        size_t remainder_length = cx_linked_list_size(re, loc_next);
+        if (remainder_length > 0) {
+            void *remainder = re;
+            cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);
+
+            // merge sorted list with (also sorted) remainder
+            cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                      ln + rn + remainder_length,
+                                      sorted_begin, remainder, NULL, cmp_func,
+                                      &sorted_begin, &sorted_end);
+        }
+        *begin = sorted_begin;
+        if (end) *end = sorted_end;
+    }
+}
+
+int cx_linked_list_compare(
+        const void *begin_left,
+        const void *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    const void *left = begin_left, *right = begin_right;
+
+    while (left != NULL && right != NULL) {
+        const void *left_data = ll_data(left);
+        const void *right_data = ll_data(right);
+        int result = cmp_func(left_data, right_data);
+        if (result != 0) return result;
+        left = ll_advance(left);
+        right = ll_advance(right);
+    }
+
+    if (left != NULL) { return 1; }
+    else if (right != NULL) { return -1; }
+    else { return 0; }
+}
+
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+
+    // swap all links
+    void *prev = NULL;
+    void *cur = *begin;
+    while (cur != NULL) {
+        void *next = ll_next(cur);
+
+        ll_next(cur) = prev;
+        if (loc_prev >= 0) {
+            ll_prev(cur) = next;
+        }
+
+        prev = cur;
+        cur = next;
+    }
+
+    // update begin and end
+    if (end != NULL) {
+        *end = *begin;
+    }
+    *begin = prev;
+}
+
+// HIGH LEVEL LINKED LIST IMPLEMENTATION
+
+typedef struct cx_linked_list_node cx_linked_list_node;
+struct cx_linked_list_node {
+    cx_linked_list_node *prev;
+    cx_linked_list_node *next;
+    char payload[];
+};
+
+#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
+#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
+#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)
+
+typedef struct {
+    struct cx_list_s base;
+    cx_linked_list_node *begin;
+    cx_linked_list_node *end;
+} cx_linked_list;
+
+static cx_linked_list_node *cx_ll_node_at(
+        const cx_linked_list *list,
+        size_t index
+) {
+    if (index >= list->base.collection.size) {
+        return NULL;
+    } else if (index > list->base.collection.size / 2) {
+        return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index);
+    } else {
+        return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
+    }
+}
+
+static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) {
+    return cxMalloc(list->collection.allocator,
+                    sizeof(cx_linked_list_node) + list->collection.elem_size);
+}
+
+static int cx_ll_insert_at(
+        struct cx_list_s *list,
+        cx_linked_list_node *node,
+        const void *elem
+) {
+
+    // create the new new_node
+    cx_linked_list_node *new_node = cx_ll_malloc_node(list);
+
+    // sortir if failed
+    if (new_node == NULL) return 1;
+
+    // initialize new new_node
+    new_node->prev = new_node->next = NULL;
+    memcpy(new_node->payload, elem, list->collection.elem_size);
+
+    // insert
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_insert_chain(
+            (void **) &ll->begin, (void **) &ll->end,
+            CX_LL_LOC_PREV, CX_LL_LOC_NEXT,
+            node, new_node, new_node
+    );
+
+    // increase the size and return
+    list->collection.size++;
+    return 0;
+}
+
+static size_t cx_ll_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    // out-of bounds and corner case check
+    if (index > list->collection.size || n == 0) return 0;
+
+    // find position efficiently
+    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+    // perform first insert
+    if (0 != cx_ll_insert_at(list, node, array)) {
+        return 1;
+    }
+
+    // is there more?
+    if (n == 1) return 1;
+
+    // we now know exactly where we are
+    node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;
+
+    // we can add the remaining nodes and immediately advance to the inserted node
+    const char *source = array;
+    for (size_t i = 1; i < n; i++) {
+        source += list->collection.elem_size;
+        if (0 != cx_ll_insert_at(list, node, source)) {
+            return i;
+        }
+        node = node->next;
+    }
+    return n;
+}
+
+static int cx_ll_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        const void *element
+) {
+    return 1 != cx_ll_insert_array(list, index, element, 1);
+}
+
+static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
+
+static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
+    const cx_linked_list_node *left = l;
+    const cx_linked_list_node *right = r;
+    return cx_ll_insert_sorted_cmp_func(left->payload, right->payload);
+}
+
+static size_t cx_ll_insert_sorted(
+        struct cx_list_s *list,
+        const void *array,
+        size_t n
+) {
+    // special case
+    if (n == 0) return 0;
+
+    // create a new chain of nodes
+    cx_linked_list_node *chain = cx_ll_malloc_node(list);
+    if (chain == NULL) return 0;
+
+    memcpy(chain->payload, array, list->collection.elem_size);
+    chain->prev = NULL;
+    chain->next = NULL;
+
+    // add all elements from the array to that chain
+    cx_linked_list_node *prev = chain;
+    const char *src = array;
+    size_t inserted = 1;
+    for (; inserted < n; inserted++) {
+        cx_linked_list_node *next = cx_ll_malloc_node(list);
+        if (next == NULL) break;
+        src += list->collection.elem_size;
+        memcpy(next->payload, src, list->collection.elem_size);
+        prev->next = next;
+        next->prev = prev;
+        prev = next;
+    }
+    prev->next = NULL;
+
+    // invoke the low level function
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
+    cx_linked_list_insert_sorted_chain(
+            (void **) &ll->begin,
+            (void **) &ll->end,
+            CX_LL_LOC_PREV,
+            CX_LL_LOC_NEXT,
+            chain,
+            cx_ll_insert_sorted_cmp_helper
+    );
+
+    // adjust the list metadata
+    list->collection.size += inserted;
+
+    return inserted;
+}
+
+static int cx_ll_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = cx_ll_node_at(ll, index);
+
+    // out-of-bounds check
+    if (node == NULL) return 1;
+
+    // element destruction
+    cx_invoke_destructor(list, node->payload);
+
+    // remove
+    cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                          CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+
+    // adjust size
+    list->collection.size--;
+
+    // free and return
+    cxFree(list->collection.allocator, node);
+
+    return 0;
+}
+
+static void cx_ll_clear(struct cx_list_s *list) {
+    if (list->collection.size == 0) return;
+
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = ll->begin;
+    while (node != NULL) {
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_node *next = node->next;
+        cxFree(list->collection.allocator, node);
+        node = next;
+    }
+    ll->begin = ll->end = NULL;
+    list->collection.size = 0;
+}
+
+#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
+#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
+
+static int cx_ll_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->collection.size || j >= list->collection.size) return 1;
+    if (i == j) return 0;
+
+    // perform an optimized search that finds both elements in one run
+    cx_linked_list *ll = (cx_linked_list *) list;
+    size_t mid = list->collection.size / 2;
+    size_t left, right;
+    if (i < j) {
+        left = i;
+        right = j;
+    } else {
+        left = j;
+        right = i;
+    }
+    cx_linked_list_node *nleft, *nright;
+    if (left < mid && right < mid) {
+        // case 1: both items left from mid
+        nleft = cx_ll_node_at(ll, left);
+        assert(nleft != NULL);
+        nright = nleft;
+        for (size_t c = left; c < right; c++) {
+            nright = nright->next;
+        }
+    } else if (left >= mid && right >= mid) {
+        // case 2: both items right from mid
+        nright = cx_ll_node_at(ll, right);
+        assert(nright != NULL);
+        nleft = nright;
+        for (size_t c = right; c > left; c--) {
+            nleft = nleft->prev;
+        }
+    } else {
+        // case 3: one item left, one item right
+
+        // chose the closest to begin / end
+        size_t closest;
+        size_t other;
+        size_t diff2boundary = list->collection.size - right - 1;
+        if (left <= diff2boundary) {
+            closest = left;
+            other = right;
+            nleft = cx_ll_node_at(ll, left);
+        } else {
+            closest = right;
+            other = left;
+            diff2boundary = left;
+            nright = cx_ll_node_at(ll, right);
+        }
+
+        // is other element closer to us or closer to boundary?
+        if (right - left <= diff2boundary) {
+            // search other element starting from already found element
+            if (closest == left) {
+                nright = nleft;
+                for (size_t c = left; c < right; c++) {
+                    nright = nright->next;
+                }
+            } else {
+                nleft = nright;
+                for (size_t c = right; c > left; c--) {
+                    nleft = nleft->prev;
+                }
+            }
+        } else {
+            // search other element starting at the boundary
+            if (closest == left) {
+                nright = cx_ll_node_at(ll, other);
+            } else {
+                nleft = cx_ll_node_at(ll, other);
+            }
+        }
+    }
+
+    if (list->collection.elem_size > CX_LINKED_LIST_SWAP_SBO_SIZE) {
+        cx_linked_list_node *prev = nleft->prev;
+        cx_linked_list_node *next = nright->next;
+        cx_linked_list_node *midstart = nleft->next;
+        cx_linked_list_node *midend = nright->prev;
+
+        if (prev == NULL) {
+            ll->begin = nright;
+        } else {
+            prev->next = nright;
+        }
+        nright->prev = prev;
+        if (midstart == nright) {
+            // special case: both nodes are adjacent
+            nright->next = nleft;
+            nleft->prev = nright;
+        } else {
+            // likely case: a chain is between the two nodes
+            nright->next = midstart;
+            midstart->prev = nright;
+            midend->next = nleft;
+            nleft->prev = midend;
+        }
+        nleft->next = next;
+        if (next == NULL) {
+            ll->end = nleft;
+        } else {
+            next->prev = nleft;
+        }
+    } else {
+        // swap payloads to avoid relinking
+        char buf[CX_LINKED_LIST_SWAP_SBO_SIZE];
+        memcpy(buf, nleft->payload, list->collection.elem_size);
+        memcpy(nleft->payload, nright->payload, list->collection.elem_size);
+        memcpy(nright->payload, buf, list->collection.elem_size);
+    }
+
+    return 0;
+}
+
+static void *cx_ll_at(
+        const struct cx_list_s *list,
+        size_t index
+) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = cx_ll_node_at(ll, index);
+    return node == NULL ? NULL : node->payload;
+}
+
+static ssize_t cx_ll_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    if (remove) {
+        cx_linked_list *ll = ((cx_linked_list *) list);
+        cx_linked_list_node *node;
+        ssize_t index = cx_linked_list_find_node(
+                (void **) &node,
+                ll->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->collection.cmpfunc, elem
+        );
+        if (node != NULL) {
+            cx_invoke_destructor(list, node->payload);
+            cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                                  CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+            list->collection.size--;
+            cxFree(list->collection.allocator, node);
+        }
+        return index;
+    } else {
+        return cx_linked_list_find(
+                ((cx_linked_list *) list)->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->collection.cmpfunc, elem
+        );
+    }
+}
+
+static void cx_ll_sort(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
+                        CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                        list->collection.cmpfunc);
+}
+
+static void cx_ll_reverse(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT);
+}
+
+static int cx_ll_compare(
+        const struct cx_list_s *list,
+        const struct cx_list_s *other
+) {
+    cx_linked_list *left = (cx_linked_list *) list;
+    cx_linked_list *right = (cx_linked_list *) other;
+    return cx_linked_list_compare(left->begin, right->begin,
+                                  CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                                  list->collection.cmpfunc);
+}
+
+static bool cx_ll_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_ll_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        struct cx_list_s *list = iter->src_handle.m;
+        cx_linked_list *ll = iter->src_handle.m;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->collection.size--;
+        cxFree(list->collection.allocator, node);
+    } else {
+        iter->index++;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+    }
+}
+
+static void cx_ll_iter_prev(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        struct cx_list_s *list = iter->src_handle.m;
+        cx_linked_list *ll = iter->src_handle.m;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+        iter->index--;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->collection.size--;
+        cxFree(list->collection.allocator, node);
+    } else {
+        iter->index--;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+    }
+}
+
+static void *cx_ll_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    cx_linked_list_node *node = iter->elem_handle;
+    return node->payload;
+}
+
+static CxIterator cx_ll_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        bool backwards
+) {
+    CxIterator iter;
+    iter.index = index;
+    iter.src_handle.c = list;
+    iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
+    iter.elem_size = list->collection.elem_size;
+    iter.elem_count = list->collection.size;
+    iter.base.valid = cx_ll_iter_valid;
+    iter.base.current = cx_ll_iter_current;
+    iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    return iter;
+}
+
+static int cx_ll_insert_iter(
+        CxIterator *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    cx_linked_list_node *node = iter->elem_handle;
+    if (node != NULL) {
+        assert(prepend >= 0 && prepend <= 1);
+        cx_linked_list_node *choice[2] = {node, node->prev};
+        int result = cx_ll_insert_at(list, choice[prepend], elem);
+        if (result == 0) {
+            iter->elem_count++;
+            if (prepend) {
+                iter->index++;
+            }
+        }
+        return result;
+    } else {
+        int result = cx_ll_insert_element(list, list->collection.size, elem);
+        if (result == 0) {
+            iter->elem_count++;
+            iter->index = list->collection.size;
+        }
+        return result;
+    }
+}
+
+static void cx_ll_destructor(CxList *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+
+    cx_linked_list_node *node = ll->begin;
+    while (node) {
+        cx_invoke_destructor(list, node->payload);
+        void *next = node->next;
+        cxFree(list->collection.allocator, node);
+        node = next;
+    }
+
+    cxFree(list->collection.allocator, list);
+}
+
+static cx_list_class cx_linked_list_class = {
+        cx_ll_destructor,
+        cx_ll_insert_element,
+        cx_ll_insert_array,
+        cx_ll_insert_sorted,
+        cx_ll_insert_iter,
+        cx_ll_remove,
+        cx_ll_clear,
+        cx_ll_swap,
+        cx_ll_at,
+        cx_ll_find_remove,
+        cx_ll_sort,
+        cx_ll_compare,
+        cx_ll_reverse,
+        cx_ll_iterator,
+};
+
+CxList *cxLinkedListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_linked_list_class;
+    list->base.collection.allocator = allocator;
+
+    if (elem_size > 0) {
+        list->base.collection.elem_size = elem_size;
+        list->base.collection.cmpfunc = comparator;
+    } else {
+        list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+        cxListStorePointers((CxList *) list);
+    }
+
+    return (CxList *) list;
+}
--- a/ucx/list.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/list.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,403 +26,462 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/list.h"
+#include "cx/list.h"
+
+#include <string.h>
+
+// <editor-fold desc="Store Pointers Functionality">
+
+static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
+
+static int cx_pl_cmpfunc(
+        const void *l,
+        const void *r
+) {
+    void *const *lptr = l;
+    void *const *rptr = r;
+    const void *left = lptr == NULL ? NULL : *lptr;
+    const void *right = rptr == NULL ? NULL : *rptr;
+    return cx_pl_cmpfunc_impl(left, right);
+}
+
+static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) {
+    // cast away const - this is the hacky thing
+    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+    cx_pl_cmpfunc_impl = l->cmpfunc;
+    l->cmpfunc = cx_pl_cmpfunc;
+}
 
-UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) {
-    return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data);
+static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) {
+    // cast away const - this is the hacky thing
+    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+    l->cmpfunc = cx_pl_cmpfunc_impl;
+}
+
+static void cx_pl_destructor(struct cx_list_s *list) {
+    list->climpl->destructor(list);
+}
+
+static int cx_pl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        const void *element
+) {
+    return list->climpl->insert_element(list, index, &element);
+}
+
+static size_t cx_pl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    return list->climpl->insert_array(list, index, array, n);
 }
 
-UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l,
-        copy_func fnc, void *data) {
-    UcxList *ret = NULL;
-    while (l) {
-        if (fnc) {
-            ret = ucx_list_append_a(alloc, ret, fnc(l->data, data));
-        } else {
-            ret = ucx_list_append_a(alloc, ret, l->data);
-        }
-        l = l->next;
-    }
+static size_t cx_pl_insert_sorted(
+        struct cx_list_s *list,
+        const void *array,
+        size_t n
+) {
+    cx_pl_hack_cmpfunc(list);
+    size_t result = list->climpl->insert_sorted(list, array, n);
+    cx_pl_unhack_cmpfunc(list);
+    return result;
+}
+
+static int cx_pl_insert_iter(
+        struct cx_iterator_s *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    return list->climpl->insert_iter(iter, &elem, prepend);
+}
+
+static int cx_pl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    return list->climpl->remove(list, index);
+}
+
+static void cx_pl_clear(struct cx_list_s *list) {
+    list->climpl->clear(list);
+}
+
+static int cx_pl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    return list->climpl->swap(list, i, j);
+}
+
+static void *cx_pl_at(
+        const struct cx_list_s *list,
+        size_t index
+) {
+    void **ptr = list->climpl->at(list, index);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static ssize_t cx_pl_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    cx_pl_hack_cmpfunc(list);
+    ssize_t ret = list->climpl->find_remove(list, &elem, remove);
+    cx_pl_unhack_cmpfunc(list);
     return ret;
 }
 
-int ucx_list_equals(const UcxList *l1, const UcxList *l2,
-        cmp_func fnc, void* data) {
-    if (l1 == l2) return 1;
-    
-    while (l1 != NULL && l2 != NULL) {
-        if (fnc == NULL) {
-            if (l1->data != l2->data) return 0;
-        } else {
-            if (fnc(l1->data, l2->data, data) != 0) return 0;
-        }
-        l1 = l1->next;
-        l2 = l2->next;
-    }
-    
-    return (l1 == NULL && l2 == NULL);
+static void cx_pl_sort(struct cx_list_s *list) {
+    cx_pl_hack_cmpfunc(list);
+    list->climpl->sort(list);
+    cx_pl_unhack_cmpfunc(list);
 }
 
-void ucx_list_free(UcxList *l) {
-    ucx_list_free_a(ucx_default_allocator(), l);
+static int cx_pl_compare(
+        const struct cx_list_s *list,
+        const struct cx_list_s *other
+) {
+    cx_pl_hack_cmpfunc(list);
+    int ret = list->climpl->compare(list, other);
+    cx_pl_unhack_cmpfunc(list);
+    return ret;
+}
+
+static void cx_pl_reverse(struct cx_list_s *list) {
+    list->climpl->reverse(list);
+}
+
+static void *cx_pl_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    void **ptr = iter->base.current_impl(it);
+    return ptr == NULL ? NULL : *ptr;
 }
 
-void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) {
-    UcxList *e = l, *f;
-    while (e != NULL) {
-        f = e;
-        e = e->next;
-        alfree(alloc, f);
-    }
-}
-
-void ucx_list_free_content(UcxList* list, ucx_destructor destr) {
-    if (!destr) destr = free;
-    while (list != NULL) {
-        destr(list->data);
-        list = list->next;
-    }
+static struct cx_iterator_s cx_pl_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards);
+    iter.base.current_impl = iter.base.current;
+    iter.base.current = cx_pl_iter_current;
+    return iter;
 }
 
-UcxList *ucx_list_append(UcxList *l, void *data)  {
-    return ucx_list_append_a(ucx_default_allocator(), l, data);
-}
+static cx_list_class cx_pointer_list_class = {
+        cx_pl_destructor,
+        cx_pl_insert_element,
+        cx_pl_insert_array,
+        cx_pl_insert_sorted,
+        cx_pl_insert_iter,
+        cx_pl_remove,
+        cx_pl_clear,
+        cx_pl_swap,
+        cx_pl_at,
+        cx_pl_find_remove,
+        cx_pl_sort,
+        cx_pl_compare,
+        cx_pl_reverse,
+        cx_pl_iterator,
+};
 
-UcxList *ucx_list_append_a(UcxAllocator *alloc, UcxList *l, void *data)  {
-    UcxList *nl = (UcxList*) almalloc(alloc, sizeof(UcxList));
-    if (!nl) {
-        return NULL;
-    }
-    
-    nl->data = data;
-    nl->next = NULL;
-    if (l) {
-        UcxList *t = ucx_list_last(l);
-        t->next = nl;
-        nl->prev = t;
-        return l;
-    } else {
-        nl->prev = NULL;
-        return nl;
+void cxListStoreObjects(CxList *list) {
+    list->collection.store_pointer = false;
+    if (list->climpl != NULL) {
+        list->cl = list->climpl;
+        list->climpl = NULL;
     }
 }
 
-UcxList *ucx_list_prepend(UcxList *l, void *data) {
-    return ucx_list_prepend_a(ucx_default_allocator(), l, data);
-}
-
-UcxList *ucx_list_prepend_a(UcxAllocator *alloc, UcxList *l, void *data) {
-    UcxList *nl = ucx_list_append_a(alloc, NULL, data);
-    if (!nl) {
-        return NULL;
-    }
-    l = ucx_list_first(l);
-    
-    if (l) {
-        nl->next = l;
-        l->prev = nl;
-    }
-    return nl;
-}
-
-UcxList *ucx_list_concat(UcxList *l1, UcxList *l2) {
-    if (l1) {
-        UcxList *last = ucx_list_last(l1);
-        last->next = l2;
-        if (l2) {
-            l2->prev = last;
-        }
-        return l1;
-    } else {
-        return l2;
-    }
+void cxListStorePointers(CxList *list) {
+    list->collection.elem_size = sizeof(void *);
+    list->collection.store_pointer = true;
+    list->climpl = list->cl;
+    list->cl = &cx_pointer_list_class;
 }
 
-UcxList *ucx_list_last(const UcxList *l) {
-    if (l == NULL) return NULL;
-    
-    const UcxList *e = l;
-    while (e->next != NULL) {
-        e = e->next;
-    }
-    return (UcxList*)e;
-}
+// </editor-fold>
 
-ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem) {
-    ssize_t index = 0;
-    while (list) {
-        if (list == elem) {
-            return index;
-        }
-        list = list->next;
-        index++;
-    }
-    return -1;
+// <editor-fold desc="empty list implementation">
+
+static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+    // this is a noop, but MUST be implemented
 }
 
-UcxList *ucx_list_get(const UcxList *l, size_t index) {
-    if (l == NULL) return NULL;
-
-    const UcxList *e = l;
-    while (e->next && index > 0) {
-        e = e->next;
-        index--;
-    }
-    
-    return (UcxList*)(index == 0 ? e : NULL);
+static void *cx_emptyl_at(
+        __attribute__((__unused__)) const struct cx_list_s *list,
+        __attribute__((__unused__)) size_t index
+) {
+    return NULL;
 }
 
-ssize_t ucx_list_find(const UcxList *l, void *elem,
-        cmp_func fnc, void *cmpdata) {
-    ssize_t index = 0;
-    UCX_FOREACH(e, l) {
-        if (fnc) {
-            if (fnc(elem, e->data, cmpdata) == 0) {
-                return index;
-            }
-        } else {
-            if (elem == e->data) {
-                return index;
-            }
-        }
-        index++;
-    }
+static ssize_t cx_emptyl_find_remove(
+        __attribute__((__unused__)) struct cx_list_s *list,
+        __attribute__((__unused__)) const void *elem,
+        __attribute__((__unused__)) bool remove
+) {
     return -1;
 }
 
-int ucx_list_contains(const UcxList *l, void *elem,
-        cmp_func fnc, void *cmpdata) {
-    return ucx_list_find(l, elem, fnc, cmpdata) > -1;
+static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) {
+    return false;
 }
 
-size_t ucx_list_size(const UcxList *l) {
-    if (l == NULL) return 0;
-    
-    const UcxList *e = l;
-    size_t s = 1;
-    while (e->next != NULL) {
-        e = e->next;
-        s++;
-    }
-
-    return s;
+static CxIterator cx_emptyl_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        __attribute__((__unused__)) bool backwards
+) {
+    CxIterator iter = {0};
+    iter.src_handle.c = list;
+    iter.index = index;
+    iter.base.valid = cx_emptyl_iter_valid;
+    return iter;
 }
 
-static UcxList *ucx_list_sort_merge(size_t length,
-        UcxList* ls, UcxList* le, UcxList* re,
-        cmp_func fnc, void* data) {
+static cx_list_class cx_empty_list_class = {
+        cx_emptyl_noop,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        cx_emptyl_noop,
+        NULL,
+        cx_emptyl_at,
+        cx_emptyl_find_remove,
+        cx_emptyl_noop,
+        NULL,
+        cx_emptyl_noop,
+        cx_emptyl_iterator,
+};
 
-    UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length);
-    UcxList *rc, *lc;
+CxList cx_empty_list = {
+        {
+                NULL,
+                NULL,
+                0,
+                0,
+                NULL,
+                NULL,
+                NULL,
+                false
+        },
+        &cx_empty_list_class,
+        NULL
+};
+
+CxList *const cxEmptyList = &cx_empty_list;
+
+// </editor-fold>
+
+#define invoke_list_func(name, list, ...) \
+    ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
+    (list, __VA_ARGS__)
 
-    lc = ls; rc = le;
-    size_t n = 0;
-    while (lc && lc != le && rc != re) {
-        if (fnc(lc->data, rc->data, data) <= 0) {
-            sorted[n] = lc;
-            lc = lc->next;
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *data,
+        size_t n
+) {
+    size_t elem_size = list->collection.elem_size;
+    const char *src = data;
+    size_t i = 0;
+    for (; i < n; i++) {
+        if (0 != invoke_list_func(insert_element,
+                                  list, index + i, src + (i * elem_size))) {
+            return i;
+        }
+    }
+    return i;
+}
+
+size_t cx_list_default_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    // corner case
+    if (n == 0) return 0;
+
+    size_t elem_size = list->collection.elem_size;
+    cx_compare_func cmp = list->collection.cmpfunc;
+    const char *src = sorted_data;
+
+    // track indices and number of inserted items
+    size_t di = 0, si = 0, inserted = 0;
+
+    // search the list for insertion points
+    for (; di < list->collection.size; di++) {
+        const void *list_elm = invoke_list_func(at, list, di);
+
+        // compare current list element with first source element
+        // if less or equal, skip
+        if (cmp(list_elm, src) <= 0) {
+            continue;
+        }
+
+        // determine number of consecutive elements that can be inserted
+        size_t ins = 1;
+        const char *next = src;
+        while (++si < n) {
+            next += elem_size;
+            // once we become larger than the list elem, break
+            if (cmp(list_elm, next) <= 0) {
+                break;
+            }
+            // otherwise, we can insert one more
+            ins++;
+        }
+
+        // insert the elements at location si
+        if (ins == 1) {
+            if (0 != invoke_list_func(insert_element,
+                                      list, di, src))
+                return inserted;
         } else {
-            sorted[n] = rc;
-            rc = rc->next;
+            size_t r = invoke_list_func(insert_array, list, di, src, ins);
+            if (r < ins) return inserted + r;
         }
-        n++;
-    }
-    while (lc && lc != le) {
-        sorted[n] = lc;
-        lc = lc->next;
-        n++;
-    }
-    while (rc && rc != re) {
-        sorted[n] = rc;
-        rc = rc->next;
-        n++;
+        inserted += ins;
+        di += ins;
+
+        // everything inserted?
+        if (inserted == n) return inserted;
+        src = next;
     }
 
-    // Update pointer
-    sorted[0]->prev = NULL;
-    for (int i = 0 ; i < length-1 ; i++) {
-        sorted[i]->next = sorted[i+1];
-        sorted[i+1]->prev = sorted[i];
+    // insert remaining items
+    if (si < n) {
+        inserted += invoke_list_func(insert_array, list, di, src, n - si);
     }
-    sorted[length-1]->next = NULL;
 
-    UcxList *ret = sorted[0];
-    free(sorted);
-    return ret;
+    return inserted;
 }
 
-UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) {
-    if (l == NULL) {
-        return NULL;
+void cx_list_default_sort(struct cx_list_s *list) {
+    size_t elem_size = list->collection.elem_size;
+    size_t list_size = list->collection.size;
+    void *tmp = malloc(elem_size * list_size);
+    if (tmp == NULL) abort();
+
+    // copy elements from source array
+    char *loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *src = invoke_list_func(at, list, i);
+        memcpy(loc, src, elem_size);
+        loc += elem_size;
+    }
+
+    // qsort
+    qsort(tmp, list_size, elem_size,
+          list->collection.cmpfunc);
+
+    // copy elements back
+    loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *dest = invoke_list_func(at, list, i);
+        memcpy(dest, loc, elem_size);
+        loc += elem_size;
     }
 
-    UcxList *lc;
-    size_t ln = 1;
+    free(tmp);
+}
+
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
+    if (i == j) return 0;
+    if (i >= list->collection.size) return 1;
+    if (j >= list->collection.size) return 1;
+
+    size_t elem_size = list->collection.elem_size;
+
+    void *tmp = malloc(elem_size);
+    if (tmp == NULL) return 1;
+
+    void *ip = invoke_list_func(at, list, i);
+    void *jp = invoke_list_func(at, list, j);
+
+    memcpy(tmp, ip, elem_size);
+    memcpy(ip, jp, elem_size);
+    memcpy(jp, tmp, elem_size);
+
+    free(tmp);
+
+    return 0;
+}
+
+void cxListDestroy(CxList *list) {
+    list->cl->destructor(list);
+}
+
+int cxListCompare(
+        const CxList *list,
+        const CxList *other
+) {
+    bool cannot_optimize = false;
 
-    UcxList *ls = l, *le, *re;
-    
-    // check how many elements are already sorted
-    lc = ls;
-    while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) {
-        lc = lc->next;
-        ln++;
+    // if one is storing pointers but the other is not
+    cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
+
+    // if one class is wrapped but the other is not
+    cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
+
+    // if the compare functions do not match or both are NULL
+    if (!cannot_optimize) {
+        cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
+                                                      list->climpl->compare : list->cl->compare);
+        cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
+                                                       other->climpl->compare : other->cl->compare);
+        cannot_optimize |= list_cmp != other_cmp;
+        cannot_optimize |= list_cmp == NULL;
     }
-    le = lc->next;
 
-    if (le == NULL) {
-        return l; // this list is already sorted :)
+    if (cannot_optimize) {
+        // lists are definitely different - cannot use internal compare function
+        if (list->collection.size == other->collection.size) {
+            CxIterator left = list->cl->iterator(list, 0, false);
+            CxIterator right = other->cl->iterator(other, 0, false);
+            for (size_t i = 0; i < list->collection.size; i++) {
+                void *leftValue = cxIteratorCurrent(left);
+                void *rightValue = cxIteratorCurrent(right);
+                int d = list->collection.cmpfunc(leftValue, rightValue);
+                if (d != 0) {
+                    return d;
+                }
+                cxIteratorNext(left);
+                cxIteratorNext(right);
+            }
+            return 0;
+        } else {
+            return list->collection.size < other->collection.size ? -1 : 1;
+        }
     } else {
-        UcxList *rc;
-        size_t rn = 1;
-        rc = le;
-        // skip already sorted elements
-        while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) {
-            rc = rc->next;
-            rn++;
-        }
-        re = rc->next;
-
-        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
-        UcxList *sorted = ucx_list_sort_merge(ln+rn,
-                ls, le, re,
-                fnc, data);
-        
-        // Something left? Sort it!
-        size_t remainder_length = ucx_list_size(re);
-        if (remainder_length > 0) {
-            UcxList *remainder = ucx_list_sort(re, fnc, data);
-
-            // merge sorted list with (also sorted) remainder
-            l = ucx_list_sort_merge(ln+rn+remainder_length,
-                    sorted, remainder, NULL, fnc, data);
-        } else {
-            // no remainder - we've got our sorted list
-            l = sorted;
-        }
-
-        return l;
+        // lists are compatible
+        return list->cl->compare(list, other);
     }
 }
 
-UcxList *ucx_list_first(const UcxList *l) {
-    if (!l) {
-        return NULL;
-    }
-    
-    const UcxList *e = l;
-    while (e->prev) {
-        e = e->prev;
-    }
-    return (UcxList *)e;
-}
-
-UcxList *ucx_list_remove(UcxList *l, UcxList *e) {
-    return ucx_list_remove_a(ucx_default_allocator(), l, e);
-}
-    
-UcxList *ucx_list_remove_a(UcxAllocator *alloc, UcxList *l, UcxList *e) {
-    if (l == e) {
-        l = e->next;
-    }
-    
-    if (e->next) {
-        e->next->prev = e->prev;
-    }
-    
-    if (e->prev) {
-        e->prev->next = e->next;
-    }
-    
-    alfree(alloc, e);
-    return l;
+CxIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, false);
+    it.base.mutating = true;
+    return it;
 }
 
-
-static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator,
-        UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata,
-        int op) {
-    
-    UcxList *res = NULL;
-    UcxList *cur = NULL;
-    const UcxList *src = left;
-    
-    do {
-        UCX_FOREACH(node, src) {
-            void* elem = node->data;
-            if (
-                (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) ||
-                (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) ||
-                (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) {
-                UcxList *nl = almalloc(allocator, sizeof(UcxList));
-                nl->prev = cur;
-                nl->next = NULL;
-                if (cpfnc) {
-                    nl->data = cpfnc(elem, cpdata);
-                } else {
-                    nl->data = elem;
-                }
-                if (cur != NULL)
-                    cur->next = nl;
-                cur = nl;
-                if (res == NULL)
-                    res = cur;
-            }
-        }
-        if (op == 0 && src == left)
-            src = right;
-        else
-            src = NULL;
-    } while (src != NULL);
-    
-    return res;
+CxIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, true);
+    it.base.mutating = true;
+    return it;
 }
-
-UcxList* ucx_list_union(UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    return ucx_list_union_a(ucx_default_allocator(),
-            left, right, cmpfnc, cmpdata, cpfnc, cpdata);
-}
-
-UcxList* ucx_list_union_a(UcxAllocator *allocator,
-        UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    
-    return ucx_list_setoperation_a(allocator, left, right,
-            cmpfnc, cmpdata, cpfnc, cpdata, 0);
-}
-
-UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    return ucx_list_intersection_a(ucx_default_allocator(), left, right,
-            cmpfnc, cmpdata, cpfnc, cpdata);
-}
-
-UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
-        UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    
-    return ucx_list_setoperation_a(allocator, left, right,
-            cmpfnc, cmpdata, cpfnc, cpdata, 1);
-}
-
-UcxList* ucx_list_difference(UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    return ucx_list_difference_a(ucx_default_allocator(), left, right,
-            cmpfnc, cmpdata, cpfnc, cpdata);
-}
-
-UcxList* ucx_list_difference_a(UcxAllocator *allocator,
-        UcxList const *left, UcxList const *right,
-        cmp_func cmpfnc, void* cmpdata,
-        copy_func cpfnc, void* cpdata) {
-    
-    return ucx_list_setoperation_a(allocator, left, right,
-            cmpfnc, cmpdata, cpfnc, cpdata, 2);
-}
--- a/ucx/logging.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/logging.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <time.h>
-
-UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask) {
-    UcxLogger *logger = (UcxLogger*) malloc(sizeof(UcxLogger));
-    if (logger != NULL) {
-        logger->stream = stream;
-        logger->writer = (write_func)fwrite;
-        logger->dateformat = (char*) "%F %T %z ";
-        logger->level = level;
-        logger->mask = mask;
-        logger->levels = ucx_map_new(8);
-        
-        unsigned int l;
-        l = UCX_LOGGER_ERROR;
-        ucx_map_int_put(logger->levels, l, (void*) "[ERROR]");
-        l = UCX_LOGGER_WARN;
-        ucx_map_int_put(logger->levels, l, (void*) "[WARNING]");
-        l = UCX_LOGGER_INFO;
-        ucx_map_int_put(logger->levels, l, (void*) "[INFO]");
-        l = UCX_LOGGER_DEBUG;
-        ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]");
-        l = UCX_LOGGER_TRACE;
-        ucx_map_int_put(logger->levels, l, (void*) "[TRACE]");
-    }
-
-    return logger;
-}
-
-void ucx_logger_free(UcxLogger *logger) {
-    ucx_map_free(logger->levels);
-    free(logger);
-}
-
-// estimated max. message length (documented)
-#define UCX_LOGGER_MSGMAX 4096
-
-void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
-        const unsigned int line, const char *format, ...) {
-    if (level <= logger->level) {
-        char msg[UCX_LOGGER_MSGMAX];
-        const char *text;
-        size_t k = 0;
-        size_t n;
-        
-        if ((logger->mask & UCX_LOGGER_LEVEL) > 0) {
-            text = (const char*) ucx_map_int_get(logger->levels, level);
-            if (!text) {
-                text = "[UNKNOWN]";
-            }
-            n = strlen(text);
-            n = n > 256 ? 256 : n;
-            memcpy(msg+k, text, n);
-            k += n;
-            msg[k++] = ' ';
-        }
-        if ((logger->mask & UCX_LOGGER_TIMESTAMP) > 0) {
-            time_t now = time(NULL);
-            k += strftime(msg+k, 128, logger->dateformat, localtime(&now));
-        }
-        if ((logger->mask & UCX_LOGGER_SOURCE) > 0) {
-            char *fpart = strrchr(file, '/');
-            if (fpart) file = fpart+1;
-            fpart = strrchr(file, '\\');
-            if (fpart) file = fpart+1;
-            n = strlen(file);
-            memcpy(msg+k, file, n);
-            k += n;
-            k += sprintf(msg+k, ":%u ", line);
-        }
-        
-        if (k > 0) {
-            msg[k++] = '-'; msg[k++] = ' ';
-        }
-        
-        va_list args;
-        va_start (args, format);
-        k += vsnprintf(msg+k, UCX_LOGGER_MSGMAX-k-1, format, args);
-        va_end (args);        
-        
-        msg[k++] = '\n';
-        
-        logger->writer(msg, 1, k, logger->stream);
-    }
-}
--- a/ucx/map.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/map.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,402 +1,102 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/map.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-UcxMap *ucx_map_new(size_t size) {
-    return ucx_map_new_a(NULL, size);
-}
-
-UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size) {
-    if(size == 0) {
-        size = 16;
-    }
-       
-    if(!allocator) {
-        allocator = ucx_default_allocator();
-    }
-    
-    UcxMap *map = (UcxMap*)almalloc(allocator, sizeof(UcxMap));
-    if (!map) {
-        return NULL;
-    }
-    
-    map->allocator = allocator;
-    map->map = (UcxMapElement**)alcalloc(
-            allocator, size, sizeof(UcxMapElement*));
-    if(map->map == NULL) {
-        alfree(allocator, map);
-        return NULL;
-    }
-    map->size = size;
-    map->count = 0;
-
-    return map;
-}
-
-static void ucx_map_free_elmlist_contents(UcxMap *map) {
-    for (size_t n = 0 ; n < map->size ; n++) {
-        UcxMapElement *elem = map->map[n];
-        if (elem != NULL) {
-            do {
-                UcxMapElement *next = elem->next;
-                alfree(map->allocator, elem->key.data);
-                alfree(map->allocator, elem);
-                elem = next;
-            } while (elem != NULL);
-        }
-    }
-}
-
-void ucx_map_free(UcxMap *map) {
-    ucx_map_free_elmlist_contents(map);
-    alfree(map->allocator, map->map);
-    alfree(map->allocator, map);
-}
-
-void ucx_map_free_content(UcxMap *map, ucx_destructor destr) {
-    UcxMapIterator iter = ucx_map_iterator(map);
-    void *val;
-    UCX_MAP_FOREACH(key, val, iter) {
-        if (destr) {
-            destr(val);
-        } else {
-            alfree(map->allocator, val);
-        }
-    }
-}
-
-void ucx_map_clear(UcxMap *map) {
-    if (map->count == 0) {
-        return; // nothing to do
-    }
-    ucx_map_free_elmlist_contents(map);
-    memset(map->map, 0, map->size*sizeof(UcxMapElement*));
-    map->count = 0;
-}
-
-int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) {
-    UcxMapIterator i = ucx_map_iterator(from);
-    void *value;
-    UCX_MAP_FOREACH(key, value, i) {
-        if (ucx_map_put(to, key, fnc ? fnc(value, data) : value)) {
-            return 1;
-        }
-    }
-    return 0;
-}
-
-UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) {
-    return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data);
-}
-
-UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
-        UcxMap const *map, copy_func fnc, void *data) {
-    size_t bs = (map->count * 5) >> 1;
-    UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size);
-    if (!newmap) {
-        return NULL;
-    }
-    ucx_map_copy(map, newmap, fnc, data);
-    return newmap;
-}
-
-int ucx_map_rehash(UcxMap *map) {
-    size_t load = (map->size * 3) >> 2;
-    if (map->count > load) {
-        UcxMap oldmap;
-        oldmap.map = map->map;
-        oldmap.size = map->size;
-        oldmap.count = map->count;
-        oldmap.allocator = map->allocator;
-        
-        map->size = (map->count * 5) >> 1;
-        map->map = (UcxMapElement**)alcalloc(
-                map->allocator, map->size, sizeof(UcxMapElement*));
-        if (!map->map) {
-            *map = oldmap;
-            return 1;
-        }
-        map->count = 0;
-        ucx_map_copy(&oldmap, map, NULL, NULL);
-        
-        /* free the UcxMapElement list of oldmap */
-        ucx_map_free_elmlist_contents(&oldmap);
-        alfree(map->allocator, oldmap.map);
-    }
-    return 0;
-}
-
-int ucx_map_put(UcxMap *map, UcxKey key, void *data) {
-    UcxAllocator *allocator = map->allocator;
-    
-    if (key.hash == 0) {
-        key.hash = ucx_hash((const char*)key.data, key.len);
-    }
-    
-    struct UcxMapKey mapkey;
-    mapkey.hash = key.hash;
-
-    size_t slot = mapkey.hash%map->size;
-    UcxMapElement *elm = map->map[slot];
-    UcxMapElement *prev = NULL;
-
-    while (elm && elm->key.hash < mapkey.hash) {
-        prev = elm;
-        elm = elm->next;
-    }
-    
-    if (!elm || elm->key.hash != mapkey.hash) {
-        UcxMapElement *e = (UcxMapElement*)almalloc(
-                allocator, sizeof(UcxMapElement));
-        if (!e) {
-            return -1;
-        }
-        e->key.data = NULL;
-        if (prev) {
-            prev->next = e;
-        } else {
-            map->map[slot] = e;
-        }
-        e->next = elm;
-        elm = e;
-    }
-    
-    if (!elm->key.data) {
-        void *kd = almalloc(allocator, key.len);
-        if (!kd) {
-            return -1;
-        }
-        memcpy(kd, key.data, key.len);
-        mapkey.data = kd;
-        mapkey.len = key.len;
-        elm->key = mapkey;
-        map->count++;
-    }
-    elm->data = data;
-
-    return 0;
-}
-
-static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) {
-    if(key.hash == 0) {
-        key.hash = ucx_hash((const char*)key.data, key.len);
-    }
-    
-    size_t slot = key.hash%map->size;
-    UcxMapElement *elm = map->map[slot];
-    UcxMapElement *pelm = NULL;
-    while (elm && elm->key.hash <= key.hash) {
-        if(elm->key.hash == key.hash) {
-            int n = (key.len > elm->key.len) ? elm->key.len : key.len;
-            if (memcmp(elm->key.data, key.data, n) == 0) {
-                void *data = elm->data;
-                if (remove) {
-                    if (pelm) {
-                        pelm->next = elm->next;
-                    } else {
-                        map->map[slot] = elm->next;
-                    }
-                    alfree(map->allocator, elm->key.data);
-                    alfree(map->allocator, elm);
-                    map->count--;
-                }
-
-                return data;
-            }
-        }
-        pelm = elm;
-        elm = pelm->next;
-    }
-
-    return NULL;
-}
-
-void *ucx_map_get(UcxMap const *map, UcxKey key) {
-    return ucx_map_get_and_remove((UcxMap *)map, key, 0);
-}
-
-void *ucx_map_remove(UcxMap *map, UcxKey key) {
-    return ucx_map_get_and_remove(map, key, 1);
-}
-
-UcxKey ucx_key(const void *data, size_t len) {
-    UcxKey key;
-    key.data = data;
-    key.len = len;
-    key.hash = ucx_hash((const char*)data, len);
-    return key;
-}
-
-
-int ucx_hash(const char *data, size_t len) {
-    /* murmur hash 2 */
-
-    int m = 0x5bd1e995;
-    int r = 24;
-
-    int h = 25 ^ len;
-
-    int i = 0;
-    while (len >= 4) {
-        int k = data[i + 0] & 0xFF;
-        k |= (data[i + 1] & 0xFF) << 8;
-        k |= (data[i + 2] & 0xFF) << 16;
-        k |= (data[i + 3] & 0xFF) << 24;
-
-        k *= m;
-        k ^= k >> r;
-        k *= m;
-
-        h *= m;
-        h ^= k;
-
-        i += 4;
-        len -= 4;
-    }
-
-    switch (len) {
-        case 3: h ^= (data[i + 2] & 0xFF) << 16;
-        /* no break */
-        case 2: h ^= (data[i + 1] & 0xFF) << 8;
-        /* no break */
-        case 1: h ^= (data[i + 0] & 0xFF); h *= m;
-        /* no break */
-    }
-
-    h ^= h >> 13;
-    h *= m;
-    h ^= h >> 15;
-
-    return h;
-}
-
-UcxMapIterator ucx_map_iterator(UcxMap const *map) {
-    UcxMapIterator i;
-    i.map = map;
-    i.cur = NULL;
-    i.index = 0;
-    return i;
-}
-
-int ucx_map_iter_next(UcxMapIterator *i, UcxKey *key, void **elm) {
-    UcxMapElement *e = i->cur;
-    
-    if (e) {
-        e = e->next;
-    } else {
-        e = i->map->map[0];
-    }
-    
-    while (i->index < i->map->size) {
-        if (e) {
-            if (e->data) {
-                i->cur = e;
-                *elm = e->data;
-                key->data = e->key.data;
-                key->hash = e->key.hash;
-                key->len = e->key.len;
-                return 1;
-            }
-
-            e = e->next;
-        } else {
-            i->index++;
-            
-            if (i->index < i->map->size) {
-                e = i->map->map[i->index];
-            }
-        }
-    }
-    
-    return 0;
-}
-
-UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
-                      copy_func cpfnc, void* cpdata) {
-    return ucx_map_union_a(ucx_default_allocator(),
-            first, second, cpfnc, cpdata);
-}
-
-UcxMap* ucx_map_union_a(UcxAllocator *allocator,
-                        const UcxMap *first, const UcxMap *second,
-                        copy_func cpfnc, void* cpdata) {
-    UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata);
-    ucx_map_copy(second, result, cpfnc, cpdata);
-    return result;
-}
-
-UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
-                             copy_func cpfnc, void* cpdata) {
-    return ucx_map_intersection_a(ucx_default_allocator(),
-            first, second, cpfnc, cpdata);
-}
-
-UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
-                               const UcxMap *first, const UcxMap *second,
-                               copy_func cpfnc, void* cpdata) {
-    UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ?
-            first->size : second->size);
-
-    UcxMapIterator iter = ucx_map_iterator(first);
-    void* value;
-    UCX_MAP_FOREACH(key, value, iter) {
-        if (ucx_map_get(second, key)) {
-            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
-        }
-    }
-
-    return result;
-}
-
-UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
-                           copy_func cpfnc, void* cpdata) {
-    return ucx_map_difference_a(ucx_default_allocator(),
-            first, second, cpfnc, cpdata);
-}
-
-UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
-                             const UcxMap *first, const UcxMap *second,
-                             copy_func cpfnc, void* cpdata) {
-
-    UcxMap *result = ucx_map_new_a(allocator, first->size - second->count);
-
-    UcxMapIterator iter = ucx_map_iterator(first);
-    void* value;
-    UCX_MAP_FOREACH(key, value, iter) {
-        if (!ucx_map_get(second, key)) {
-            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
-        }
-    }
-
-    ucx_map_rehash(result);
-    return result;
-}
\ No newline at end of file
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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 "cx/map.h"
+#include <string.h>
+
+// <editor-fold desc="empty map implementation">
+
+static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {
+    // this is a noop, but MUST be implemented
+}
+
+static void *cx_empty_map_get(
+        __attribute__((__unused__)) const CxMap *map,
+        __attribute__((__unused__)) CxHashKey key
+) {
+    return NULL;
+}
+
+static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) {
+    return false;
+}
+
+static CxIterator cx_empty_map_iterator(
+        const struct cx_map_s *map,
+        __attribute__((__unused__)) enum cx_map_iterator_type type
+) {
+    CxIterator iter = {0};
+    iter.src_handle.c = map;
+    iter.base.valid = cx_empty_map_iter_valid;
+    return iter;
+}
+
+static struct cx_map_class_s cx_empty_map_class = {
+        cx_empty_map_noop,
+        cx_empty_map_noop,
+        NULL,
+        cx_empty_map_get,
+        NULL,
+        cx_empty_map_iterator
+};
+
+CxMap cx_empty_map = {
+        {
+                NULL,
+                NULL,
+                0,
+                0,
+                NULL,
+                NULL,
+                NULL,
+                false
+        },
+        &cx_empty_map_class
+};
+
+CxMap *const cxEmptyMap = &cx_empty_map;
+
+// </editor-fold>
+
+CxIterator cxMapMutIteratorValues(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+    it.base.mutating = true;
+    return it;
+}
+
+CxIterator cxMapMutIteratorKeys(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+    it.base.mutating = true;
+    return it;
+}
+
+CxIterator cxMapMutIterator(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+    it.base.mutating = true;
+    return it;
+}
--- a/ucx/mempool.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/mempool.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,212 +26,207 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/mempool.h"
-
-#include <stdlib.h>
+#include "cx/mempool.h"
+#include "cx/utils.h"
 #include <string.h>
-#include <stdio.h>
-#ifdef __cplusplus
-#define __STDC_FORMAT_MACROS
-#endif
-#include <inttypes.h>
 
-/** Capsule for destructible memory chunks. */
-typedef struct {
-    /** The destructor for the memory chunk. */
-    ucx_destructor destructor;
-    /**
-     * First byte of the memory chunk.
-     * Note, that the address <code>&amp;c</code> is also the address
-     * of the whole memory chunk.
-     */
-    char c;
-} ucx_memchunk;
-
-/** Capsule for data and its destructor. */
-typedef struct {
-    /** The destructor for the data. */
-    ucx_destructor destructor;
-    /** A pointer to the data. */
-    void           *ptr;
-} ucx_regdestr;
-
-#ifdef __cplusplus
-extern "C"
-#endif
-void ucx_mempool_shared_destr(void* ptr) {
-    ucx_regdestr *rd = (ucx_regdestr*)ptr;
-    rd->destructor(rd->ptr);
-}
+struct cx_mempool_memory_s {
+    /** The destructor. */
+    cx_destructor_func destructor;
+    /** The actual memory. */
+    char c[];
+};
 
-UcxMempool *ucx_mempool_new(size_t n) {
-    size_t poolsz;
-    if(ucx_szmul(n, sizeof(void*), &poolsz)) {
-        return NULL;
-    }
-    
-    UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool));
-    if (!pool) {
-        return NULL;
-    }
-    
-    pool->data = (void**) malloc(poolsz);
-    if (pool->data == NULL) {
-        free(pool);
-        return NULL;
-    }
-    
-    pool->ndata = 0;
-    pool->size = n;
-    
-    UcxAllocator *allocator = (UcxAllocator*)malloc(sizeof(UcxAllocator));
-    if(!allocator) {
-        free(pool->data);
-        free(pool);
-        return NULL;
-    }
-    allocator->malloc = (ucx_allocator_malloc)ucx_mempool_malloc;
-    allocator->calloc = (ucx_allocator_calloc)ucx_mempool_calloc;
-    allocator->realloc = (ucx_allocator_realloc)ucx_mempool_realloc;
-    allocator->free = (ucx_allocator_free)ucx_mempool_free;
-    allocator->pool = pool;
-    pool->allocator = allocator;
-    
-    return pool;
-}
+static void *cx_mempool_malloc(
+        void *p,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
 
-int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) {
-    if (newcap < pool->ndata) {
-        return 1;
-    }
-    
-    size_t newcapsz;
-    if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) {
-        return 1;
-    }
-    
-    void **data = (void**) realloc(pool->data, newcapsz);
-    if (data) {
-        pool->data = data; 
-        pool->size = newcap;
-        return 0;
-    } else {
-        return 1;
-    }
-}
-
-void *ucx_mempool_malloc(UcxMempool *pool, size_t n) {
-    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
-        return NULL;
-    }
-    
-    if (pool->ndata >= pool->size) {
-        size_t newcap = pool->size*2;
-        if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) {
+    if (pool->size >= pool->capacity) {
+        size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
+        struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*));
+        if (newdata == NULL) {
             return NULL;
         }
+        pool->data = newdata;
+        pool->capacity = newcap;
     }
 
-    void *p = malloc(sizeof(ucx_destructor) + n);
-    ucx_memchunk *mem = (ucx_memchunk*)p;
-    if (!mem) {
+    struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
+    if (mem == NULL) {
         return NULL;
     }
 
-    mem->destructor = NULL;
-    pool->data[pool->ndata] = mem;
-    pool->ndata++;
+    mem->destructor = pool->auto_destr;
+    pool->data[pool->size] = mem;
+    pool->size++;
 
-    return &(mem->c);
+    return mem->c;
 }
 
-void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) {
+static void *cx_mempool_calloc(
+        void *p,
+        size_t nelem,
+        size_t elsize
+) {
     size_t msz;
-    if(ucx_szmul(nelem, elsize, &msz)) {
+    if (cx_szmul(nelem, elsize, &msz)) {
         return NULL;
     }
-    
-    void *ptr = ucx_mempool_malloc(pool, msz);
-    if (!ptr) {
+    void *ptr = cx_mempool_malloc(p, msz);
+    if (ptr == NULL) {
         return NULL;
     }
     memset(ptr, 0, nelem * elsize);
     return ptr;
 }
 
-void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) {
-    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
-        return NULL;
-    }
-    
-    char *mem = ((char*)ptr) - sizeof(ucx_destructor);
-    char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor));
-    if (!newm) {
+static void *cx_mempool_realloc(
+        void *p,
+        void *ptr,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
+
+    struct cx_mempool_memory_s *mem, *newm;
+    mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
+    newm = realloc(mem, n + sizeof(cx_destructor_func));
+
+    if (newm == NULL) {
         return NULL;
     }
     if (mem != newm) {
-        for(size_t i=0 ; i < pool->ndata ; i++) {
-            if(pool->data[i] == mem) {
+        cx_for_n(i, pool->size) {
+            if (pool->data[i] == mem) {
                 pool->data[i] = newm;
-                return newm + sizeof(ucx_destructor);
+                return ((char*)newm) + sizeof(cx_destructor_func);
             }
         }
-        fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
-          (intptr_t)ptr, (intptr_t)pool);
         abort();
     } else {
-        return newm + sizeof(ucx_destructor);
+        return ptr;
     }
 }
 
-void ucx_mempool_free(UcxMempool *pool, void *ptr) {
-    ucx_memchunk *chunk = (ucx_memchunk*)((char*)ptr-sizeof(ucx_destructor));
-    for(size_t i=0 ; i<pool->ndata ; i++) {
-        if(chunk == pool->data[i]) {
-            if(chunk->destructor != NULL) {
-                chunk->destructor(&(chunk->c));
+static void cx_mempool_free(
+        void *p,
+        void *ptr
+) {
+    struct cx_mempool_s *pool = p;
+
+    struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
+            ((char *) ptr - sizeof(cx_destructor_func));
+
+    cx_for_n(i, pool->size) {
+        if (mem == pool->data[i]) {
+            if (mem->destructor) {
+                mem->destructor(mem->c);
             }
-            free(chunk);
-            size_t last_index = pool->ndata - 1;
-            if(i != last_index) {
+            free(mem);
+            size_t last_index = pool->size - 1;
+            if (i != last_index) {
                 pool->data[i] = pool->data[last_index];
                 pool->data[last_index] = NULL;
             }
-            pool->ndata--;
+            pool->size--;
             return;
         }
     }
-    fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
-            (intptr_t)ptr, (intptr_t)pool);
     abort();
 }
 
-void ucx_mempool_destroy(UcxMempool *pool) {
-    ucx_memchunk *chunk;
-    for(size_t i=0 ; i<pool->ndata ; i++) {
-        chunk = (ucx_memchunk*) pool->data[i];
-        if(chunk) {
-            if(chunk->destructor) {
-                chunk->destructor(&(chunk->c));
-            }
-            free(chunk);
+void cxMempoolDestroy(CxMempool *pool) {
+    struct cx_mempool_memory_s *mem;
+    cx_for_n(i, pool->size) {
+        mem = pool->data[i];
+        if (mem->destructor) {
+            mem->destructor(mem->c);
         }
+        free(mem);
     }
     free(pool->data);
-    free(pool->allocator);
+    free((void*) pool->allocator);
     free(pool);
 }
 
-void ucx_mempool_set_destr(void *ptr, ucx_destructor func) {
-    *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func;
+void cxMempoolSetDestructor(
+        void *ptr,
+        cx_destructor_func func
+) {
+    *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
+}
+
+struct cx_mempool_foreign_mem_s {
+    cx_destructor_func destr;
+    void* mem;
+};
+
+static void cx_mempool_destr_foreign_mem(void* ptr) {
+    struct cx_mempool_foreign_mem_s *fm = ptr;
+    fm->destr(fm->mem);
+}
+
+int cxMempoolRegister(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func destr
+) {
+    struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
+            pool,
+            sizeof(struct cx_mempool_foreign_mem_s)
+    );
+    if (fm == NULL) return 1;
+
+    fm->mem = memory;
+    fm->destr = destr;
+    *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+
+    return 0;
 }
 
-void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) {
-    ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc(
-            pool,
-            sizeof(ucx_regdestr));
-    rd->destructor = destr;
-    rd->ptr = ptr;
-    ucx_mempool_set_destr(rd, ucx_mempool_shared_destr);
+static cx_allocator_class cx_mempool_allocator_class = {
+        cx_mempool_malloc,
+        cx_mempool_realloc,
+        cx_mempool_calloc,
+        cx_mempool_free
+};
+
+CxMempool *cxMempoolCreate(
+        size_t capacity,
+        cx_destructor_func destr
+) {
+    size_t poolsize;
+    if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+        return NULL;
+    }
+
+    struct cx_mempool_s *pool =
+            malloc(sizeof(struct cx_mempool_s));
+    if (pool == NULL) {
+        return NULL;
+    }
+
+    CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
+    if (provided_allocator == NULL) {
+        free(pool);
+        return NULL;
+    }
+    provided_allocator->cl = &cx_mempool_allocator_class;
+    provided_allocator->data = pool;
+
+    pool->allocator = provided_allocator;
+
+    pool->data = malloc(poolsize);
+    if (pool->data == NULL) {
+        free(provided_allocator);
+        free(pool);
+        return NULL;
+    }
+
+    pool->size = 0;
+    pool->capacity = capacity;
+    pool->auto_destr = destr;
+
+    return (CxMempool *) pool;
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/printf.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,194 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/printf.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef CX_PRINTF_SBO_SIZE
+#define CX_PRINTF_SBO_SIZE 512
+#endif
+unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
+
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        const char *fmt,
+        ...
+) {
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = cx_vfprintf(stream, wfc, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        const char *fmt,
+        va_list ap
+) {
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret < 0) {
+        va_end(ap2);
+        return ret;
+    } else if (ret < CX_PRINTF_SBO_SIZE) {
+        va_end(ap2);
+        return (int) wfc(buf, 1, ret, stream);
+    } else {
+        int len = ret + 1;
+        char *newbuf = malloc(len);
+        if (!newbuf) {
+            va_end(ap2);
+            return -1;
+        }
+
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        va_end(ap2);
+        if (ret > 0) {
+            ret = (int) wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+}
+
+cxmutstr cx_asprintf_a(
+        const CxAllocator *allocator,
+        const char *fmt,
+        ...
+) {
+    va_list ap;
+    va_start(ap, fmt);
+    cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+cxmutstr cx_vasprintf_a(
+        const CxAllocator *a,
+        const char *fmt,
+        va_list ap
+) {
+    cxmutstr s;
+    s.ptr = NULL;
+    s.length = 0;
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) {
+        s.ptr = cxMalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t) ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else {
+        int len = ret + 1;
+        s.ptr = cxMalloc(a, len);
+        if (s.ptr) {
+            ret = vsnprintf(s.ptr, len, fmt, ap2);
+            if (ret < 0) {
+                free(s.ptr);
+                s.ptr = NULL;
+            } else {
+                s.length = (size_t) ret;
+            }
+        }
+    }
+    va_end(ap2);
+    return s;
+}
+
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(*str, *len, fmt, ap);
+    if ((unsigned) ret >= *len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxRealloc(alloc, *str, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *len = newlen;
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
+
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, *len, fmt, ap);
+    *str = buf;
+    if ((unsigned) ret >= *len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxMalloc(alloc, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *len = newlen;
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
--- a/ucx/properties.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,264 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/properties.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-UcxProperties *ucx_properties_new() {
-    UcxProperties *parser = (UcxProperties*)malloc(
-            sizeof(UcxProperties));
-    if(!parser) {
-        return NULL;
-    }
-    
-    parser->buffer = NULL;
-    parser->buflen = 0;
-    parser->pos = 0;
-    parser->tmp = NULL;
-    parser->tmplen = 0;
-    parser->tmpcap = 0;
-    parser->error = 0;
-    parser->delimiter = '=';
-    parser->comment1 = '#';
-    parser->comment2 = 0;
-    parser->comment3 = 0;   
-    
-    return parser;
-}
-
-void ucx_properties_free(UcxProperties *parser) {
-    if(parser->tmp) {
-        free(parser->tmp);
-    }
-    free(parser);
-}
-
-void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
-    parser->buffer = buf;
-    parser->buflen = len;
-    parser->pos = 0;
-}
-
-static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
-    if(parser->tmpcap - parser->tmplen < len) {
-        size_t newcap = parser->tmpcap + len + 64;
-        parser->tmp = (char*)realloc(parser->tmp, newcap);
-        parser->tmpcap = newcap;
-    }
-    memcpy(parser->tmp + parser->tmplen, buf, len);
-    parser->tmplen += len;
-}
-
-int ucx_properties_next(UcxProperties *parser, sstr_t *name, sstr_t *value)  {   
-    if(parser->tmplen > 0) {
-        char *buf = parser->buffer + parser->pos;
-        size_t len = parser->buflen - parser->pos;
-        sstr_t str = sstrn(buf, len);
-        sstr_t nl = sstrchr(str, '\n');
-        if(nl.ptr) {
-            size_t newlen = (size_t)(nl.ptr - buf) + 1;
-            parser_tmp_append(parser, buf, newlen);
-            // the tmp buffer contains exactly one line now
-            
-            char *orig_buf = parser->buffer;
-            size_t orig_len = parser->buflen;
-            
-            parser->buffer = parser->tmp;
-            parser->buflen = parser->tmplen;
-            parser->pos = 0;    
-            parser->tmp = NULL;
-            parser->tmpcap = 0;
-            parser->tmplen = 0;
-            // run ucx_properties_next with the tmp buffer as main buffer
-            int ret = ucx_properties_next(parser, name, value);
-            
-            // restore original buffer
-            parser->tmp = parser->buffer;
-            parser->buffer = orig_buf;
-            parser->buflen = orig_len;
-            parser->pos = newlen;
-            
-            /*
-             * if ret == 0 the tmp buffer contained just space or a comment
-             * we parse again with the original buffer to get a name/value
-             * or a new tmp buffer
-             */
-            return ret ? ret : ucx_properties_next(parser, name, value);
-        } else {
-            parser_tmp_append(parser, buf, len);
-            return 0;
-        }
-    } else if(parser->tmp) {
-        free(parser->tmp);
-        parser->tmp = NULL;
-    }
-    
-    char comment1 = parser->comment1;
-    char comment2 = parser->comment2;
-    char comment3 = parser->comment3;
-    char delimiter = parser->delimiter;
-    
-    // get one line and parse it
-    while(parser->pos < parser->buflen) {
-        char *buf = parser->buffer + parser->pos;
-        size_t len = parser->buflen - parser->pos;
-        
-        /*
-         * First we check if we have at least one line. We also get indices of
-         * delimiter and comment chars
-         */
-        size_t delimiter_index = 0;
-        size_t comment_index = 0;
-        int has_comment = 0;
-
-        size_t i = 0;
-        char c = 0;
-        for(;i<len;i++) {
-            c = buf[i];
-            if(c == comment1 || c == comment2 || c == comment3) {
-                if(comment_index == 0) {
-                    comment_index = i;
-                    has_comment = 1;
-                }
-            } else if(c == delimiter) {
-                if(delimiter_index == 0 && !has_comment) {
-                    delimiter_index = i;
-                }
-            } else if(c == '\n') {
-                break;
-            }
-        }
-
-        if(c != '\n') {
-            // we don't have enough data for a line
-            // store remaining bytes in temporary buffer for next round
-            parser->tmpcap = len + 128;
-            parser->tmp = (char*)malloc(parser->tmpcap);
-            parser->tmplen = len;
-            memcpy(parser->tmp, buf, len);
-            return 0;
-        }
-        
-        sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i);
-        // check line
-        if(delimiter_index == 0) {
-            line = sstrtrim(line);
-            if(line.length != 0) {
-                parser->error = 1;
-            }
-        } else {
-            sstr_t n = sstrn(buf, delimiter_index);
-            sstr_t v = sstrn(
-                    buf + delimiter_index + 1,
-                    line.length - delimiter_index - 1); 
-            n = sstrtrim(n);
-            v = sstrtrim(v);
-            if(n.length != 0 || v.length != 0) {
-                *name = n;
-                *value = v;
-                parser->pos += i + 1;
-                return 1;
-            } else {
-                parser->error = 1;
-            }
-        }
-        
-        parser->pos += i + 1;
-    }
-    
-    return 0;
-}
-
-int ucx_properties2map(UcxProperties *parser, UcxMap *map) {
-    sstr_t name;
-    sstr_t value;
-    while(ucx_properties_next(parser, &name, &value)) {
-        value = sstrdup_a(map->allocator, value);
-        if(!value.ptr) {
-            return 1;
-        }
-        if(ucx_map_sstr_put(map, name, value.ptr)) {
-            alfree(map->allocator, value.ptr);
-            return 1;
-        }
-    }
-    if (parser->error) {
-        return parser->error;
-    } else {
-        return 0;
-    }
-}
-
-// buffer size is documented - change doc, when you change bufsize!
-#define UCX_PROPLOAD_BUFSIZE  1024
-int ucx_properties_load(UcxMap *map, FILE *file) {
-    UcxProperties *parser = ucx_properties_new();
-    if(!(parser && map && file)) {
-        return 1;
-    }
-    
-    int error = 0;
-    size_t r;
-    char buf[UCX_PROPLOAD_BUFSIZE];
-    while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
-        ucx_properties_fill(parser, buf, r);
-        error = ucx_properties2map(parser, map);
-        if (error) {
-            break;
-        }
-    }
-    ucx_properties_free(parser);
-    return error;
-}
-
-int ucx_properties_store(UcxMap *map, FILE *file) {
-    UcxMapIterator iter = ucx_map_iterator(map);
-    void *v;
-    sstr_t value;
-    size_t written;
-
-    UCX_MAP_FOREACH(k, v, iter) {
-        value = sstr((char*)v);
-
-        written = 0;
-        written += fwrite(k.data, 1, k.len, file);
-        written += fwrite(" = ", 1, 3, file);
-        written += fwrite(value.ptr, 1, value.length, file);
-        written += fwrite("\n", 1, 1, file);
-
-        if (written != k.len + value.length + 4) {
-            return 1;
-        }
-    }
-
-    return 0;
-}
-
--- a/ucx/stack.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/stack.h"
-
-#include <string.h>
-
-static size_t ucx_stack_align(size_t n) {
-    int align = n % sizeof(void*);
-    if (align) {
-        n += sizeof(void*) - align;
-    }
-    return n;
-}
-
-void ucx_stack_init(UcxStack *stack, char* space, size_t size) {
-    stack->size = size - size % sizeof(void*);
-    stack->space = space;
-    stack->top = NULL;
-    
-    stack->allocator.pool = stack;
-    stack->allocator.malloc = (ucx_allocator_malloc) ucx_stack_malloc;
-    stack->allocator.calloc = (ucx_allocator_calloc) ucx_stack_calloc;
-    stack->allocator.realloc = (ucx_allocator_realloc) ucx_stack_realloc;
-    stack->allocator.free = (ucx_allocator_free) ucx_stack_free;
-}
-
-void *ucx_stack_malloc(UcxStack *stack, size_t n) {
-
-    if (ucx_stack_avail(stack) < ucx_stack_align(n)) {
-        return NULL;
-    } else {
-        char *prev = stack->top;
-        if (stack->top) {
-            stack->top += ucx_stack_align(ucx_stack_topsize(stack));
-        } else {
-            stack->top = stack->space;
-        }
-        
-        ((struct ucx_stack_metadata*)stack->top)->prev = prev;
-        ((struct ucx_stack_metadata*)stack->top)->size = n;
-        stack->top += sizeof(struct ucx_stack_metadata);
-        
-        return stack->top;
-    }
-}
-
-void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize) {
-    void *mem = ucx_stack_malloc(stack, nelem*elsize);
-    memset(mem, 0, nelem*elsize);
-    return mem;
-}
-
-void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n) {
-    if (ptr == stack->top) {
-        if (stack->size - (stack->top - stack->space) < ucx_stack_align(n)) {
-            return NULL;
-        } else {
-            ((struct ucx_stack_metadata*)stack->top - 1)->size = n;
-            return ptr;
-        }
-    } else {
-        if (ucx_stack_align(((struct ucx_stack_metadata*)ptr - 1)->size) <
-                ucx_stack_align(n)) {
-            void *nptr = ucx_stack_malloc(stack, n);
-            if (nptr) {
-                memcpy(nptr, ptr, n);
-                ucx_stack_free(stack, ptr);
-                
-                return nptr;
-            } else {
-                return NULL;
-            }
-        } else {
-            ((struct ucx_stack_metadata*)ptr - 1)->size = n;
-            return ptr;
-        }
-    }
-}
-
-void ucx_stack_free(UcxStack *stack, void *ptr) {
-    if (ptr == stack->top) {
-        stack->top = ((struct ucx_stack_metadata*) stack->top - 1)->prev;
-    } else {
-        struct ucx_stack_metadata *next = (struct ucx_stack_metadata*)(
-            (char*)ptr +
-            ucx_stack_align(((struct ucx_stack_metadata*) ptr - 1)->size)
-        );
-        next->prev = ((struct ucx_stack_metadata*) ptr - 1)->prev;
-    }
-}
-
-void ucx_stack_popn(UcxStack *stack, void *dest, size_t n) {
-    if (ucx_stack_empty(stack)) {
-        return;
-    }
-    
-    if (dest) {
-        size_t len = ucx_stack_topsize(stack);
-        if (len > n) {
-            len = n;
-        }
-
-        memcpy(dest, stack->top, len);
-    }
-    
-    ucx_stack_free(stack, stack->top);
-}
-
-size_t ucx_stack_avail(UcxStack *stack) {
-    size_t avail = ((stack->top ? (stack->size
-                    - (stack->top - stack->space)
-                    - ucx_stack_align(ucx_stack_topsize(stack)))
-                    : stack->size));
-    
-    if (avail > sizeof(struct ucx_stack_metadata)) {
-        return avail - sizeof(struct ucx_stack_metadata);
-    } else {
-        return 0;
-    }
-}
-
-void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) {
-    void *space = ucx_stack_malloc(stack, n);
-    if (space) {
-        memcpy(space, data, n);
-    }
-    return space;
-}
-
-void *ucx_stack_pusharr(UcxStack *stack,
-        size_t nelem, size_t elsize, const void *data) {
-    
-    // skip the memset by using malloc
-    void *space = ucx_stack_malloc(stack, nelem*elsize);
-    if (space) {
-        memcpy(space, data, nelem*elsize);
-    }
-    return space;
-}
--- a/ucx/string.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/string.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,63 +26,71 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/string.h"
+#include "cx/string.h"
+#include "cx/utils.h"
 
-#include "ucx/allocator.h"
-
-#include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
-#include <stdint.h>
 #include <ctype.h>
 
 #ifndef _WIN32
-#include <strings.h> /* for strncasecmp() */
-#endif /* _WIN32 */
+
+#include <strings.h> // for strncasecmp()
 
-sstr_t sstr(char *cstring) {
-    sstr_t string;
-    string.ptr = cstring;
-    string.length = strlen(cstring);
-    return string;
+#endif // _WIN32
+
+cxmutstr cx_mutstr(char *cstring) {
+    return (cxmutstr) {cstring, strlen(cstring)};
 }
 
-sstr_t sstrn(char *cstring, size_t length) {
-    sstr_t string;
-    string.ptr = cstring;
-    string.length = length;
-    return string;
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+) {
+    return (cxmutstr) {cstring, length};
+}
+
+cxstring cx_str(const char *cstring) {
+    return (cxstring) {cstring, strlen(cstring)};
+}
+
+cxstring cx_strn(
+        const char *cstring,
+        size_t length
+) {
+    return (cxstring) {cstring, length};
 }
 
-scstr_t scstr(const char *cstring) {
-    scstr_t string;
-    string.ptr = cstring;
-    string.length = strlen(cstring);
-    return string;
+cxstring cx_strcast(cxmutstr str) {
+    return (cxstring) {str.ptr, str.length};
 }
 
-scstr_t scstrn(const char *cstring, size_t length) {
-    scstr_t string;
-    string.ptr = cstring;
-    string.length = length;
-    return string;
+void cx_strfree(cxmutstr *str) {
+    free(str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
 }
 
+void cx_strfree_a(
+        const CxAllocator *alloc,
+        cxmutstr *str
+) {
+    cxFree(alloc, str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
+}
 
-size_t scstrnlen(size_t n, ...) {
-    if (n == 0) return 0;
-    
+size_t cx_strlen(
+        size_t count,
+        ...
+) {
+    if (count == 0) return 0;
+
     va_list ap;
-    va_start(ap, n);
-    
+    va_start(ap, count);
     size_t size = 0;
-
-    for (size_t i = 0 ; i < n ; i++) {
-        scstr_t str = va_arg(ap, scstr_t);
-        if(SIZE_MAX - str.length < size) {
-            size = SIZE_MAX;
-            break;
-        }
+    cx_for_n(i, count) {
+        cxstring str = va_arg(ap, cxstring);
         size += str.length;
     }
     va_end(ap);
@@ -90,410 +98,341 @@
     return size;
 }
 
-static sstr_t sstrvcat_a(
-        UcxAllocator *a,
+cxmutstr cx_strcat_ma(
+        const CxAllocator *alloc,
+        cxmutstr str,
         size_t count,
-        scstr_t s1,
-        va_list ap) {
-    sstr_t str;
-    str.ptr = NULL;
-    str.length = 0;
-    if(count < 2) {
-        return str;
-    }
-    
-    scstr_t s2 = va_arg (ap, scstr_t);
-    
-    if(((size_t)-1) - s1.length < s2.length) {
-        return str;
-    }
-    
-    scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t));
-    if(!strings) {
-        return str;
-    }
-    
+        ...
+) {
+    if (count == 0) return str;
+
+    cxstring *strings = calloc(count, sizeof(cxstring));
+    if (!strings) abort();
+
+    va_list ap;
+    va_start(ap, count);
+
     // get all args and overall length
-    strings[0] = s1;
-    strings[1] = s2;
-    size_t slen = s1.length + s2.length;
-    int error = 0;
-    for (size_t i=2;i<count;i++) {
-        scstr_t s = va_arg (ap, scstr_t);
+    size_t slen = str.length;
+    cx_for_n(i, count) {
+        cxstring s = va_arg (ap, cxstring);
         strings[i] = s;
-        if(((size_t)-1) - s.length < slen) {
-            error = 1;
-            break;
-        }
         slen += s.length;
     }
-    if(error) {
-        free(strings);
-        return str;
+    va_end(ap);
+
+    // reallocate or create new string
+    if (str.ptr == NULL) {
+        str.ptr = cxMalloc(alloc, slen + 1);
+    } else {
+        str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
     }
-    
-    // create new string
-    str.ptr = (char*) almalloc(a, slen + 1);
+    if (str.ptr == NULL) abort();
+
+    // concatenate strings
+    size_t pos = str.length;
     str.length = slen;
-    if(!str.ptr) {
-        free(strings);
-        str.length = 0;
-        return str;
-    }
-    
-    // concatenate strings
-    size_t pos = 0;
-    for (size_t i=0;i<count;i++) {
-        scstr_t s = strings[i];
+    cx_for_n(i, count) {
+        cxstring s = strings[i];
         memcpy(str.ptr + pos, s.ptr, s.length);
         pos += s.length;
     }
-    
+
+    // terminate string
     str.ptr[str.length] = '\0';
-    
+
+    // free temporary array
     free(strings);
-    
+
     return str;
 }
 
-sstr_t scstrcat(size_t count, scstr_t s1, ...) {
-    va_list ap;
-    va_start(ap, s1);
-    sstr_t s = sstrvcat_a(ucx_default_allocator(), count, s1, ap);
-    va_end(ap);
-    return s;
-}
-
-sstr_t scstrcat_a(UcxAllocator *a, size_t count, scstr_t s1, ...) {
-    va_list ap;
-    va_start(ap, s1);
-    sstr_t s = sstrvcat_a(a, count, s1, ap);
-    va_end(ap);
-    return s;
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+) {
+    return cx_strsubsl(string, start, string.length - start);
 }
 
-static int ucx_substring(
-        size_t str_length,
-        size_t start,
-        size_t length,
-        size_t *newlen,
-        size_t *newpos)
-{
-    *newlen = 0;
-    *newpos = 0;
-    
-    if(start > str_length) {
-        return 0;
-    }
-    
-    if(length > str_length - start) {
-        length = str_length - start;
-    }
-    *newlen = length;
-    *newpos = start;
-    return 1;
-}
-
-sstr_t sstrsubs(sstr_t s, size_t start) {
-    return sstrsubsl (s, start, s.length-start);
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+) {
+    return cx_strsubsl_m(string, start, string.length - start);
 }
 
-sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) {
-    size_t pos;
-    sstr_t ret = { NULL, 0 };
-    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
-        ret.ptr = s.ptr + pos;
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+) {
+    if (start > string.length) {
+        return (cxstring) {NULL, 0};
     }
-    return ret;
-}
 
-scstr_t scstrsubs(scstr_t string, size_t start) {
-    return scstrsubsl(string, start, string.length-start);
-}
+    size_t rem_len = string.length - start;
+    if (length > rem_len) {
+        length = rem_len;
+    }
 
-scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) {
-    size_t pos;
-    scstr_t ret = { NULL, 0 };
-    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
-        ret.ptr = s.ptr + pos;
-    }
-    return ret;
+    return (cxstring) {string.ptr + start, length};
 }
 
-
-static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) {
-    for(size_t i=0;i<length;i++) {
-        if(str[i] == chr) {
-            *pos = i;
-            return 1;
-        }
-    }
-    return 0;
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+) {
+    cxstring result = cx_strsubsl(cx_strcast(string), start, length);
+    return (cxmutstr) {(char *) result.ptr, result.length};
 }
 
-static int ucx_strrchr(const char *str, size_t length, int chr, size_t *pos) {
-    if(length > 0) {
-        for(size_t i=length ; i>0 ; i--) {
-            if(str[i-1] == chr) {
-                *pos = i-1;
-                return 1;
-            }
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    // TODO: improve by comparing multiple bytes at once
+    cx_for_n(i, string.length) {
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
         }
     }
-    return 0;
+    return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
 }
 
-sstr_t sstrchr(sstr_t s, int c) {
-    size_t pos = 0;
-    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
-        return sstrsubs(s, pos);
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    size_t i = string.length;
+    while (i > 0) {
+        i--;
+        // TODO: improve by comparing multiple bytes at once
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
+        }
     }
-    return sstrn(NULL, 0);
+    return (cxstring) {NULL, 0};
 }
 
-sstr_t sstrrchr(sstr_t s, int c) {
-    size_t pos = 0;
-    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
-        return sstrsubs(s, pos);
-    }
-    return sstrn(NULL, 0);
-}
-
-scstr_t scstrchr(scstr_t s, int c) {
-    size_t pos = 0;
-    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
-        return scstrsubs(s, pos);
-    }
-    return scstrn(NULL, 0);
-}
-
-scstr_t scstrrchr(scstr_t s, int c) {
-    size_t pos = 0;
-    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
-        return scstrsubs(s, pos);
-    }
-    return scstrn(NULL, 0);
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strrchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
 }
 
-#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \
-    ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index])
-
-#define ptable_w(useheap, ptable, index, src) do {\
-    if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\
-    else ((size_t*)ptable)[index] = src;\
-    } while (0);
-
+#ifndef CX_STRSTR_SBO_SIZE
+#define CX_STRSTR_SBO_SIZE 512
+#endif
+unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
 
-static const char* ucx_strstr(
-        const char *str,
-        size_t length,
-        const char *match,
-        size_t matchlen,
-        size_t *newlen)
-{
-    *newlen = length;
-    if (matchlen == 0) {
-        return str;
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+) {
+    if (needle.length == 0) {
+        return haystack;
     }
-    
-    const char *result = NULL;
-    size_t resultlen = 0;
-    
+
+    // optimize for single-char needles
+    if (needle.length == 1) {
+        return cx_strchr(haystack, *needle.ptr);
+    }
+
     /*
      * IMPORTANT:
-     * our prefix table contains the prefix length PLUS ONE
-     * this is our decision, because we want to use the full range of size_t
-     * the original algorithm needs a (-1) at one single place
-     * and we want to avoid that
+     * Our prefix table contains the prefix length PLUS ONE
+     * this is our decision, because we want to use the full range of size_t.
+     * The original algorithm needs a (-1) at one single place,
+     * and we want to avoid that.
      */
-    
-    /* static prefix table */
-    static uint8_t s_prefix_table[256];
-    
-    /* check pattern length and use appropriate prefix table */
-    /* if the pattern exceeds static prefix table, allocate on the heap */
-    register int useheap = matchlen > 255;
-    register void* ptable = useheap ?
-        calloc(matchlen+1, sizeof(size_t)): s_prefix_table;
-    
-    /* keep counter in registers */
+
+    // local prefix table
+    size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
+
+    // check needle length and use appropriate prefix table
+    // if the pattern exceeds static prefix table, allocate on the heap
+    bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+    register size_t *ptable = useheap ? calloc(needle.length + 1,
+                                               sizeof(size_t)) : s_prefix_table;
+
+    // keep counter in registers
     register size_t i, j;
-    
-    /* fill prefix table */
-    i = 0; j = 0;
-    ptable_w(useheap, ptable, i, j);
-    while (i < matchlen) {
-        while (j >= 1 && match[j-1] != match[i]) {
-            ptable_r(j, useheap, ptable, j-1);
+
+    // fill prefix table
+    i = 0;
+    j = 0;
+    ptable[i] = j;
+    while (i < needle.length) {
+        while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
+            j = ptable[j - 1];
         }
-        i++; j++;
-        ptable_w(useheap, ptable, i, j);
+        i++;
+        j++;
+        ptable[i] = j;
     }
 
-    /* search */
-    i = 0; j = 1;
-    while (i < length) {
-        while (j >= 1 && str[i] != match[j-1]) {
-            ptable_r(j, useheap, ptable, j-1);
+    // search
+    cxstring result = {NULL, 0};
+    i = 0;
+    j = 1;
+    while (i < haystack.length) {
+        while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
+            j = ptable[j - 1];
         }
-        i++; j++;
-        if (j-1 == matchlen) {
-            size_t start = i - matchlen;
-            result = str + start;
-            resultlen = length - start;
+        i++;
+        j++;
+        if (j - 1 == needle.length) {
+            size_t start = i - needle.length;
+            result.ptr = haystack.ptr + start;
+            result.length = haystack.length - start;
             break;
         }
     }
 
-    /* if prefix table was allocated on the heap, free it */
+    // if prefix table was allocated on the heap, free it
     if (ptable != s_prefix_table) {
         free(ptable);
     }
-    
-    *newlen = resultlen;
-    return result;
-}
-
-sstr_t scstrsstr(sstr_t string, scstr_t match) {
-    sstr_t result;
-    
-    size_t reslen;
-    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
-    if(!resstr) {
-        result.ptr = NULL;
-        result.length = 0;
-        return result;
-    }
-    
-    size_t pos = resstr - string.ptr;
-    result.ptr = string.ptr + pos;
-    result.length = reslen;
-    
-    return result;
-}
-
-scstr_t scstrscstr(scstr_t string, scstr_t match) {
-    scstr_t result;
-    
-    size_t reslen;
-    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
-    if(!resstr) {
-        result.ptr = NULL;
-        result.length = 0;
-        return result;
-    }
-    
-    size_t pos = resstr - string.ptr;
-    result.ptr = string.ptr + pos;
-    result.length = reslen;
-    
-    return result;
-}
-
-#undef ptable_r
-#undef ptable_w
-
-sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) {
-    return scstrsplit_a(ucx_default_allocator(), s, d, n);
-}
-
-sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) {
-    if (s.length == 0 || d.length == 0) {
-        *n = -1;
-        return NULL;
-    }
-    
-    /* special cases: delimiter is at least as large as the string */
-    if (d.length >= s.length) {
-        /* exact match */
-        if (sstrcmp(s, d) == 0) {
-            *n = 0;
-            return NULL;
-        } else /* no match possible */ {
-            *n = 1;
-            sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t));
-            if(result) {
-                *result = sstrdup_a(allocator, s);
-            } else {
-                *n = -2;
-            }
-            return result;
-        }
-    }
-    
-    ssize_t nmax = *n;
-    size_t arrlen = 16;
-    sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t));
-
-    if (result) {
-        scstr_t curpos = s;
-        ssize_t j = 1;
-        while (1) {
-            scstr_t match;
-            /* optimize for one byte delimiters */
-            if (d.length == 1) {
-                match = curpos;
-                for (size_t i = 0 ; i < curpos.length ; i++) {
-                    if (curpos.ptr[i] == *(d.ptr)) {
-                        match.ptr = curpos.ptr + i;
-                        break;
-                    }
-                    match.length--;
-                }
-            } else {
-                match = scstrscstr(curpos, d);
-            }
-            if (match.length > 0) {
-                /* is this our last try? */
-                if (nmax == 0 || j < nmax) {
-                    /* copy the current string to the array */
-                    scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr);
-                    result[j-1] = sstrdup_a(allocator, item);
-                    size_t processed = item.length + d.length;
-                    curpos.ptr += processed;
-                    curpos.length -= processed;
-
-                    /* allocate memory for the next string */
-                    j++;
-                    if (j > arrlen) {
-                        arrlen *= 2;
-                        size_t reallocsz;
-                        sstr_t* reallocated = NULL;
-                        if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) {
-                            reallocated = (sstr_t*) alrealloc(
-                                    allocator, result, reallocsz);
-                        }
-                        if (reallocated) {
-                            result = reallocated;
-                        } else {
-                            for (ssize_t i = 0 ; i < j-1 ; i++) {
-                                alfree(allocator, result[i].ptr);
-                            }
-                            alfree(allocator, result);
-                            *n = -2;
-                            return NULL;
-                        }
-                    }
-                } else {
-                    /* nmax reached, copy the _full_ remaining string */
-                    result[j-1] = sstrdup_a(allocator, curpos);
-                    break;
-                }
-            } else {
-                /* no more matches, copy last string */
-                result[j-1] = sstrdup_a(allocator, curpos);
-                break;
-            }
-        }
-        *n = j;
-    } else {
-        *n = -2;
-    }
 
     return result;
 }
 
-int scstrcmp(scstr_t s1, scstr_t s2) {
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+) {
+    cxstring result = cx_strstr(cx_strcast(haystack), needle);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+) {
+    // special case: output limit is zero
+    if (limit == 0) return 0;
+
+    // special case: delimiter is empty
+    if (delim.length == 0) {
+        output[0] = string;
+        return 1;
+    }
+
+    // special cases: delimiter is at least as large as the string
+    if (delim.length >= string.length) {
+        // exact match
+        if (cx_strcmp(string, delim) == 0) {
+            output[0] = cx_strn(string.ptr, 0);
+            output[1] = cx_strn(string.ptr + string.length, 0);
+            return 2;
+        } else {
+            // no match possible
+            output[0] = string;
+            return 1;
+        }
+    }
+
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                // copy the current string to the array
+                cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
+                output[n - 1] = item;
+                size_t processed = item.length + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached, copy the _full_ remaining string
+                output[n - 1] = curpos;
+                break;
+            }
+        } else {
+            // no more matches, copy last string
+            output[n - 1] = curpos;
+            break;
+        }
+    }
+
+    return n;
+}
+
+size_t cx_strsplit_a(
+        const CxAllocator *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+) {
+    // find out how many splits we're going to make and allocate memory
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                size_t processed = match.ptr - curpos.ptr + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached
+                break;
+            }
+        } else {
+            // no more matches
+            break;
+        }
+    }
+    *output = cxCalloc(allocator, n, sizeof(cxstring));
+    return cx_strsplit(string, delim, n, *output);
+}
+
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+) {
+    return cx_strsplit(cx_strcast(string),
+                       delim, limit, (cxstring *) output);
+}
+
+size_t cx_strsplit_ma(
+        const CxAllocator *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+) {
+    return cx_strsplit_a(allocator, cx_strcast(string),
+                         delim, limit, (cxstring **) output);
+}
+
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+) {
     if (s1.length == s2.length) {
         return memcmp(s1.ptr, s2.ptr, s1.length);
     } else if (s1.length > s2.length) {
@@ -503,7 +442,10 @@
     }
 }
 
-int scstrcasecmp(scstr_t s1, scstr_t s2) {
+int cx_strcasecmp(
+        cxstring s1,
+        cxstring s2
+) {
     if (s1.length == s2.length) {
 #ifdef _WIN32
         return _strnicmp(s1.ptr, s2.ptr, s1.length);
@@ -517,216 +459,186 @@
     }
 }
 
-sstr_t scstrdup(scstr_t s) {
-    return sstrdup_a(ucx_default_allocator(), s);
+int cx_strcmp_p(
+        const void *s1,
+        const void *s2
+) {
+    const cxstring *left = s1;
+    const cxstring *right = s2;
+    return cx_strcmp(*left, *right);
+}
+
+int cx_strcasecmp_p(
+        const void *s1,
+        const void *s2
+) {
+    const cxstring *left = s1;
+    const cxstring *right = s2;
+    return cx_strcasecmp(*left, *right);
 }
 
-sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) {
-    sstr_t newstring;
-    newstring.ptr = (char*)almalloc(allocator, s.length + 1);
-    if (newstring.ptr) {
-        newstring.length = s.length;
-        newstring.ptr[newstring.length] = 0;
-        
-        memcpy(newstring.ptr, s.ptr, s.length);
-    } else {
-        newstring.length = 0;
+cxmutstr cx_strdup_a(
+        const CxAllocator *allocator,
+        cxstring string
+) {
+    cxmutstr result = {
+            cxMalloc(allocator, string.length + 1),
+            string.length
+    };
+    if (result.ptr == NULL) {
+        result.length = 0;
+        return result;
     }
-    
-    return newstring;
+    memcpy(result.ptr, string.ptr, string.length);
+    result.ptr[string.length] = '\0';
+    return result;
+}
+
+cxstring cx_strtrim(cxstring string) {
+    cxstring result = string;
+    // TODO: optimize by comparing multiple bytes at once
+    while (result.length > 0 && isspace(*result.ptr)) {
+        result.ptr++;
+        result.length--;
+    }
+    while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
+        result.length--;
+    }
+    return result;
 }
 
+cxmutstr cx_strtrim_m(cxmutstr string) {
+    cxstring result = cx_strtrim(cx_strcast(string));
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
 
-static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) {
-    const char *newptr = s;
-    size_t length = len;
-    
-    while(length > 0 && isspace(*newptr)) {
-        newptr++;
-        length--;
-    }
-    while(length > 0 && isspace(newptr[length-1])) {
-        length--;
-    }
-    
-    *newlen = length;
-    return newptr - s;
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+    return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+}
+
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+    return memcmp(string.ptr + string.length - suffix.length,
+                  suffix.ptr, suffix.length) == 0;
 }
 
-sstr_t sstrtrim(sstr_t string) {
-    sstr_t newstr;
-    newstr.ptr = string.ptr
-                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
-    return newstr;
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#else
+    return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#endif
 }
 
-scstr_t scstrtrim(scstr_t string) {
-    scstr_t newstr;
-    newstr.ptr = string.ptr
-                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
-    return newstr;
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr+string.length-suffix.length,
+                  suffix.ptr, suffix.length) == 0;
+#else
+    return strncasecmp(string.ptr + string.length - suffix.length,
+                       suffix.ptr, suffix.length) == 0;
+#endif
 }
 
-int scstrprefix(scstr_t string, scstr_t prefix) {
-    if (string.length == 0) {
-        return prefix.length == 0;
-    }
-    if (prefix.length == 0) {
-        return 1;
-    }
-    
-    if (prefix.length > string.length) {
-        return 0;
-    } else {
-        return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+void cx_strlower(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) tolower(string.ptr[i]);
     }
 }
 
-int scstrsuffix(scstr_t string, scstr_t suffix) {
-    if (string.length == 0) {
-        return suffix.length == 0;
-    }
-    if (suffix.length == 0) {
-        return 1;
-    }
-    
-    if (suffix.length > string.length) {
-        return 0;
-    } else {
-        return memcmp(string.ptr+string.length-suffix.length,
-            suffix.ptr, suffix.length) == 0;
-    }
-}
-
-int scstrcaseprefix(scstr_t string, scstr_t prefix) {
-    if (string.length == 0) {
-        return prefix.length == 0;
-    }
-    if (prefix.length == 0) {
-        return 1;
-    }
-    
-    if (prefix.length > string.length) {
-        return 0;
-    } else {
-        scstr_t subs = scstrsubsl(string, 0, prefix.length);
-        return scstrcasecmp(subs, prefix) == 0;
+void cx_strupper(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) toupper(string.ptr[i]);
     }
 }
 
-int scstrcasesuffix(scstr_t string, scstr_t suffix) {
-    if (string.length == 0) {
-        return suffix.length == 0;
-    }
-    if (suffix.length == 0) {
-        return 1;
-    }
-    
-    if (suffix.length > string.length) {
-        return 0;
-    } else {
-        scstr_t subs = scstrsubs(string, string.length-suffix.length);
-        return scstrcasecmp(subs, suffix) == 0;
-    }
-}
-
-sstr_t scstrlower(scstr_t string) {
-    sstr_t ret = sstrdup(string);
-    for (size_t i = 0; i < ret.length ; i++) {
-        ret.ptr[i] = tolower(ret.ptr[i]);
-    }
-    return ret;
-}
+#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
+#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
+#endif
 
-sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) {
-    sstr_t ret = sstrdup_a(allocator, string);
-    for (size_t i = 0; i < ret.length ; i++) {
-        ret.ptr[i] = tolower(ret.ptr[i]);
-    }
-    return ret;
-}
-
-sstr_t scstrupper(scstr_t string) {
-    sstr_t ret = sstrdup(string);
-    for (size_t i = 0; i < ret.length ; i++) {
-        ret.ptr[i] = toupper(ret.ptr[i]);
-    }
-    return ret;
-}
-
-sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) {
-    sstr_t ret = sstrdup_a(allocator, string);
-    for (size_t i = 0; i < ret.length ; i++) {
-        ret.ptr[i] = toupper(ret.ptr[i]);
-    }
-    return ret;
-}
-
-#define REPLACE_INDEX_BUFFER_MAX 100
-
-struct scstrreplace_ibuf {
-    size_t* buf;
-    unsigned int len; /* small indices */
-    struct scstrreplace_ibuf* next;
+struct cx_strreplace_ibuf {
+    size_t *buf;
+    struct cx_strreplace_ibuf *next;
+    unsigned int len;
 };
 
-static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) {
+static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
     while (buf) {
-        struct scstrreplace_ibuf *next = buf->next;
+        struct cx_strreplace_ibuf *next = buf->next;
         free(buf->buf);
         free(buf);
         buf = next;
     }
 }
 
-sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
-                     scstr_t pattern, scstr_t replacement, size_t replmax) {
+cxmutstr cx_strreplacen_a(
+        const CxAllocator *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+) {
 
     if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
-        return sstrdup(str);
+        return cx_strdup_a(allocator, str);
 
-    /* Compute expected buffer length */
+    // Compute expected buffer length
     size_t ibufmax = str.length / pattern.length;
     size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
-    if (ibuflen > REPLACE_INDEX_BUFFER_MAX) {
-        ibuflen = REPLACE_INDEX_BUFFER_MAX;
+    if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
+        ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
     }
 
-    /* Allocate first index buffer */
-    struct scstrreplace_ibuf *firstbuf, *curbuf;
-    firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf));
-    if (!firstbuf) return sstrn(NULL, 0);
+    // Allocate first index buffer
+    struct cx_strreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
+    if (!firstbuf) return cx_mutstrn(NULL, 0);
     firstbuf->buf = calloc(ibuflen, sizeof(size_t));
     if (!firstbuf->buf) {
         free(firstbuf);
-        return sstrn(NULL, 0);
+        return cx_mutstrn(NULL, 0);
     }
 
-    /* Search occurrences */
-    scstr_t searchstr = str;
+    // Search occurrences
+    cxstring searchstr = str;
     size_t found = 0;
     do {
-        scstr_t match = scstrscstr(searchstr, pattern);
+        cxstring match = cx_strstr(searchstr, pattern);
         if (match.length > 0) {
-            /* Allocate next buffer in chain, if required */
+            // Allocate next buffer in chain, if required
             if (curbuf->len == ibuflen) {
-                struct scstrreplace_ibuf *nextbuf =
-                        calloc(1, sizeof(struct scstrreplace_ibuf));
+                struct cx_strreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct cx_strreplace_ibuf));
                 if (!nextbuf) {
-                    scstrrepl_free_ibuf(firstbuf);
-                    return sstrn(NULL, 0);
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
                 }
                 nextbuf->buf = calloc(ibuflen, sizeof(size_t));
                 if (!nextbuf->buf) {
                     free(nextbuf);
-                    scstrrepl_free_ibuf(firstbuf);
-                    return sstrn(NULL, 0);
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
                 }
                 curbuf->next = nextbuf;
                 curbuf = nextbuf;
             }
 
-            /* Record match index */
+            // Record match index
             found++;
             size_t idx = match.ptr - str.ptr;
             curbuf->buf[curbuf->len++] = idx;
@@ -737,8 +649,8 @@
         }
     } while (searchstr.length > 0 && found < replmax);
 
-    /* Allocate result string */
-    sstr_t result;
+    // Allocate result string
+    cxmutstr result;
     {
         ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
         size_t rcount = 0;
@@ -748,60 +660,127 @@
             curbuf = curbuf->next;
         } while (curbuf);
         result.length = str.length + rcount * adjlen;
-        result.ptr = almalloc(allocator, result.length);
+        result.ptr = cxMalloc(allocator, result.length + 1);
         if (!result.ptr) {
-            scstrrepl_free_ibuf(firstbuf);
-            return sstrn(NULL, 0);
+            cx_strrepl_free_ibuf(firstbuf);
+            return cx_mutstrn(NULL, 0);
         }
     }
 
-    /* Build result string */
+    // Build result string
     curbuf = firstbuf;
     size_t srcidx = 0;
-    char* destptr = result.ptr;
+    char *destptr = result.ptr;
     do {
         for (size_t i = 0; i < curbuf->len; i++) {
-            /* Copy source part up to next match*/
+            // Copy source part up to next match
             size_t idx = curbuf->buf[i];
             size_t srclen = idx - srcidx;
             if (srclen > 0) {
-                memcpy(destptr, str.ptr+srcidx, srclen);
+                memcpy(destptr, str.ptr + srcidx, srclen);
                 destptr += srclen;
                 srcidx += srclen;
             }
 
-            /* Copy the replacement and skip the source pattern */
+            // Copy the replacement and skip the source pattern
             srcidx += pattern.length;
             memcpy(destptr, replacement.ptr, replacement.length);
             destptr += replacement.length;
         }
         curbuf = curbuf->next;
     } while (curbuf);
-    memcpy(destptr, str.ptr+srcidx, str.length-srcidx);
+    memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
 
-    /* Free index buffer */
-    scstrrepl_free_ibuf(firstbuf);
+    // Result is guaranteed to be zero-terminated
+    result.ptr[result.length] = '\0';
+
+    // Free index buffer
+    cx_strrepl_free_ibuf(firstbuf);
 
     return result;
 }
 
-sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
-        scstr_t replacement, size_t replmax) {
-    return scstrreplacen_a(ucx_default_allocator(),
-            str, pattern, replacement, replmax);
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+) {
+    CxStrtokCtx ctx;
+    ctx.str = str;
+    ctx.delim = delim;
+    ctx.limit = limit;
+    ctx.pos = 0;
+    ctx.next_pos = 0;
+    ctx.delim_pos = 0;
+    ctx.found = 0;
+    ctx.delim_more = NULL;
+    ctx.delim_more_count = 0;
+    return ctx;
+}
+
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+) {
+    return cx_strtok(cx_strcast(str), delim, limit);
 }
 
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+) {
+    // abortion criteria
+    if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
+        return false;
+    }
 
-// type adjustment functions
-scstr_t ucx_sc2sc(scstr_t str) {
-    return str;
+    // determine the search start
+    cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
+
+    // search the next delimiter
+    cxstring delim = cx_strstr(haystack, ctx->delim);
+
+    // if found, make delim capture exactly the delimiter
+    if (delim.length > 0) {
+        delim.length = ctx->delim.length;
+    }
+
+    // if more delimiters are specified, check them now
+    if (ctx->delim_more_count > 0) {
+        cx_for_n(i, ctx->delim_more_count) {
+            cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
+            if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
+                delim.ptr = d.ptr;
+                delim.length = ctx->delim_more[i].length;
+            }
+        }
+    }
+
+    // store the token information and adjust the context
+    ctx->found++;
+    ctx->pos = ctx->next_pos;
+    token->ptr = &ctx->str.ptr[ctx->pos];
+    ctx->delim_pos = delim.length == 0 ?
+                     ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
+    token->length = ctx->delim_pos - ctx->pos;
+    ctx->next_pos = ctx->delim_pos + delim.length;
+
+    return true;
 }
-scstr_t ucx_ss2sc(sstr_t str) {
-    scstr_t cs;
-    cs.ptr = str.ptr;
-    cs.length = str.length;
-    return cs;
+
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+) {
+    return cx_strtok_next(ctx, (cxstring *) token);
 }
-scstr_t ucx_ss2c_s(scstr_t c) {
-    return c;
+
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        const cxstring *delim,
+        size_t count
+) {
+    ctx->delim_more = delim;
+    ctx->delim_more_count = count;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/szmul.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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.
+ */
+
+int cx_szmul_impl(
+        size_t a,
+        size_t b,
+        size_t *result
+) {
+    if (a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    size_t r = a * b;
+    if (r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
--- a/ucx/test.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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 "ucx/test.h"
-
-UcxTestSuite* ucx_test_suite_new() {
-    UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite));
-    if (suite != NULL) {
-        suite->success = 0;
-        suite->failure = 0;
-        suite->tests = NULL;
-    }
-
-    return suite;
-}
-
-void ucx_test_suite_free(UcxTestSuite* suite) {
-    UcxTestList *l = suite->tests;
-    while (l != NULL) {
-        UcxTestList *e = l;
-        l = l->next;
-        free(e);
-    }
-    free(suite);
-}
-
-int ucx_test_register(UcxTestSuite* suite, UcxTest test) {
-    if (suite->tests) {
-        UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList));
-        if (newelem) {
-            newelem->test = test;
-            newelem->next = NULL;
-            
-            UcxTestList *last = suite->tests;
-            while (last->next) {
-                last = last->next;
-            }
-            last->next = newelem;
-            
-            return EXIT_SUCCESS;
-        } else {
-            return EXIT_FAILURE;
-        }
-    } else {
-        suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList));
-        if (suite->tests) {
-            suite->tests->test = test;
-            suite->tests->next = NULL;
-            
-            return EXIT_SUCCESS;
-        } else {
-            return EXIT_FAILURE;
-        }
-    }
-}
-
-void ucx_test_run(UcxTestSuite* suite, FILE* output) {
-    suite->success = 0;
-    suite->failure = 0;
-    for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) {
-        elem->test(suite, output);
-    }
-    fwrite("\nAll test completed.\n", 1, 21, output);
-    fprintf(output, "  Total:   %u\n  Success: %u\n  Failure: %u\n",
-            suite->success+suite->failure, suite->success, suite->failure);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/tree.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,958 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, 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 "cx/tree.h"
+
+#include "cx/array_list.h"
+
+#include <assert.h>
+
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
+#define tree_children(node) CX_TREE_PTR(node, loc_children)
+#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child)
+#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
+#define tree_next(node) CX_TREE_PTR(node, loc_next)
+
+#define cx_tree_ptr_locations \
+    loc_parent, loc_children, loc_last_child, loc_prev, loc_next
+
+static void cx_tree_zero_pointers(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    tree_parent(node) = NULL;
+    tree_prev(node) = NULL;
+    tree_next(node) = NULL;
+    tree_children(node) = NULL;
+    if (loc_last_child >= 0) {
+        tree_last_child(node) = NULL;
+    }
+}
+
+void cx_tree_link(
+        void *restrict parent,
+        void *restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    void *current_parent = tree_parent(node);
+    if (current_parent == parent) return;
+    if (current_parent != NULL) {
+        cx_tree_unlink(node, cx_tree_ptr_locations);
+    }
+
+    if (tree_children(parent) == NULL) {
+        tree_children(parent) = node;
+        if (loc_last_child >= 0) {
+            tree_last_child(parent) = node;
+        }
+    } else {
+        if (loc_last_child >= 0) {
+            void *child = tree_last_child(parent);
+            tree_prev(node) = child;
+            tree_next(child) = node;
+            tree_last_child(parent) = node;
+        } else {
+            void *child = tree_children(parent);
+            void *next;
+            while ((next = tree_next(child)) != NULL) {
+                child = next;
+            }
+            tree_prev(node) = child;
+            tree_next(child) = node;
+        }
+    }
+    tree_parent(node) = parent;
+}
+
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    if (tree_parent(node) == NULL) return;
+
+    void *left = tree_prev(node);
+    void *right = tree_next(node);
+    void *parent = tree_parent(node);
+    assert(left == NULL || tree_children(parent) != node);
+    assert(right == NULL || loc_last_child < 0 ||
+           tree_last_child(parent) != node);
+
+    if (left == NULL) {
+        tree_children(parent) = right;
+    } else {
+        tree_next(left) = right;
+    }
+    if (right == NULL) {
+        if (loc_last_child >= 0) {
+            tree_last_child(parent) = left;
+        }
+    } else {
+        tree_prev(right) = left;
+    }
+
+    tree_parent(node) = NULL;
+    tree_prev(node) = NULL;
+    tree_next(node) = NULL;
+}
+
+int cx_tree_search(
+        const void *root,
+        const void *node,
+        cx_tree_search_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    int ret;
+    *result = NULL;
+
+    // shortcut: compare root before doing anything else
+    ret = sfunc(root, node);
+    if (ret < 0) {
+        return ret;
+    } else if (ret == 0 || tree_children(root) == NULL) {
+        *result = (void*)root;
+        return ret;
+    }
+
+    // create a working stack
+    CX_ARRAY_DECLARE(const void *, work);
+    cx_array_initialize(work, 32);
+
+    // add the children of root to the working stack
+    {
+        void *c = tree_children(root);
+        while (c != NULL) {
+            cx_array_simple_add(work, c);
+            c = tree_next(c);
+        }
+    }
+
+    // remember a candidate for adding the data
+    // also remember the exact return code from sfunc
+    void *candidate = (void *) root;
+    int ret_candidate = ret;
+
+    // process the working stack
+    while (work_size > 0) {
+        // pop element
+        const void *elem = work[--work_size];
+
+        // apply the search function
+        ret = sfunc(elem, node);
+
+        if (ret == 0) {
+            // if found, exit the search
+            *result = (void *) elem;
+            work_size = 0;
+            break;
+        } else if (ret > 0) {
+            // if children might contain the data, add them to the stack
+            void *c = tree_children(elem);
+            while (c != NULL) {
+                cx_array_simple_add(work, c);
+                c = tree_next(c);
+            }
+
+            // remember this node in case no child is suitable
+            if (ret < ret_candidate) {
+                candidate = (void *) elem;
+                ret_candidate = ret;
+            }
+        }
+    }
+
+    // not found, but was there a candidate?
+    if (ret != 0 && candidate != NULL) {
+        ret = ret_candidate;
+        *result = candidate;
+    }
+
+    // free the working queue and return
+    free(work);
+    return ret;
+}
+
+int cx_tree_search_data(
+        const void *root,
+        const void *data,
+        cx_tree_search_data_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    // it is basically the same implementation
+    return cx_tree_search(
+            root, data,
+            (cx_tree_search_func) sfunc,
+            result,
+            loc_children, loc_next);
+}
+
+static bool cx_tree_iter_valid(const void *it) {
+    const struct cx_tree_iterator_s *iter = it;
+    return iter->node != NULL;
+}
+
+static void *cx_tree_iter_current(const void *it) {
+    const struct cx_tree_iterator_s *iter = it;
+    return iter->node;
+}
+
+static void cx_tree_iter_next(void *it) {
+    struct cx_tree_iterator_s *iter = it;
+    ptrdiff_t const loc_next = iter->loc_next;
+    ptrdiff_t const loc_children = iter->loc_children;
+    // protect us from misuse
+    if (!iter->base.valid(iter)) return;
+
+    void *children;
+
+    // check if we are currently exiting or entering nodes
+    if (iter->exiting) {
+        children = NULL;
+        // skipping on exit is pointless, just clear the flag
+        iter->skip = false;
+    } else {
+        if (iter->skip) {
+            // skip flag is set, pretend that there are no children
+            iter->skip = false;
+            children = NULL;
+        } else {
+            // try to enter the children (if any)
+            children = tree_children(iter->node);
+        }
+    }
+
+    if (children == NULL) {
+        // search for the next node
+        void *next;
+        cx_tree_iter_search_next:
+        // check if there is a sibling
+        if (iter->exiting) {
+            next = iter->node_next;
+        } else {
+            next = tree_next(iter->node);
+            iter->node_next = next;
+        }
+        if (next == NULL) {
+            // no sibling, we are done with this node and exit
+            if (iter->visit_on_exit && !iter->exiting) {
+                // iter is supposed to visit the node again
+                iter->exiting = true;
+            } else {
+                iter->exiting = false;
+                if (iter->depth == 1) {
+                    // there is no parent - we have iterated the entire tree
+                    // invalidate the iterator and free the node stack
+                    iter->node = iter->node_next = NULL;
+                    iter->stack_capacity = iter->depth = 0;
+                    free(iter->stack);
+                    iter->stack = NULL;
+                } else {
+                    // the parent node can be obtained from the top of stack
+                    // this way we can avoid the loc_parent in the iterator
+                    iter->depth--;
+                    iter->node = iter->stack[iter->depth - 1];
+                    // retry with the parent node to find a sibling
+                    goto cx_tree_iter_search_next;
+                }
+            }
+        } else {
+            if (iter->visit_on_exit && !iter->exiting) {
+                // iter is supposed to visit the node again
+                iter->exiting = true;
+            } else {
+                iter->exiting = false;
+                // move to the sibling
+                iter->counter++;
+                iter->node = next;
+                // new top of stack is the sibling
+                iter->stack[iter->depth - 1] = next;
+            }
+        }
+    } else {
+        // node has children, push the first child onto the stack and enter it
+        cx_array_simple_add(iter->stack, children);
+        iter->node = children;
+        iter->counter++;
+    }
+}
+
+CxTreeIterator cx_tree_iterator(
+        void *root,
+        bool visit_on_exit,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    CxTreeIterator iter;
+    iter.loc_children = loc_children;
+    iter.loc_next = loc_next;
+    iter.visit_on_exit = visit_on_exit;
+
+    // initialize members
+    iter.node_next = NULL;
+    iter.exiting = false;
+    iter.skip = false;
+
+    // assign base iterator functions
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    iter.base.current_impl = NULL;
+    iter.base.valid = cx_tree_iter_valid;
+    iter.base.next = cx_tree_iter_next;
+    iter.base.current = cx_tree_iter_current;
+
+    // visit the root node
+    iter.node = root;
+    if (root != NULL) {
+        iter.stack_capacity = 16;
+        iter.stack = malloc(sizeof(void *) * 16);
+        iter.stack[0] = root;
+        iter.counter = 1;
+        iter.depth = 1;
+    } else {
+        iter.stack_capacity = 0;
+        iter.stack = NULL;
+        iter.counter = 0;
+        iter.depth = 0;
+    }
+
+    return iter;
+}
+
+static bool cx_tree_visitor_valid(const void *it) {
+    const struct cx_tree_visitor_s *iter = it;
+    return iter->node != NULL;
+}
+
+static void *cx_tree_visitor_current(const void *it) {
+    const struct cx_tree_visitor_s *iter = it;
+    return iter->node;
+}
+
+__attribute__((__nonnull__))
+static void cx_tree_visitor_enqueue_siblings(
+        struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
+    node = tree_next(node);
+    while (node != NULL) {
+        struct cx_tree_visitor_queue_s *q;
+        q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+        q->depth = iter->queue_last->depth;
+        q->node = node;
+        iter->queue_last->next = q;
+        iter->queue_last = q;
+        node = tree_next(node);
+    }
+    iter->queue_last->next = NULL;
+}
+
+static void cx_tree_visitor_next(void *it) {
+    struct cx_tree_visitor_s *iter = it;
+    // protect us from misuse
+    if (!iter->base.valid(iter)) return;
+
+    ptrdiff_t const loc_next = iter->loc_next;
+    ptrdiff_t const loc_children = iter->loc_children;
+
+    // add the children of the current node to the queue
+    // unless the skip flag is set
+    void *children;
+    if (iter->skip) {
+        iter->skip = false;
+        children = NULL;
+    } else {
+        children = tree_children(iter->node);
+    }
+    if (children != NULL) {
+        struct cx_tree_visitor_queue_s *q;
+        q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+        q->depth = iter->depth + 1;
+        q->node = children;
+        if (iter->queue_last == NULL) {
+            assert(iter->queue_next == NULL);
+            iter->queue_next = q;
+        } else {
+            iter->queue_last->next = q;
+        }
+        iter->queue_last = q;
+        cx_tree_visitor_enqueue_siblings(iter, children, loc_next);
+    }
+
+    // check if there is a next node
+    if (iter->queue_next == NULL) {
+        iter->node = NULL;
+        return;
+    }
+
+    // dequeue the next node
+    iter->node = iter->queue_next->node;
+    iter->depth = iter->queue_next->depth;
+    {
+        struct cx_tree_visitor_queue_s *q = iter->queue_next;
+        iter->queue_next = q->next;
+        if (iter->queue_next == NULL) {
+            assert(iter->queue_last == q);
+            iter->queue_last = NULL;
+        }
+        free(q);
+    }
+
+    // increment the node counter
+    iter->counter++;
+}
+
+CxTreeVisitor cx_tree_visitor(
+        void *root,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    CxTreeVisitor iter;
+    iter.loc_children = loc_children;
+    iter.loc_next = loc_next;
+
+    // initialize members
+    iter.skip = false;
+    iter.queue_next = NULL;
+    iter.queue_last = NULL;
+
+    // assign base iterator functions
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    iter.base.current_impl = NULL;
+    iter.base.valid = cx_tree_visitor_valid;
+    iter.base.next = cx_tree_visitor_next;
+    iter.base.current = cx_tree_visitor_current;
+
+    // visit the root node
+    iter.node = root;
+    if (root != NULL) {
+        iter.counter = 1;
+        iter.depth = 1;
+    } else {
+        iter.counter = 0;
+        iter.depth = 0;
+    }
+
+    return iter;
+}
+
+static void cx_tree_add_link_duplicate(
+        void *original, void *duplicate,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+    void *shared_parent = tree_parent(original);
+    if (shared_parent == NULL) {
+        cx_tree_link(original, duplicate, cx_tree_ptr_locations);
+    } else {
+        cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations);
+    }
+}
+
+static void cx_tree_add_link_new(
+        void *parent, void *node, cx_tree_search_func sfunc,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+    // check the current children one by one,
+    // if they could be children of the new node
+    void *child = tree_children(parent);
+    while (child != NULL) {
+        void *next = tree_next(child);
+
+        if (sfunc(node, child) > 0) {
+            // the sibling could be a child -> re-link
+            cx_tree_link(node, child, cx_tree_ptr_locations);
+        }
+
+        child = next;
+    }
+
+    // add new node as new child
+    cx_tree_link(parent, node, cx_tree_ptr_locations);
+}
+
+int cx_tree_add(
+        const void *src,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **cnode,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    *cnode = cfunc(src, cdata);
+    if (*cnode == NULL) return 1;
+    cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);
+
+    void *match = NULL;
+    int result = cx_tree_search(
+            root,
+            *cnode,
+            sfunc,
+            &match,
+            loc_children,
+            loc_next
+    );
+
+    if (result < 0) {
+        // node does not fit into the tree - return non-zero value
+        return 1;
+    } else if (result == 0) {
+        // data already found in the tree, link duplicate
+        cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations);
+    } else {
+        // closest match found, add new node
+        cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations);
+    }
+
+    return 0;
+}
+
+unsigned int cx_tree_add_look_around_depth = 3;
+
+size_t cx_tree_add_iter(
+        struct cx_iterator_base_s *iter,
+        size_t num,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    // erase the failed pointer
+    *failed = NULL;
+
+    // iter not valid? cancel...
+    if (!iter->valid(iter)) return 0;
+
+    size_t processed = 0;
+    void *current_node = root;
+    const void *elem;
+
+    for (void **eptr; processed < num &&
+         iter->valid(iter) && (eptr = iter->current(iter)) != NULL;
+         iter->next(iter)) {
+        elem = *eptr;
+
+        // create the new node
+        void *new_node = cfunc(elem, cdata);
+        if (new_node == NULL) return processed;
+        cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);
+
+        // start searching from current node
+        void *match;
+        int result;
+        unsigned int look_around_retries = cx_tree_add_look_around_depth;
+        cx_tree_add_look_around_retry:
+        result = cx_tree_search(
+                current_node,
+                new_node,
+                sfunc,
+                &match,
+                loc_children,
+                loc_next
+        );
+
+        if (result < 0) {
+            // traverse upwards and try to find better parents
+            void *parent = tree_parent(current_node);
+            if (parent != NULL) {
+                if (look_around_retries > 0) {
+                    look_around_retries--;
+                    current_node = parent;
+                } else {
+                    // look around retries exhausted, start from the root
+                    current_node = root;
+                }
+                goto cx_tree_add_look_around_retry;
+            } else {
+                // no parents. so we failed
+                *failed = new_node;
+                return processed;
+            }
+        } else if (result == 0) {
+            // data already found in the tree, link duplicate
+            cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations);
+            // but stick with the original match, in case we needed a new root
+            current_node = match;
+        } else {
+            // closest match found, add new node as child
+            cx_tree_add_link_new(match, new_node, sfunc,
+                                 cx_tree_ptr_locations);
+            current_node = match;
+        }
+
+        processed++;
+    }
+    return processed;
+}
+
+size_t cx_tree_add_array(
+        const void *src,
+        size_t num,
+        size_t elem_size,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    // erase failed pointer
+    *failed = NULL;
+
+    // super special case: zero elements
+    if (num == 0) {
+        return 0;
+    }
+
+    // special case: one element does not need an iterator
+    if (num == 1) {
+        void *node;
+        if (0 == cx_tree_add(
+                src, sfunc, cfunc, cdata, &node, root,
+                loc_parent, loc_children, loc_last_child,
+                loc_prev, loc_next)) {
+            return 1;
+        } else {
+            *failed = node;
+            return 0;
+        }
+    }
+
+    // otherwise, create iterator and hand over to other function
+    CxIterator iter = cxIterator(src, elem_size, num);
+    return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
+                            cfunc, cdata, failed, root,
+                            loc_parent, loc_children, loc_last_child,
+                            loc_prev, loc_next);
+}
+
+static void cx_tree_default_destructor(CxTree *tree) {
+    if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) {
+        CxTreeIterator iter = tree->cl->iterator(tree, true);
+        cx_foreach(void *, node, iter) {
+            if (iter.exiting) {
+                if (tree->simple_destructor) {
+                    tree->simple_destructor(node);
+                }
+                if (tree->advanced_destructor) {
+                    tree->advanced_destructor(tree->destructor_data, node);
+                }
+            }
+        }
+    }
+    cxFree(tree->allocator, tree);
+}
+
+static CxTreeIterator cx_tree_default_iterator(
+        CxTree *tree,
+        bool visit_on_exit
+) {
+    return cx_tree_iterator(
+            tree->root, visit_on_exit,
+            tree->loc_children, tree->loc_next
+    );
+}
+
+static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) {
+    return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next);
+}
+
+static int cx_tree_default_insert_element(
+        CxTree *tree,
+        const void *data
+) {
+    void *node;
+    if (tree->root == NULL) {
+        node = tree->node_create(data, tree);
+        if (node == NULL) return 1;
+        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+        tree->root = node;
+        tree->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++;
+    } else {
+        cxFree(tree->allocator, node);
+    }
+    return result;
+}
+
+static size_t cx_tree_default_insert_many(
+        CxTree *tree,
+        struct cx_iterator_base_s *iter,
+        size_t n
+) {
+    size_t ins = 0;
+    if (!iter->valid(iter)) return 0;
+    if (tree->root == NULL) {
+        // 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;
+        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+        tree->root = node;
+        ins = 1;
+        iter->next(iter);
+    }
+    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;
+    if (ins < n) {
+        cxFree(tree->allocator, failed);
+    }
+    return ins;
+}
+
+static void *cx_tree_default_find(
+        CxTree *tree,
+        const void *subtree,
+        const void *data
+) {
+    if (tree->root == NULL) return NULL;
+
+    void *found;
+    if (0 == cx_tree_search_data(
+            subtree,
+            data,
+            tree->search_data,
+            &found,
+            tree->loc_children,
+            tree->loc_next
+    )) {
+        return found;
+    } else {
+        return NULL;
+    }
+}
+
+static cx_tree_class cx_tree_default_class = {
+        cx_tree_default_destructor,
+        cx_tree_default_insert_element,
+        cx_tree_default_insert_many,
+        cx_tree_default_find,
+        cx_tree_default_iterator,
+        cx_tree_default_visitor
+};
+
+CxTree *cxTreeCreate(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+    if (tree == NULL) return NULL;
+
+    tree->cl = &cx_tree_default_class;
+    tree->allocator = allocator;
+    tree->node_create = create_func;
+    tree->search = search_func;
+    tree->search_data = search_data_func;
+    tree->advanced_destructor = (cx_destructor_func2) cxFree;
+    tree->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;
+}
+
+CxTree *cxTreeCreateWrapped(
+        const CxAllocator *allocator,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+    if (tree == NULL) return NULL;
+
+    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->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);
+    return tree;
+}
+
+int cxTreeAddChild(
+        CxTree *tree,
+        void *parent,
+        const void *data) {
+    void *node = tree->node_create(data, tree);
+    if (node == NULL) return 1;
+    cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+    cx_tree_link(parent, node, cx_tree_node_layout(tree));
+    tree->size++;
+    return 0;
+}
+
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
+    CxTreeVisitor visitor = cx_tree_visitor(
+            subtree_root,
+            tree->loc_children,
+            tree->loc_next
+    );
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.counter;
+}
+
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) {
+    CxTreeVisitor visitor = cx_tree_visitor(
+            subtree_root,
+            tree->loc_children,
+            tree->loc_next
+    );
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.depth;
+}
+
+size_t cxTreeDepth(CxTree *tree) {
+    CxTreeVisitor visitor = tree->cl->visitor(tree);
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.depth;
+}
+
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+) {
+    if (node == tree->root) return 1;
+
+    // determine the new parent
+    ptrdiff_t loc_parent = tree->loc_parent;
+    void *new_parent = tree_parent(node);
+
+    // first, unlink from the parent
+    cx_tree_unlink(node, cx_tree_node_layout(tree));
+
+    // then relink each child
+    ptrdiff_t loc_children = tree->loc_children;
+    ptrdiff_t loc_next = tree->loc_next;
+    void *child = tree_children(node);
+    while (child != NULL) {
+        // forcibly set the parent to NULL - we do not use the unlink function
+        // because that would unnecessarily modify the children linked list
+        tree_parent(child) = NULL;
+
+        // update contents, if required
+        if (relink_func != NULL) {
+            relink_func(child, node, new_parent);
+        }
+
+        // link to new parent
+        cx_tree_link(new_parent, child, cx_tree_node_layout(tree));
+
+        // proceed to next child
+        child = tree_next(child);
+    }
+
+    // clear the linked list of the removed node
+    tree_children(node) = NULL;
+    ptrdiff_t loc_last_child = tree->loc_last_child;
+    if (loc_last_child >= 0) tree_last_child(node) = NULL;
+
+    // the tree now has one member less
+    tree->size--;
+
+    return 0;
+}
+
+void cxTreeRemoveSubtree(CxTree *tree, void *node) {
+    if (node == tree->root) {
+        tree->root = NULL;
+        tree->size = 0;
+        return;
+    }
+    size_t subtree_size = cxTreeSubtreeSize(tree, node);
+    cx_tree_unlink(node, cx_tree_node_layout(tree));
+    tree->size -= subtree_size;
+}
--- a/ucx/ucx.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * @mainpage UAP Common Extensions
- * Library with common and useful functions, macros and data structures.
- * <p>
- * Latest available source:<br>
- * <a href="https://sourceforge.net/projects/ucx/files/">
- * https://sourceforge.net/projects/ucx/files/</a>
- * </p>
- * 
- * <p>
- * Repositories:<br>
- * <a href="https://sourceforge.net/p/ucx/code">
- * https://sourceforge.net/p/ucx/code</a>
- * -&nbsp;or&nbsp;-
- * <a href="https://develop.uap-core.de/hg/ucx">
- * https://develop.uap-core.de/hg/ucx</a>
- * </p>
- * 
- * <h2>LICENCE</h2>
- * 
- * Copyright 2017 Mike Becker, 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 "ucx/ucx.h"
-
-int ucx_szmul_impl(size_t a, size_t b, size_t *result) {
-    if(a == 0 || b == 0) {
-        *result = 0;
-        return 0;
-    }
-    size_t r = a * b;
-    if(r / b == a) {
-        *result = r;
-        return 0;
-    } else {
-        *result = 0;
-        return 1;
-    }
-}
-
--- a/ucx/ucx/allocator.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * Allocator for custom memory management.
- * 
- * A UCX allocator consists of a pointer to the memory area / pool and four
- * function pointers to memory management functions operating on this memory
- * area / pool. These functions shall behave equivalent to the standard libc
- * functions <code>malloc(), calloc(), realloc()</code> and <code>free()</code>.
- * 
- * The signature of the memory management functions is based on the signature
- * of the respective libc function but each of them takes the pointer to the
- * memory area / pool as first argument.
- * 
- * As the pointer to the memory area / pool can be arbitrarily chosen, any data
- * can be provided to the memory management functions. A UcxMempool is just
- * one example.
- * 
- * @see mempool.h
- * @see UcxMap
- * 
- * @file   allocator.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_ALLOCATOR_H
-#define	UCX_ALLOCATOR_H
-
-#include "ucx.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * A function pointer to the allocators <code>malloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_malloc)(void *pool, size_t n);
-
-/**
- * A function pointer to the allocators <code>calloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size);
-
-/**
- * A function pointer to the allocators <code>realloc()</code> function.
- * @see UcxAllocator
- */
-typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n);
-
-/**
- * A function pointer to the allocators <code>free()</code> function.
- * @see UcxAllocator
- */
-typedef void(*ucx_allocator_free)(void *pool, void *data);
-
-/**
- * UCX allocator data structure containing memory management functions.
- */
-typedef struct {
-    /** Pointer to an area of memory or a complex memory pool.
-     * This pointer will be passed to any memory management function as first
-     * argument.
-     */
-    void *pool;
-    /**
-     * The <code>malloc()</code> function for this allocator.
-     */
-    ucx_allocator_malloc  malloc;
-    /**
-     * The <code>calloc()</code> function for this allocator.
-     */
-    ucx_allocator_calloc  calloc;
-    /**
-     * The <code>realloc()</code> function for this allocator.
-     */
-    ucx_allocator_realloc realloc;
-    /**
-     * The <code>free()</code> function for this allocator.
-     */
-    ucx_allocator_free    free;
-} UcxAllocator;
-
-/**
- * Returns a pointer to the default allocator.
- * 
- * The default allocator contains wrappers to the standard libc memory
- * management functions. Use this function to get a pointer to a globally
- * available allocator. You may also define an own UcxAllocator by assigning
- * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable
- * to any function that takes a UcxAllocator as argument. Note that using
- * this function is the recommended way of passing a default allocator, thus
- * it never runs out of scope.
- * 
- * @return a pointer to the default allocator
- * 
- * @see UCX_ALLOCATOR_DEFAULT
- */
-UcxAllocator *ucx_default_allocator();
-
-/**
- * A wrapper for the standard libc <code>malloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param n argument passed to <code>malloc()</code>
- * @return return value of <code>malloc()</code>
- */
-void *ucx_default_malloc(void *ignore, size_t n);
-/**
- * A wrapper for the standard libc <code>calloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param n argument passed to <code>calloc()</code>
- * @param size  argument passed to <code>calloc()</code>
- * @return return value of <code>calloc()</code>
- */
-void *ucx_default_calloc(void *ignore, size_t n, size_t size);
-/**
- * A wrapper for the standard libc <code>realloc()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param data argumend passed to <code>realloc()</code>
- * @param n argument passed to <code>realloc()</code>
- * @return return value of <code>realloc()</code>
- */
-void *ucx_default_realloc(void *ignore, void *data, size_t n);
-/**
- * A wrapper for the standard libc <code>free()</code> function.
- * @param ignore ignored (may be used by allocators for pooled memory)
- * @param data argument passed to <code>free()</code>
- */
-void ucx_default_free(void *ignore, void *data);
-
-/**
- * Shorthand for calling an allocators malloc function.
- * @param allocator the allocator to use
- * @param n size of space to allocate
- * @return a pointer to the allocated memory area
- */
-#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n))
-
-/**
- * Shorthand for calling an allocators calloc function.
- * @param allocator the allocator to use
- * @param n the count of elements the space should be allocated for
- * @param size the size of each element
- * @return a pointer to the allocated memory area
- */
-#define alcalloc(allocator, n, size) \
-        ((allocator)->calloc((allocator)->pool, n, size))
-
-/**
- * Shorthand for calling an allocators realloc function.
- * @param allocator the allocator to use
- * @param ptr the pointer to the memory area that shall be reallocated
- * @param n the new size of the allocated memory area
- * @return a pointer to the reallocated memory area
- */
-#define alrealloc(allocator, ptr, n) \
-        ((allocator)->realloc((allocator)->pool, ptr, n))
-
-/**
- * Shorthand for calling an allocators free function.
- * @param allocator the allocator to use
- * @param ptr the pointer to the memory area that shall be freed
- */
-#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr))
-
-/**
- * Convenient macro for a default allocator <code>struct</code> definition.
- */
-#define UCX_ALLOCATOR_DEFAULT {NULL, \
-        ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \
-        ucx_default_free }
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_ALLOCATOR_H */
-
--- a/ucx/ucx/array.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,460 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2019 Mike Becker, 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.
- */
-/**
- * Dynamically allocated array implementation.
- * 
- * @file   array.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_ARRAY_H
-#define	UCX_ARRAY_H
-
-#include "ucx.h"
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UCX array type.
- */
-typedef struct {
-    /**
-     * The current capacity of the array.
-     */
-    size_t capacity;
-    /**
-     * The actual number of elements in the array.
-     */
-    size_t size;
-    /**
-     * The size of an individual element in bytes.
-     */
-    size_t elemsize;
-    /**
-     * A pointer to the data.
-     */
-    void* data;
-    /**
-     * The allocator used for the data.
-     */
-    UcxAllocator* allocator;
-} UcxArray;
-
-/**
- * Sets an element in an arbitrary user defined array.
- * The data is copied from the specified data location.
- * 
- * If the capacity is insufficient, the array is automatically reallocated and
- * the possibly new pointer is stored in the <code>array</code> argument.
- * 
- * On reallocation the capacity of the array is doubled until it is sufficient.
- * The new capacity is stored back to <code>capacity</code>.
- *  
- * @param array a pointer to location of the array pointer
- * @param capacity a pointer to the capacity
- * @param elmsize the size of each element
- * @param idx the index of the element to set
- * @param data a pointer to the element data
- * @return zero on success or non-zero on error (errno will be set)
- */
-#define ucx_array_util_set(array, capacity, elmsize, idx, data) \
-    ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \
-                         elmsize, idx, data)
-
-/**
- * Sets an element in an arbitrary user defined array.
- * The data is copied from the specified data location.
- * 
- * If the capacity is insufficient, the array is automatically reallocated
- * using the specified allocator and the possibly new pointer is stored in
- * the <code>array</code> argument.
- * 
- * On reallocation the capacity of the array is doubled until it is sufficient.
- * The new capacity is stored back to <code>capacity</code>. 
- * 
- * @param alloc the allocator that shall be used to reallocate the array
- * @param array a pointer to location of the array pointer
- * @param capacity a pointer to the capacity
- * @param elmsize the size of each element
- * @param idx the index of the element to set
- * @param data a pointer to the element data
- * @return zero on success or non-zero on error (errno will be set)
- */
-int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
-    size_t elmsize, size_t idx, void* data);
-
-/**
- * Stores a pointer in an arbitrary user defined array.
- * The element size of the array must be sizeof(void*).
- * 
- * If the capacity is insufficient, the array is automatically reallocated and
- * the possibly new pointer is stored in the <code>array</code> argument.
- * 
- * On reallocation the capacity of the array is doubled until it is sufficient.
- * The new capacity is stored back to <code>capacity</code>.
- *  
- * @param array a pointer to location of the array pointer
- * @param capacity a pointer to the capacity
- * @param idx the index of the element to set
- * @param ptr the pointer to store
- * @return zero on success or non-zero on error (errno will be set)
- */
-#define ucx_array_util_setptr(array, capacity, idx, ptr) \
-    ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \
-                            capacity, idx, ptr)
-
-/**
- * Stores a pointer in an arbitrary user defined array.
- * The element size of the array must be sizeof(void*).
- * 
- * If the capacity is insufficient, the array is automatically reallocated
- * using the specified allocator and the possibly new pointer is stored in
- * the <code>array</code> argument.
- * 
- * On reallocation the capacity of the array is doubled until it is sufficient.
- * The new capacity is stored back to <code>capacity</code>. 
- * 
- * @param alloc the allocator that shall be used to reallocate the array
- * @param array a pointer to location of the array pointer
- * @param capacity a pointer to the capacity
- * @param idx the index of the element to set
- * @param ptr the pointer to store
- * @return zero on success or non-zero on error (errno will be set)
- */
-int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
-    size_t idx, void* ptr);
-
-
-/**
- * Creates a new UCX array with the given capacity and element size.
- * @param capacity the initial capacity
- * @param elemsize the element size
- * @return a pointer to a new UCX array structure
- */
-UcxArray* ucx_array_new(size_t capacity, size_t elemsize);
-
-/**
- * Creates a new UCX array using the specified allocator.
- * 
- * @param capacity the initial capacity
- * @param elemsize the element size
- * @param allocator the allocator to use
- * @return a pointer to new UCX array structure
- */
-UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
-        UcxAllocator* allocator);
-
-/**
- * Initializes a UCX array structure with the given capacity and element size.
- * The structure must be uninitialized as the data pointer will be overwritten.
- * 
- * @param array the structure to initialize
- * @param capacity the initial capacity
- * @param elemsize the element size
- */
-void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize);
-
-/**
- * Initializes a UCX array structure using the specified allocator.
- * The structure must be uninitialized as the data pointer will be overwritten.
- * 
- * @param array the structure to initialize
- * @param capacity the initial capacity
- * @param elemsize the element size
- * @param allocator the allocator to use
- */
-void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
-        UcxAllocator* allocator);
-
-/**
- * Creates an shallow copy of an array.
- * 
- * This function clones the specified array by using memcpy().
- * If the destination capacity is insufficient, an automatic reallocation is
- * attempted.
- * 
- * Note: if the destination array is uninitialized, the behavior is undefined.
- * 
- * @param dest the array to copy to
- * @param src the array to copy from
- * @return zero on success, non-zero on reallocation failure.
- */
-int ucx_array_clone(UcxArray* dest, UcxArray const* src);
-
-
-/**
- * Compares two UCX arrays element-wise by using a compare function.
- *
- * Elements of the two specified arrays are compared by using the specified
- * compare function and the additional data. The type and content of this
- * additional data depends on the cmp_func() used.
- * 
- * This function always returns zero, if the element sizes of the arrays do
- * not match and performs no comparisons in this case.
- * 
- * @param array1 the first array
- * @param array2 the second array
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the two arrays equal element-wise, 0 otherwise
- */
-int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
-        cmp_func cmpfnc, void* data);
-
-/**
- * Destroys the array.
- * 
- * The data is freed and both capacity and count are reset to zero.
- * If the array structure itself has been dynamically allocated, it has to be
- * freed separately.
- * 
- * @param array the array to destroy
- */
-void ucx_array_destroy(UcxArray *array);
-
-/**
- * Destroys and frees the array.
- * 
- * @param array the array to free
- */
-void ucx_array_free(UcxArray *array);
-
-/**
- * Inserts elements at the end of the array.
- * 
- * This is an O(1) operation.
- * The array will automatically grow, if the capacity is exceeded.
- * If a pointer to data is provided, the data is copied into the array with
- * memcpy(). Otherwise the new elements are completely zeroed.
- * 
- * @param array a pointer the array where to append the data
- * @param data a pointer to the data to insert (may be <code>NULL</code>)
- * @param count number of elements to copy from data (if data is
- * <code>NULL</code>, zeroed elements are appended)
- * @return zero on success, non-zero if a reallocation was necessary but failed
- * @see ucx_array_set_from()
- * @see ucx_array_append()
- */
-int ucx_array_append_from(UcxArray *array, void *data, size_t count);
-
-
-/**
- * Inserts elements at the beginning of the array.
- * 
- * This is an expensive operation, because the contents must be moved.
- * If there is no particular reason to prepend data, you should use
- * ucx_array_append_from() instead.
- * 
- * @param array a pointer the array where to prepend the data
- * @param data a pointer to the data to insert (may be <code>NULL</code>)
- * @param count number of elements to copy from data (if data is
- * <code>NULL</code>, zeroed elements are inserted)
- * @return zero on success, non-zero if a reallocation was necessary but failed
- * @see ucx_array_append_from()
- * @see ucx_array_set_from()
- * @see ucx_array_prepend()
- */
-int ucx_array_prepend_from(UcxArray *array, void *data, size_t count);
-
-
-/**
- * Sets elements starting at the specified index.
- * 
- * If the any index is out of bounds, the array automatically grows.
- * The pointer to the data may be NULL, in which case the elements are zeroed. 
- * 
- * @param array a pointer the array where to set the data
- * @param index the index of the element to set
- * @param data a pointer to the data to insert (may be <code>NULL</code>)
- * @param count number of elements to copy from data (if data is
- * <code>NULL</code>, the memory in the array is zeroed)
- * @return zero on success, non-zero if a reallocation was necessary but failed
- * @see ucx_array_append_from()
- * @see ucx_array_set()
- */
-int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count);
-
-/**
- * Concatenates two arrays.
- * 
- * The contents of the second array are appended to the first array in one
- * single operation. The second array is otherwise left untouched.
- * 
- * The first array may grow automatically. If this fails, both arrays remain
- * unmodified.
- * 
- * @param array1 first array
- * @param array2 second array
- * @return zero on success, non-zero if reallocation was necessary but failed 
- * or the element size does not match
- */
-int ucx_array_concat(UcxArray *array1, const UcxArray *array2);
-
-/**
- * Returns a pointer to the array element at the specified index.
- * 
- * @param array the array to retrieve the element from
- * @param index index of the element to return
- * @return a pointer to the element at the specified index or <code>NULL</code>,
- * if the index is greater than the array size
- */
-void *ucx_array_at(UcxArray const* array, size_t index);
-
-/**
- * Returns the index of an element containing the specified data.
- *
- * This function uses a cmp_func() to compare the data of each list element
- * with the specified data. If no cmp_func is provided, memcmp() is used.
- * 
- * If the array contains the data more than once, the index of the first
- * occurrence is returned.
- * If the array does not contain the data, the size of array is returned.
- *  
- * @param array the array where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return the index of the element containing the specified data or the size of
- * the array, if the data is not found in this array
- */
-size_t ucx_array_find(UcxArray const *array, void *elem,
-    cmp_func cmpfnc, void *data);
-
-/**
- * Checks, if an array contains a specific element.
- * 
- * An element is found, if ucx_array_find() returns a value less than the size.
- * 
- * @param array the array where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the array contains the specified element data
- * @see ucx_array_find()
- */
-int ucx_array_contains(UcxArray const *array, void *elem,
-    cmp_func cmpfnc, void *data);
-
-/**
- * Sorts a UcxArray with the best available sort algorithm.
- * 
- * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS).
- * The order of arguments is automatically adjusted for the FreeBSD and MacOS
- * version of qsort_r().
- * 
- * If qsort_r() is not available, a merge sort algorithm is used, which is
- * guaranteed to use no more additional memory than for exactly one element.
- * 
- * @param array the array to sort
- * @param cmpfnc the function that shall be used to compare the element data
- * @param data additional data for the cmp_func() or <code>NULL</code>
- */
-void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data);
-
-/**
- * Removes an element from the array.
- * 
- * This is in general an expensive operation, because several elements may
- * be moved. If the order of the elements is not relevant, use
- * ucx_array_remove_fast() instead.
- * 
- * @param array pointer to the array from which the element shall be removed
- * @param index the index of the element to remove
- */
-void ucx_array_remove(UcxArray *array, size_t index);
-
-/**
- * Removes an element from the array.
- * 
- * This is an O(1) operation, but does not maintain the order of the elements.
- * The last element in the array is moved to the location of the removed
- * element.
- * 
- * @param array pointer to the array from which the element shall be removed
- * @param index the index of the element to remove
- */
-void ucx_array_remove_fast(UcxArray *array, size_t index);
-
-/**
- * Shrinks the memory to exactly fit the contents.
- * 
- * After this operation, the capacity equals the size.
- * 
- * @param array a pointer to the array
- * @return zero on success, non-zero if reallocation failed
- */
-int ucx_array_shrink(UcxArray* array);
-
-/**
- * Sets the capacity of the array.
- * 
- * If the new capacity is smaller than the size of the array, the elements
- * are removed and the size is adjusted accordingly.
- * 
- * @param array a pointer to the array
- * @param capacity the new capacity
- * @return zero on success, non-zero if reallocation failed
- */
-int ucx_array_resize(UcxArray* array, size_t capacity);
-
-/**
- * Resizes the array only, if the capacity is insufficient.
- * 
- * If the requested capacity is smaller than the current capacity, this
- * function does nothing.
- * 
- * @param array a pointer to the array
- * @param capacity the guaranteed capacity
- * @return zero on success, non-zero if reallocation failed
- */
-int ucx_array_reserve(UcxArray* array, size_t capacity);
-
-/**
- * Resizes the capacity, if the specified number of elements would not fit.
- * 
- * A call to ucx_array_grow(array, count) is effectively the same as
- * ucx_array_reserve(array, array->size+count).
- * 
- * @param array a pointer to the array
- * @param count the number of elements that should additionally fit
- * into the array
- * @return zero on success, non-zero if reallocation failed
- */
-int ucx_array_grow(UcxArray* array, size_t count);
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_ARRAY_H */
-
--- a/ucx/ucx/avl.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,353 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-
-/**
- * @file avl.h
- * 
- * AVL tree implementation.
- * 
- * This binary search tree implementation allows average O(1) insertion and
- * removal of elements (excluding binary search time).
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_AVL_H
-#define UCX_AVL_H
-
-#include "ucx.h"
-#include "allocator.h"
-#include <inttypes.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UCX AVL Node type.
- * 
- * @see UcxAVLNode
- */
-typedef struct UcxAVLNode UcxAVLNode;
-
-/**
- * UCX AVL Node.
- */
-struct UcxAVLNode {
-    /**
-     * The key for this node.
-     */
-    intptr_t key;
-    /**
-     * Data contained by this node.
-     */
-    void *value;
-    /**
-     * The height of this (sub)-tree.
-     */
-    size_t height;
-    /**
-     * Parent node.
-     */
-    UcxAVLNode *parent;
-    /**
-     * Root node of left subtree.
-     */
-    UcxAVLNode *left;
-    /**
-     * Root node of right subtree.
-     */
-    UcxAVLNode *right;
-};
-
-/**
- * UCX AVL Tree.
- */
-typedef struct {
-    /**
-     * The UcxAllocator that shall be used to manage the memory for node data.
-     */
-    UcxAllocator *allocator;
-    /**
-     * Root node of the tree.
-     */
-    UcxAVLNode *root;
-    /**
-     * Compare function that shall be used to compare the UcxAVLNode keys.
-     * @see UcxAVLNode.key
-     */
-    cmp_func cmpfunc;
-    /**
-     * Custom user data.
-     * This data will also be provided to the cmpfunc.
-     */
-    void *userdata;
-} UcxAVLTree;
-
-/**
- * Initializes a new UcxAVLTree with a default allocator.
- * 
- * @param cmpfunc the compare function that shall be used
- * @return a new UcxAVLTree object
- * @see ucx_avl_new_a()
- */
-UcxAVLTree *ucx_avl_new(cmp_func cmpfunc);
-
-/**
- * Initializes a new UcxAVLTree with the specified allocator.
- * 
- * The cmpfunc should be capable of comparing two keys within this AVL tree.
- * So if you want to use null terminated strings as keys, you could use the
- * ucx_cmp_str() function here.
- * 
- * @param cmpfunc the compare function that shall be used
- * @param allocator the UcxAllocator that shall be used
- * @return a new UcxAVLTree object
- */
-UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator);
-
-/**
- * Destroys a UcxAVLTree.
- * 
- * Note, that the contents are not automatically freed.
- * Use may use #ucx_avl_free_content() before calling this function.
- * 
- * @param tree the tree to destroy
- * @see ucx_avl_free_content()
- */
-void ucx_avl_free(UcxAVLTree *tree);
-
-/**
- * Frees the contents of a UcxAVLTree.
- * 
- * This is a convenience function that iterates over the tree and passes all
- * values to the specified destructor function.
- * 
- * If no destructor is specified (<code>NULL</code>), the free() function of
- * the tree's own allocator is used.
- * 
- * You must ensure, that it is valid to pass each value in the map to the same
- * destructor function.
- * 
- * You should free the entire tree afterwards, as the contents will be invalid.
- * 
- * @param tree for which the contents shall be freed
- * @param destr optional pointer to a destructor function
- * @see ucx_avl_free()
- */
-void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr);
-
-/**
- * Macro for initializing a new UcxAVLTree with the default allocator and a
- * ucx_cmp_ptr() compare function.
- * 
- * @return a new default UcxAVLTree object
- */
-#define ucx_avl_default_new() \
-    ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator())
-
-/**
- * Gets the node from the tree, that is associated with the specified key.
- * @param tree the UcxAVLTree
- * @param key the key
- * @return the node (or <code>NULL</code>, if the key is not present)
- */
-UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key);
-
-/**
- * Gets the value from the tree, that is associated with the specified key.
- * @param tree the UcxAVLTree
- * @param key the key
- * @return the value (or <code>NULL</code>, if the key is not present)
- */
-void *ucx_avl_get(UcxAVLTree *tree, intptr_t key);
-
-/**
- * A mode for #ucx_avl_find_node() with the same behavior as
- * #ucx_avl_get_node().
- */
-#define UCX_AVL_FIND_EXACT         0
-/**
- * A mode for #ucx_avl_find_node() finding the node whose key is at least
- * as large as the specified key.
- */
-#define UCX_AVL_FIND_LOWER_BOUNDED 1
-/**
- * A mode for #ucx_avl_find_node() finding the node whose key is at most
- * as large as the specified key.
- */
-#define UCX_AVL_FIND_UPPER_BOUNDED 2
-/**
- * A mode for #ucx_avl_find_node() finding the node with a key that is as close
- * to the specified key as possible. If the key is present, the behavior is
- * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
- * empty trees.
- */
-#define UCX_AVL_FIND_CLOSEST       3
-
-/**
- * Finds a node within the tree. The following modes are supported:
- * <ul>
- * <li>#UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()</li>
- * <li>#UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least
- * as large as the specified key</li>
- * <li>#UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most
- * as large as the specified key</li>
- * <li>#UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to
- * the specified key as possible. If the key is present, the behavior is
- * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
- * empty trees.</li> 
- * </ul>
- * 
- * The distance function provided MUST agree with the compare function of
- * the AVL tree.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param dfnc the distance function
- * @param mode the find mode
- * @return the node (or <code>NULL</code>, if no node can be found)
- */
-UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
-        distance_func dfnc, int mode);
-
-/**
- * Finds a value within the tree.
- * See #ucx_avl_find_node() for details.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param dfnc the distance function
- * @param mode the find mode
- * @return the value (or <code>NULL</code>, if no value can be found)
- */
-void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
-        distance_func dfnc, int mode);
-
-/**
- * Puts a key/value pair into the tree.
- * 
- * Attention: use this function only, if a possible old value does not need
- * to be preserved.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param value the new value
- * @return zero, if and only if the operation succeeded
- */
-int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value);
-
-/**
- * Puts a key/value pair into the tree.
- * 
- * This is a secure function which saves the old value to the variable pointed
- * at by oldvalue.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @param value the new value
- * @param oldvalue optional: a pointer to the location where a possible old
- * value shall be stored
- * @return zero, if and only if the operation succeeded
- */
-int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue);
-
-/**
- * Removes a node from the AVL tree.
- * 
- * Note: the specified node is logically removed. The tree implementation
- * decides which memory area is freed. In most cases the here provided node
- * is freed, so its further use is generally undefined.
- * 
- * @param tree the UcxAVLTree
- * @param node the node to remove
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node);
-
-/**
- * Removes an element from the AVL tree.
- * 
- * @param tree the UcxAVLTree
- * @param key the key
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove(UcxAVLTree *tree, intptr_t key);
-
-/**
- * Removes an element from the AVL tree.
- * 
- * This is a secure function which saves the old key and value data from node
- * to the variables at the location of oldkey and oldvalue (if specified), so
- * they can be freed afterwards (if necessary).
- * 
- * Note: the returned key in oldkey is possibly not the same as the provided
- * key for the lookup (in terms of memory location).
- * 
- * @param tree the UcxAVLTree
- * @param key the key of the element to remove
- * @param oldkey optional: a pointer to the location where the old key shall be
- * stored
- * @param oldvalue optional: a pointer to the location where the old value
- * shall be stored
- * @return zero, if and only if an element has been removed
- */
-int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
-        intptr_t *oldkey, void **oldvalue);
-
-/**
- * Counts the nodes in the specified UcxAVLTree.
- * @param tree the AVL tree
- * @return the node count
- */
-size_t ucx_avl_count(UcxAVLTree *tree);
-
-/**
- * Finds the in-order predecessor of the given node.
- * @param node an AVL node
- * @return the in-order predecessor of the given node, or <code>NULL</code> if
- * the given node is the in-order minimum
- */
-UcxAVLNode* ucx_avl_pred(UcxAVLNode* node);
-
-/**
- * Finds the in-order successor of the given node.
- * @param node an AVL node
- * @return the in-order successor of the given node, or <code>NULL</code> if
- * the given node is the in-order maximum
- */
-UcxAVLNode* ucx_avl_succ(UcxAVLNode* node);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_AVL_H */
-
--- a/ucx/ucx/buffer.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,339 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-/**
- * @file buffer.h
- * 
- * Advanced buffer implementation.
- * 
- * Instances of UcxBuffer can be used to read from or to write to like one
- * would do with a stream. This allows the use of ucx_stream_copy() to copy
- * contents from one buffer to another.
- * 
- * Some features for convenient use of the buffer
- * can be enabled. See the documentation of the macro constants for more
- * information.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_BUFFER_H
-#define	UCX_BUFFER_H
-
-#include "ucx.h"
-#include <sys/types.h>
-#include <stdio.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * No buffer features enabled (all flags cleared).
- */
-#define UCX_BUFFER_DEFAULT      0x00
-
-/**
- * If this flag is enabled, the buffer will automatically free its contents.
- */
-#define UCX_BUFFER_AUTOFREE     0x01
-
-/**
- * If this flag is enabled, the buffer will automatically extends its capacity.
- */
-#define UCX_BUFFER_AUTOEXTEND   0x02
-
-/** UCX Buffer. */
-typedef struct {
-    /** A pointer to the buffer contents. */
-    char *space;
-    /** Current position of the buffer. */
-    size_t pos;
-    /** Current capacity (i.e. maximum size) of the buffer. */
-    size_t capacity;
-    /** Current size of the buffer content. */
-    size_t size;
-    /**
-     * Flag register for buffer features.
-     * @see #UCX_BUFFER_DEFAULT
-     * @see #UCX_BUFFER_AUTOFREE
-     * @see #UCX_BUFFER_AUTOEXTEND
-     */
-    int flags;
-} UcxBuffer;
-
-/**
- * Creates a new buffer.
- * 
- * <b>Note:</b> you may provide <code>NULL</code> as argument for
- * <code>space</code>. Then this function will allocate the space and enforce
- * the #UCX_BUFFER_AUTOFREE flag.
- * 
- * @param space pointer to the memory area, or <code>NULL</code> to allocate
- * new memory
- * @param capacity the capacity of the buffer
- * @param flags buffer features (see UcxBuffer.flags)
- * @return the new buffer
- */
-UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags);
-
-/**
- * Destroys a buffer.
- * 
- * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer
- * are also freed.
- * 
- * @param buffer the buffer to destroy
- */
-void ucx_buffer_free(UcxBuffer* buffer);
-
-/**
- * Creates a new buffer and fills it with extracted content from another buffer.
- * 
- * <b>Note:</b> the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer.
- * 
- * @param src the source buffer
- * @param start the start position of extraction
- * @param length the count of bytes to extract (must not be zero)
- * @param flags feature mask for the new buffer
- * @return a new buffer containing the extraction
- */
-UcxBuffer* ucx_buffer_extract(UcxBuffer *src,
-        size_t start, size_t length, int flags);
-
-/**
- * A shorthand macro for the full extraction of the buffer.
- * 
- * @param src the source buffer
- * @param flags feature mask for the new buffer
- * @return a new buffer with the extracted content
- */
-#define ucx_buffer_clone(src,flags) \
-    ucx_buffer_extract(src, 0, (src)->capacity, flags)
-
-
-/**
- * Shifts the contents of the buffer by the given offset.
- * 
- * If the offset is positive, the contents are shifted to the right.
- * If auto extension is enabled, the buffer grows, if necessary.
- * In case the auto extension fails, this function returns a non-zero value and
- * no contents are changed.
- * If auto extension is disabled, the contents that do not fit into the buffer
- * are discarded.
- * 
- * If the offset is negative, the contents are shifted to the left where the
- * first <code>shift</code> bytes are discarded.
- * The new size of the buffer is the old size minus
- * the absolute shift value.
- * If this value is larger than the buffer size, the buffer is emptied (but
- * not cleared, see the security note below).
- * 
- * The buffer position gets shifted alongside with the content but is kept
- * within the boundaries of the buffer.
- * 
- * <b>Security note:</b> the shifting operation does <em>not</em> erase the
- * previously occupied memory cells. You can easily do that manually, e.g. by
- * calling <code>memset(buffer->space, 0, shift)</code> for a right shift or
- * <code>memset(buffer->size, 0, buffer->capacity-buffer->size)</code>
- * for a left shift.
- * 
- * @param buffer the buffer
- * @param shift the shift offset (negative means left shift)
- * @return 0 on success, non-zero if a required auto-extension fails
- */
-int ucx_buffer_shift(UcxBuffer* buffer, off_t shift);
-
-/**
- * Shifts the buffer to the right.
- * See ucx_buffer_shift() for details.
- * 
- * @param buffer the buffer
- * @param shift the shift offset
- * @return 0 on success, non-zero if a required auto-extension fails
- * @see ucx_buffer_shift()
- */
-int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift);
-
-/**
- * Shifts the buffer to the left.
- * 
- * See ucx_buffer_shift() for details. Note, however, that this method expects
- * a positive shift offset.
- * 
- * Since a left shift cannot fail due to memory allocation problems, this
- * function always returns zero.
- * 
- * @param buffer the buffer
- * @param shift the shift offset
- * @return always zero
- * @see ucx_buffer_shift()
- */
-int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift);
-
-
-/**
- * Moves the position of the buffer.
- * 
- * The new position is relative to the <code>whence</code> argument.
- *
- * SEEK_SET marks the start of the buffer.
- * SEEK_CUR marks the current position.
- * SEEK_END marks the end of the buffer.
- * 
- * With an offset of zero, this function sets the buffer position to zero
- * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position
- * unchanged (SEEK_CUR).
- * 
- * @param buffer
- * @param offset position offset relative to <code>whence</code>
- * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END
- * @return 0 on success, non-zero if the position is invalid
- *
- */
-int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence);
-
-/**
- * Clears the buffer by resetting the position and deleting the data.
- * 
- * The data is deleted by a zeroing it with call to <code>memset()</code>.
- * 
- * @param buffer the buffer to be cleared
- */
-#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \
-        (buffer)->size = 0; (buffer)->pos = 0;
-
-/**
- * Tests, if the buffer position has exceeded the buffer capacity.
- * 
- * @param buffer the buffer to test
- * @return non-zero, if the current buffer position has exceeded the last
- * available byte of the buffer.
- */
-int ucx_buffer_eof(UcxBuffer *buffer);
-
-
-/**
- * Extends the capacity of the buffer.
- * 
- * <b>Note:</b> The buffer capacity increased by a power of two. I.e.
- * the buffer capacity is doubled, as long as it would not hold the current
- * content plus the additional required bytes.
- * 
- * <b>Attention:</b> the argument provided is the number of <i>additional</i>
- * bytes the buffer shall hold. It is <b>NOT</b> the total number of bytes the
- * buffer shall hold.
- * 
- * @param buffer the buffer to extend
- * @param additional_bytes the number of additional bytes the buffer shall
- * <i>at least</i> hold
- * @return 0 on success or a non-zero value on failure
- */
-int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes);
-
-/**
- * Writes data to a UcxBuffer.
- * 
- * The position of the buffer is increased by the number of bytes written.
- * 
- * @param ptr a pointer to the memory area containing the bytes to be written
- * @param size the length of one element
- * @param nitems the element count
- * @param buffer the UcxBuffer to write to
- * @return the total count of bytes written
- */
-size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer);
-
-/**
- * Reads data from a UcxBuffer.
- * 
- * The position of the buffer is increased by the number of bytes read.
- * 
- * @param ptr a pointer to the memory area where to store the read data
- * @param size the length of one element
- * @param nitems the element count
- * @param buffer the UcxBuffer to read from
- * @return the total number of elements read
- */
-size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
-        UcxBuffer *buffer);
-
-/**
- * Writes a character to a buffer.
- * 
- * The least significant byte of the argument is written to the buffer. If the
- * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled,
- * the buffer capacity is extended by ucx_buffer_extend(). If the feature is
- * disabled or buffer extension fails, <code>EOF</code> is returned.
- * 
- * On successful write the position of the buffer is increased.
- * 
- * @param buffer the buffer to write to
- * @param c the character to write as <code>int</code> value
- * @return the byte that has bean written as <code>int</code> value or
- * <code>EOF</code> when the end of the stream is reached and automatic
- * extension is not enabled or not possible
- */
-int ucx_buffer_putc(UcxBuffer *buffer, int c);
-
-/**
- * Gets a character from a buffer.
- * 
- * The current position of the buffer is increased after a successful read.
- * 
- * @param buffer the buffer to read from
- * @return the character as <code>int</code> value or <code>EOF</code>, if the
- * end of the buffer is reached
- */
-int ucx_buffer_getc(UcxBuffer *buffer);
-
-/**
- * Writes a string to a buffer.
- * 
- * @param buffer the buffer
- * @param str the string
- * @return the number of bytes written
- */
-size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str);
-
-/**
- * Returns the complete buffer content as sstr_t.
- * @param buffer the buffer
- * @return the result of <code>sstrn()</code> with the buffer space and size
- * as arguments
- */
-#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size)
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_BUFFER_H */
-
--- a/ucx/ucx/list.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,512 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * Doubly linked list implementation.
- * 
- * @file   list.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_LIST_H
-#define	UCX_LIST_H
-
-#include "ucx.h"
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * Loop statement for UCX lists.
- * 
- * The first argument is the name of the iteration variable. The scope of
- * this variable is limited to the <code>UCX_FOREACH</code> statement.
- * 
- * The second argument is a pointer to the list. In most cases this will be the
- * pointer to the first element of the list, but it may also be an arbitrary
- * element of the list. The iteration will then start with that element.
- * 
- * @param list The first element of the list
- * @param elem The variable name of the element
- */
-#define UCX_FOREACH(elem,list) \
-        for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next)
-
-/**
- * UCX list type.
- * @see UcxList
- */
-typedef struct UcxList UcxList;
-
-/**
- * UCX list structure.
- */
-struct UcxList {
-    /**
-     * List element payload.
-     */
-    void    *data;
-    /**
-     * Pointer to the next list element or <code>NULL</code>, if this is the
-     * last element.
-     */
-    UcxList *next;
-    /**
-     * Pointer to the previous list element or <code>NULL</code>, if this is
-     * the first element.
-     */
-    UcxList *prev;
-};
-
-/**
- * Creates an element-wise copy of a list.
- * 
- * This function clones the specified list by creating new list elements and
- * copying the data with the specified copy_func(). If no copy_func() is
- * specified, a shallow copy is created and the new list will reference the
- * same data as the source list.
- * 
- * @param list the list to copy
- * @param cpyfnc a pointer to the function that shall copy an element (may be
- * <code>NULL</code>)
- * @param data additional data for the copy_func()
- * @return a pointer to the copy
- */
-UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data);
-
-/**
- * Creates an element-wise copy of a list using a UcxAllocator.
- * 
- * See ucx_list_clone() for details.
- * 
- * You might want to pass the allocator via the <code>data</code> parameter,
- * to access it within the copy function for making deep copies.
- * 
- * @param allocator the allocator to use
- * @param list the list to copy
- * @param cpyfnc a pointer to the function that shall copy an element (may be
- * <code>NULL</code>)
- * @param data additional data for the copy_func()
- * @return a pointer to the copy
- * @see ucx_list_clone()
- */
-UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list,
-        copy_func cpyfnc, void* data);
-
-/**
- * Compares two UCX lists element-wise by using a compare function.
- * 
- * Each element of the two specified lists are compared by using the specified
- * compare function and the additional data. The type and content of this
- * additional data depends on the cmp_func() used.
- * 
- * If the list pointers denote elements within a list, the lists are compared
- * starting with the denoted elements. Thus any previous elements are not taken
- * into account. This might be useful to check, if certain list tails match
- * each other.
- * 
- * @param list1 the first list
- * @param list2 the second list
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the two lists equal element-wise, 0 otherwise
- */
-int ucx_list_equals(const UcxList *list1, const UcxList *list2,
-        cmp_func cmpfnc, void* data);
-
-/**
- * Destroys the entire list.
- * 
- * The members of the list are not automatically freed, so ensure they are
- * otherwise referenced or destroyed by ucx_list_free_contents().
- * Otherwise, a memory leak is likely to occur.
- * 
- * <b>Caution:</b> the argument <b>MUST</b> denote an entire list (i.e. a call
- * to ucx_list_first() on the argument must return the argument itself)
- * 
- * @param list the list to free
- * @see ucx_list_free_contents()
- */
-void ucx_list_free(UcxList *list);
-
-/**
- * Destroys the entire list using a UcxAllocator.
- * 
- * See ucx_list_free() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list to free
- * @see ucx_list_free()
- */
-void ucx_list_free_a(UcxAllocator *allocator, UcxList *list);
-
-/**
- * Destroys the contents of the specified list by calling the specified
- * destructor on each of them.
- * 
- * Note, that the contents are not usable afterwards and the list should be
- * destroyed with ucx_list_free().
- *
- * If no destructor is specified (<code>NULL</code>), stdlib's free() is used.
- * 
- * @param list the list for which the contents shall be freed
- * @param destr optional destructor function
- * @see ucx_list_free()
- */
-void ucx_list_free_content(UcxList* list, ucx_destructor destr);
-
-
-/**
- * Inserts an element at the end of the list.
- * 
- * This is generally an O(n) operation, as the end of the list is retrieved with
- * ucx_list_last().
- * 
- * @param list the list where to append the data, or <code>NULL</code> to
- * create a new list
- * @param data the data to insert
- * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
- * the newly created list otherwise
- */
-UcxList *ucx_list_append(UcxList *list, void *data);
-
-/**
- * Inserts an element at the end of the list using a UcxAllocator.
- * 
- * See ucx_list_append() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list where to append the data, or <code>NULL</code> to
- * create a new list
- * @param data the data to insert
- * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
- * the newly created list otherwise
- * @see ucx_list_append()
- */
-UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data);
-
-
-/**
- * Inserts an element at the beginning of the list.
- * 
- * You <i>should</i> overwrite the old list pointer by calling
- * <code>mylist = ucx_list_prepend(mylist, mydata);</code>. However, you may
- * also perform successive calls of ucx_list_prepend() on the same list pointer,
- * as this function always searchs for the head of the list with
- * ucx_list_first().
- * 
- * @param list the list where to insert the data or <code>NULL</code> to create
- * a new list
- * @param data the data to insert
- * @return a pointer to the new list head
- */
-UcxList *ucx_list_prepend(UcxList *list, void *data);
-
-/**
- * Inserts an element at the beginning of the list using a UcxAllocator.
- * 
- * See ucx_list_prepend() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list where to insert the data or <code>NULL</code> to create
- * a new list
- * @param data the data to insert
- * @return a pointer to the new list head
- * @see ucx_list_prepend()
- */
-UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data);
-
-/**
- * Concatenates two lists.
- * 
- * Either of the two arguments may be <code>NULL</code>.
- * 
- * This function modifies the references to the next/previous element of
- * the last/first element of <code>list1</code>/<code>
- * list2</code>.
- * 
- * @param list1 first list
- * @param list2 second list
- * @return if <code>list1</code> is <code>NULL</code>, <code>list2</code> is
- * returned, otherwise <code>list1</code> is returned
- */
-UcxList *ucx_list_concat(UcxList *list1, UcxList *list2);
-
-/**
- * Returns the first element of a list.
- * 
- * If the argument is the list pointer, it is directly returned. Otherwise
- * this function traverses to the first element of the list and returns the
- * list pointer.
- * 
- * @param elem one element of the list
- * @return the first element of the list, the specified element is a member of
- */
-UcxList *ucx_list_first(const UcxList *elem);
-
-/**
- * Returns the last element of a list.
- * 
- * If the argument has no successor, it is the last element and therefore
- * directly returned. Otherwise this function traverses to the last element of
- * the list and returns it.
- * 
- * @param elem one element of the list
- * @return the last element of the list, the specified element is a member of
- */
-UcxList *ucx_list_last(const UcxList *elem);
-
-/**
- * Returns the list element at the specified index.
- * 
- * @param list the list to retrieve the element from
- * @param index index of the element to return
- * @return the element at the specified index or <code>NULL</code>, if the
- * index is greater than the list size
- */
-UcxList *ucx_list_get(const UcxList *list, size_t index);
-
-/**
- * Returns the index of an element.
- * 
- * @param list the list where to search for the element
- * @param elem the element to find
- * @return the index of the element or -1 if the list does not contain the
- * element
- */
-ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem);
-
-/**
- * Returns the element count of the list.
- * 
- * @param list the list whose elements are counted
- * @return the element count
- */
-size_t ucx_list_size(const UcxList *list);
-
-/**
- * Returns the index of an element containing the specified data.
- *
- * This function uses a cmp_func() to compare the data of each list element
- * with the specified data. If no cmp_func is provided, the pointers are
- * compared.
- * 
- * If the list contains the data more than once, the index of the first
- * occurrence is returned.
- *  
- * @param list the list where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return the index of the element containing the specified data or -1 if the
- * data is not found in this list
- */
-ssize_t ucx_list_find(const UcxList *list, void *elem,
-    cmp_func cmpfnc, void *data);
-
-/**
- * Checks, if a list contains a specific element.
- * 
- * An element is found, if ucx_list_find() returns a value greater than -1.
- * 
- * @param list the list where to search for the data
- * @param elem the element data
- * @param cmpfnc the compare function
- * @param data additional data for the compare function
- * @return 1, if and only if the list contains the specified element data
- * @see ucx_list_find()
- */
-int ucx_list_contains(const UcxList *list, void *elem,
-    cmp_func cmpfnc, void *data);
-
-/**
- * Sorts a UcxList with natural merge sort.
- * 
- * This function uses O(n) additional temporary memory for merge operations
- * that is automatically freed after each merge.
- * 
- * As the head of the list might change, you <b>MUST</b> call this function
- * as follows: <code>mylist = ucx_list_sort(mylist, mycmpfnc, mydata);</code>.
- * 
- * @param list the list to sort
- * @param cmpfnc the function that shall be used to compare the element data
- * @param data additional data for the cmp_func()
- * @return the sorted list
- */
-UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data);
-
-/**
- * Removes an element from the list.
- * 
- * If the first element is removed, the list pointer changes. So it is
- * <i>highly recommended</i> to <i>always</i> update the pointer by calling
- * <code>mylist = ucx_list_remove(mylist, myelem);</code>.
- * 
- * @param list the list from which the element shall be removed
- * @param element the element to remove
- * @return returns the updated list pointer or <code>NULL</code>, if the list
- * is now empty
- */
-UcxList *ucx_list_remove(UcxList *list, UcxList *element);
-
-/**
- * Removes an element from the list using a UcxAllocator.
- * 
- * See ucx_list_remove() for details.
- * 
- * @param allocator the allocator to use
- * @param list the list from which the element shall be removed
- * @param element the element to remove
- * @return returns the updated list pointer or <code>NULL</code>, if the list
- * @see ucx_list_remove()
- */
-UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list,
-        UcxList *element);
-
-/**
- * Returns the union of two lists.
- * 
- * The union is a list of unique elements regarding cmpfnc obtained from
- * both source lists.
- * 
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the union
- */
-UcxList* ucx_list_union(const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the union of two lists.
- * 
- * The union is a list of unique elements regarding cmpfnc obtained from
- * both source lists.
- * 
- * @param allocator allocates the new list elements
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the union
- */
-UcxList* ucx_list_union_a(UcxAllocator *allocator,
-    const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the intersection of two lists.
- * 
- * The intersection contains all elements of the left list
- * (including duplicates) that can be found in the right list.
- * 
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the intersection
- */
-UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the intersection of two lists.
- * 
- * The intersection contains all elements of the left list
- * (including duplicates) that can be found in the right list.
- * 
- * @param allocator allocates the new list elements
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the intersection
- */
-UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
-    const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the difference of two lists.
- * 
- * The difference contains all elements of the left list
- * (including duplicates) that are not equal to any element of the right list.
- * 
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the difference
- */
-UcxList* ucx_list_difference(const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the difference of two lists.
- * 
- * The difference contains all elements of the left list
- * (including duplicates) that are not equal to any element of the right list.
- * 
- * @param allocator allocates the new list elements
- * @param left the left source list
- * @param right the right source list
- * @param cmpfnc a function to compare elements
- * @param cmpdata additional data for the compare function
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the difference
- */
-UcxList* ucx_list_difference_a(UcxAllocator *allocator,
-    const UcxList *left, const UcxList *right,
-    cmp_func cmpfnc, void* cmpdata,
-    copy_func cpfnc, void* cpdata);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_LIST_H */
-
--- a/ucx/ucx/logging.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * Logging API.
- * 
- * @file   logging.h
- * @author Mike Becker, Olaf Wintermann
- */
-#ifndef UCX_LOGGING_H
-#define UCX_LOGGING_H
-
-#include "ucx.h"
-#include "map.h"
-#include "string.h"
-#include <stdio.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* leave enough space for custom log levels */
-
-/** Log level for error messages. */
-#define UCX_LOGGER_ERROR        0x00
-    
-/** Log level for warning messages. */
-#define UCX_LOGGER_WARN         0x10
-
-/** Log level for information messages. */
-#define UCX_LOGGER_INFO         0x20
-
-/** Log level for debug messages. */
-#define UCX_LOGGER_DEBUG        0x30
-
-/** Log level for trace messages. */
-#define UCX_LOGGER_TRACE        0x40
-
-/**
- * Output flag for the log level. 
- * If this flag is set, the log message will contain the log level.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_LEVEL        0x01
-
-/**
- * Output flag for the timestmap.
- * If this flag is set, the log message will contain the timestmap.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_TIMESTAMP    0x02
-
-/**
- * Output flag for the source.
- * If this flag is set, the log message will contain the source file and line
- * number.
- * @see UcxLogger.mask
- */
-#define UCX_LOGGER_SOURCE       0x04
-
-/**
- * The UCX Logger object.
- */
-typedef struct {
-    /** The stream this logger writes its messages to.*/
-    void *stream;
-
-    /**
-     * The write function that shall be used.
-     * For standard file or stdout loggers this might be standard fwrite
-     * (default).
-     */
-    write_func writer;
-
-    /**
-     * The date format for timestamp outputs including the delimiter
-     * (default: <code>"%F %T %z "</code>).
-     * @see UCX_LOGGER_TIMESTAMP
-     */
-    char *dateformat;
-
-    /**
-     * The level, this logger operates on.
-     * If a log command is issued, the message will only be logged, if the log
-     * level of the message is less or equal than the log level of the logger.
-     */
-    unsigned int level;
-
-    /**
-     * A configuration mask for automatic output. 
-     * For each flag that is set, the logger automatically outputs some extra
-     * information like the timestamp or the source file and line number.
-     * See the documentation for the flags for details.
-     */
-    unsigned int mask;
-
-    /**
-     * A map of valid log levels for this logger.
-     * 
-     * The keys represent all valid log levels and the values provide string
-     * representations, that are used, if the UCX_LOGGER_LEVEL flag is set.
-     * 
-     * The exact data types are <code>unsigned int</code> for the key and
-     * <code>const char*</code> for the value.
-     * 
-     * @see UCX_LOGGER_LEVEL
-     */
-    UcxMap* levels;
-} UcxLogger;
-
-/**
- * Creates a new logger.
- * @param stream the stream, which the logger shall write to
- * @param level the level on which the logger shall operate
- * @param mask configuration mask (cf. UcxLogger.mask)
- * @return a new logger object
- */
-UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask);
-
-/**
- * Destroys the logger.
- * 
- * The map containing the valid log levels is also automatically destroyed.
- * 
- * @param logger the logger to destroy
- */
-void ucx_logger_free(UcxLogger* logger);
-
-/**
- * Internal log function - use macros instead.
- * 
- * This function uses the <code>format</code> and variadic arguments for a
- * printf()-style output of the log message.
- * 
- * Dependent on the UcxLogger.mask some information is prepended. The complete
- * format is:
- * 
- * <code>[LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message</code>
- *
- * The source file name is reduced to the actual file name. This is necessary to
- * get consistent behavior over different definitions of the __FILE__ macro.
- *
- * <b>Attention:</b> the message (including automatically generated information)
- * is limited to 4096 characters. The level description is limited to
- * 256 characters and the timestamp string is limited to 128 characters.
- * 
- * @param logger the logger to use
- * @param level the level to log on
- * @param file information about the source file
- * @param line information about the source line number
- * @param format format string
- * @param ... arguments
- * @see ucx_logger_log()
- */
-void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
-        const unsigned int line, const char* format, ...);
-
-/**
- * Registers a custom log level.
- * @param logger the logger
- * @param level the log level as unsigned integer
- * @param name a string literal describing the level
- */
-#define ucx_logger_register_level(logger, level, name) {\
-        unsigned int l; \
-            l = level; \
-            ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \
-        } while (0);
-
-/**
- * Logs a message at the specified level.
- * @param logger the logger to use
- * @param level the level to log the message on
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_log(logger, level, ...) \
-    ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__)
-
-/**
- * Shortcut for logging an error message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_error(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__)
-
-/**
- * Shortcut for logging an information message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_info(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__)
-
-/**
- * Shortcut for logging a warning message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_warn(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__)
-
-/**
- * Shortcut for logging a debug message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_debug(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__)
-
-/**
- * Shortcut for logging a trace message.
- * @param logger the logger to use
- * @param ... format string and arguments
- * @see ucx_logger_logf()
- */
-#define ucx_logger_trace(logger, ...) \
-    ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* UCX_LOGGING_H */
--- a/ucx/ucx/map.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,549 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-/**
- * @file map.h
- * 
- * Hash map implementation.
- * 
- * This implementation uses murmur hash 2 and separate chaining with linked
- * lists.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_MAP_H
-#define	UCX_MAP_H
-
-#include "ucx.h"
-#include "string.h"
-#include "allocator.h"
-#include <stdio.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * Loop statement for UCX maps.
- * 
- * The <code>key</code> variable is implicitly defined, but the
- * <code>value</code> variable must be already declared as type information
- * cannot be inferred.
- * 
- * @param key the variable name for the key
- * @param value the variable name for the value
- * @param iter a UcxMapIterator
- * @see ucx_map_iterator()
- */
-#define UCX_MAP_FOREACH(key,value,iter) \
-        for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);)
-
-/** Type for the UCX map. @see UcxMap */
-typedef struct UcxMap          UcxMap;
-
-/** Type for a key of a UcxMap. @see UcxKey */
-typedef struct UcxKey          UcxKey;
-
-/** Type for an element of a UcxMap. @see UcxMapElement */
-typedef struct UcxMapElement   UcxMapElement;
-
-/** Type for an iterator over a UcxMap. @see UcxMapIterator */
-typedef struct UcxMapIterator  UcxMapIterator;
-
-/** Structure for the UCX map. */
-struct UcxMap {
-    /** An allocator that is used for the map elements. */
-    UcxAllocator  *allocator;
-    /** The array of map element lists. */
-    UcxMapElement **map;
-    /** The size of the map is the length of the element list array. */
-    size_t        size;
-    /** The count of elements currently stored in this map. */
-    size_t        count;
-};
-
-/** Structure to publicly denote a key of a UcxMap. */
-struct UcxKey {
-    /** The key data. */
-    const void *data;
-    /** The length of the key data. */
-    size_t     len;
-    /** A cache for the hash value of the key data. */
-    int        hash;
-};
-
-/** Internal structure for a key of a UcxMap. */
-struct UcxMapKey {
-    /** The key data. */
-    void    *data;
-    /** The length of the key data. */
-    size_t  len;
-    /** The hash value of the key data. */
-    int     hash;
-};
-
-/** Structure for an element of a UcxMap. */
-struct UcxMapElement {
-    /** The value data. */
-    void              *data;
-    
-    /** A pointer to the next element in the current list. */
-    UcxMapElement     *next;
-    
-    /** The corresponding key. */
-    struct UcxMapKey  key;
-};
-
-/** Structure for an iterator over a UcxMap. */
-struct UcxMapIterator {
-    /** The map to iterate over. */
-    UcxMap const  *map;
-    
-    /** The current map element. */
-    UcxMapElement *cur;
-    
-    /**
-     * The current index of the element list array.
-     * <b>Attention: </b> this is <b>NOT</b> the element index! Do <b>NOT</b>
-     * manually iterate over the map by increasing this index. Use
-     * ucx_map_iter_next().
-     * @see UcxMap.map*/
-    size_t        index;
-};
-
-/**
- * Creates a new hash map with the specified size.
- * @param size the size of the hash map
- * @return a pointer to the new hash map
- */
-UcxMap *ucx_map_new(size_t size);
-
-/**
- * Creates a new hash map with the specified size using a UcxAllocator.
- * @param allocator the allocator to use
- * @param size the size of the hash map
- * @return a pointer to the new hash map
- */
-UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size);
-
-/**
- * Frees a hash map.
- * 
- * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
- * before calling this function to achieve that.
- * 
- * @param map the map to be freed
- * @see ucx_map_free_content()
- */
-void ucx_map_free(UcxMap *map);
-
-/**
- * Frees the contents of a hash map.
- * 
- * This is a convenience function that iterates over the map and passes all
- * values to the specified destructor function.
- * 
- * If no destructor is specified (<code>NULL</code>), the free() function of
- * the map's own allocator is used.
- * 
- * You must ensure, that it is valid to pass each value in the map to the same
- * destructor function.
- * 
- * You should free or clear the map afterwards, as the contents will be invalid.
- * 
- * @param map for which the contents shall be freed
- * @param destr optional pointer to a destructor function
- * @see ucx_map_free()
- * @see ucx_map_clear()
- */
-void ucx_map_free_content(UcxMap *map, ucx_destructor destr);
-
-/**
- * Clears a hash map.
- * 
- * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
- * before calling this function to achieve that.
- * 
- * @param map the map to be cleared
- * @see ucx_map_free_content()
- */
-void ucx_map_clear(UcxMap *map);
-
-
-/**
- * Copies contents from a map to another map using a copy function.
- * 
- * <b>Note:</b> The destination map does not need to be empty. However, if it
- * contains data with keys that are also present in the source map, the contents
- * are overwritten.
- * 
- * @param from the source map
- * @param to the destination map
- * @param fnc the copy function or <code>NULL</code> if the pointer address
- * shall be copied
- * @param data additional data for the copy function
- * @return 0 on success or a non-zero value on memory allocation errors
- */
-int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data);
-
-/**
- * Clones the map and rehashes if necessary.
- * 
- * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
- * This function <i>always</i> ensures a new UcxMap.size of at least
- * 2.5*UcxMap.count.
- * 
- * @param map the map to clone
- * @param fnc the copy function to use or <code>NULL</code> if the new and
- * the old map shall share the data pointers
- * @param data additional data for the copy function
- * @return the cloned map
- * @see ucx_map_copy()
- */
-UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data);
-
-/**
- * Clones the map and rehashes if necessary.
- *
- * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
- * This function <i>always</i> ensures a new UcxMap.size of at least
- * 2.5*UcxMap.count.
- *
- * @param allocator the allocator to use for the cloned map
- * @param map the map to clone
- * @param fnc the copy function to use or <code>NULL</code> if the new and
- * the old map shall share the data pointers
- * @param data additional data for the copy function
- * @return the cloned map
- * @see ucx_map_copy()
- */
-UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
-                        UcxMap const *map, copy_func fnc, void *data);
-
-/**
- * Increases size of the hash map, if necessary.
- * 
- * The load value is 0.75*UcxMap.size. If the element count exceeds the load
- * value, the map needs to be rehashed. Otherwise no action is performed and
- * this function simply returns 0.
- * 
- * The rehashing process ensures, that the UcxMap.size is at least
- * 2.5*UcxMap.count. So there is enough room for additional elements without
- * the need of another soon rehashing.
- * 
- * You can use this function to dramatically increase access performance.
- * 
- * @param map the map to rehash
- * @return 1, if a memory allocation error occurred, 0 otherwise
- */
-int ucx_map_rehash(UcxMap *map);
-
-/**
- * Puts a key/value-pair into the map.
- * 
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-int ucx_map_put(UcxMap *map, UcxKey key, void *value);
-
-/**
- * Retrieves a value by using a key.
- * 
- * @param map the map
- * @param key the key
- * @return the value
- */
-void* ucx_map_get(UcxMap const *map, UcxKey key);
-
-/**
- * Removes a key/value-pair from the map by using the key.
- * 
- * @param map the map
- * @param key the key
- * @return the removed value
- */
-void* ucx_map_remove(UcxMap *map, UcxKey key);
-
-/**
- * Shorthand for putting data with a sstr_t key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_sstr_put(map, key, value) \
-    ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value)
-
-/**
- * Shorthand for putting data with a C string key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_cstr_put(map, key, value) \
-    ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value)
-
-/**
- * Shorthand for putting data with an integer key into the map.
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- * @see ucx_map_put()
- */
-#define ucx_map_int_put(map, key, value) \
-    ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value)
-
-/**
- * Shorthand for getting data from the map with a sstr_t key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_sstr_get(map, key) \
-    ucx_map_get(map, ucx_key(key.ptr, key.length))
-
-/**
- * Shorthand for getting data from the map with a C string key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_cstr_get(map, key) \
-    ucx_map_get(map, ucx_key(key, strlen(key)))
-
-/**
- * Shorthand for getting data from the map with an integer key.
- * @param map the map
- * @param key the key
- * @return the value
- * @see ucx_map_get()
- */
-#define ucx_map_int_get(map, key) \
-    ucx_map_get(map, ucx_key(&key, sizeof(int)))
-
-/**
- * Shorthand for removing data from the map with a sstr_t key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_sstr_remove(map, key) \
-    ucx_map_remove(map, ucx_key(key.ptr, key.length))
-
-/**
- * Shorthand for removing data from the map with a C string key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_cstr_remove(map, key) \
-    ucx_map_remove(map, ucx_key(key, strlen(key)))
-
-/**
- * Shorthand for removing data from the map with an integer key.
- * @param map the map
- * @param key the key
- * @return the removed value
- * @see ucx_map_remove()
- */
-#define ucx_map_int_remove(map, key) \
-    ucx_map_remove(map, ucx_key(&key, sizeof(key)))
-
-/**
- * Creates a UcxKey based on the given data.
- * 
- * This function implicitly computes the hash.
- * 
- * @param data the data for the key
- * @param len the length of the data
- * @return a UcxKey with implicitly computed hash
- * @see ucx_hash()
- */
-UcxKey ucx_key(const void *data, size_t len);
-
-/**
- * Computes a murmur hash-2.
- * 
- * @param data the data to hash
- * @param len the length of the data
- * @return the murmur hash-2 of the data
- */
-int ucx_hash(const char *data, size_t len);
-
-/**
- * Creates an iterator for a map.
- * 
- * <b>Note:</b> A UcxMapIterator iterates over all elements in all element
- * lists successively. Therefore the order highly depends on the key hashes and
- * may vary under different map sizes. So generally you may <b>NOT</b> rely on
- * the iteration order.
- * 
- * <b>Note:</b> The iterator is <b>NOT</b> initialized. You need to call
- * ucx_map_iter_next() at least once before accessing any information. However,
- * it is not recommended to access the fields of a UcxMapIterator directly.
- * 
- * @param map the map to create the iterator for
- * @return an iterator initialized on the first element of the
- * first element list
- * @see ucx_map_iter_next()
- */
-UcxMapIterator ucx_map_iterator(UcxMap const *map);
-
-/**
- * Proceeds to the next element of the map (if any).
- * 
- * Subsequent calls on the same iterator proceed to the next element and
- * store the key/value-pair into the memory specified as arguments of this
- * function.
- * 
- * If no further elements are found, this function returns zero and leaves the
- * last found key/value-pair in memory.
- * 
- * @param iterator the iterator to use
- * @param key a pointer to the memory where to store the key
- * @param value a pointer to the memory where to store the value
- * @return 1, if another element was found, 0 if all elements has been processed
- * @see ucx_map_iterator()
- */
-int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value);
-
-/**
- * Returns the union of two maps.
- *
- * The union is a fresh map which is filled by two successive calls of
- * ucx_map_copy() on the two input maps.
- *
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new map containing the union
- */
-UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
-                      copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the union of two maps.
- *
- * The union is a fresh map which is filled by two successive calls of
- * ucx_map_copy() on the two input maps.
- *
- * @param allocator the allocator that shall be used by the new map
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new map containing the union
- */
-UcxMap* ucx_map_union_a(UcxAllocator *allocator,
-                        const UcxMap *first, const UcxMap *second,
-                        copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the intersection of two maps.
- *
- * The intersection is defined as a copy of the first map with every element
- * removed that has no valid key in the second map.
- *
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new map containing the intersection
- */
-UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
-                             copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the intersection of two maps.
- *
- * The intersection is defined as a copy of the first map with every element
- * removed that has no valid key in the second map.
- *
- * @param allocator the allocator that shall be used by the new map
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new map containing the intersection
- */
-UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
-                               const UcxMap *first, const UcxMap *second,
-                               copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the difference of two maps.
- *
- * The difference contains a copy of all elements of the first map
- * for which the corresponding keys cannot be found in the second map.
- *
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the difference
- */
-UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
-                           copy_func cpfnc, void* cpdata);
-
-/**
- * Returns the difference of two maps.
- *
- * The difference contains a copy of all elements of the first map
- * for which the corresponding keys cannot be found in the second map.
- *
- * @param allocator the allocator that shall be used by the new map
- * @param first the first source map
- * @param second the second source map
- * @param cpfnc a function to copy the elements
- * @param cpdata additional data for the copy function
- * @return a new list containing the difference
- */
-UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
-                             const UcxMap *first, const UcxMap *second,
-                             copy_func cpfnc, void* cpdata);
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_MAP_H */
-
--- a/ucx/ucx/mempool.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-/**
- * @file mempool.h
- * 
- * Memory pool implementation.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_MEMPOOL_H
-#define	UCX_MEMPOOL_H
-
-#include "ucx.h"
-#include "allocator.h"
-#include <stddef.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UCX mempool structure.
- */
-typedef struct {
-    /** UcxAllocator based on this pool */
-    UcxAllocator *allocator;
-    
-    /** List of pointers to pooled memory. */
-    void         **data;
-    
-    /** Count of pooled memory items. */
-    size_t       ndata;
-    
-    /** Memory pool size. */
-    size_t       size;
-} UcxMempool;
-
-/** Shorthand for a new default memory pool with a capacity of 16 elements. */
-#define ucx_mempool_new_default() ucx_mempool_new(16)
-
-
-/**
- * Creates a memory pool with the specified initial size.
- * 
- * As the created memory pool automatically grows in size by factor two when
- * trying to allocate memory on a full pool, it is recommended that you use
- * a power of two for the initial size.
- * 
- * @param n initial pool size (should be a power of two, e.g. 16)
- * @return a pointer to the new memory pool
- * @see ucx_mempool_new_default()
- */
-UcxMempool *ucx_mempool_new(size_t n);
-
-/**
- * Resizes a memory pool.
- * 
- * This function will fail if the new capacity is not sufficient for the
- * present data.
- * 
- * @param pool the pool to resize
- * @param newcap the new capacity
- * @return zero on success or non-zero on failure
- */
-int ucx_mempool_chcap(UcxMempool *pool, size_t newcap);
-
-/**
- * Allocates pooled memory.
- * 
- * @param pool the memory pool
- * @param n amount of memory to allocate
- * @return a pointer to the allocated memory
- * @see ucx_allocator_malloc()
- */
-void *ucx_mempool_malloc(UcxMempool *pool, size_t n);
-/**
- * Allocates a pooled memory array.
- * 
- * The content of the allocated memory is set to zero.
- * 
- * @param pool the memory pool
- * @param nelem amount of elements to allocate
- * @param elsize amount of memory per element
- * @return a pointer to the allocated memory
- * @see ucx_allocator_calloc()
- */
-void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize);
-
-/**
- * Reallocates pooled memory.
- * 
- * If the memory to be reallocated is not contained by the specified pool, the
- * behavior is undefined.
- * 
- * @param pool the memory pool
- * @param ptr a pointer to the memory that shall be reallocated
- * @param n the new size of the memory
- * @return a pointer to the new location of the memory
- * @see ucx_allocator_realloc()
- */
-void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n);
-
-/**
- * Frees pooled memory.
- * 
- * Before freeing the memory, the specified destructor function (if any)
- * is called.
- * 
- * If you specify memory, that is not pooled by the specified memory pool, the
- * program will terminate with a call to <code>abort()</code>.
- * 
- * @param pool the memory pool
- * @param ptr a pointer to the memory that shall be freed
- * @see ucx_mempool_set_destr()
- */
-void ucx_mempool_free(UcxMempool *pool, void *ptr);
-
-/**
- * Destroys a memory pool.
- * 
- * For each element the destructor function (if any) is called and the element
- * is freed.
- * 
- * Each of the registered destructor function that has no corresponding element
- * within the pool (namely those registered by ucx_mempool_reg_destr) is
- * called interleaving with the element destruction, but with guarantee to the
- * order in which they were registered (FIFO order).
- * 
- * 
- * @param pool the mempool to destroy
- */
-void ucx_mempool_destroy(UcxMempool *pool);
-
-/**
- * Sets a destructor function for the specified memory.
- * 
- * The destructor is automatically called when the memory is freed or the
- * pool is destroyed.
- * A destructor for pooled memory <b>MUST NOT</b> free the memory itself,
- * as this is done by the pool. Use a destructor to free any resources
- * managed by the pooled object.
- * 
- * The only requirement for the specified memory is, that it <b>MUST</b> be
- * pooled memory by a UcxMempool or an element-compatible mempool. The pointer
- * to the destructor function is saved in a reserved area before the actual
- * memory.
- * 
- * @param ptr pooled memory
- * @param func a pointer to the destructor function
- * @see ucx_mempool_free()
- * @see ucx_mempool_destroy()
- */
-void ucx_mempool_set_destr(void *ptr, ucx_destructor func);
-
-/**
- * Registers a destructor function for the specified (non-pooled) memory.
- *
- * This is useful, if you have memory that has not been allocated by a mempool,
- * but shall be managed by a mempool.
- * 
- * This function creates an entry in the specified mempool and the memory will
- * therefore (logically) convert to pooled memory.
- * <b>However, this does not cause the memory to be freed automatically!</b>.
- * If you want to use this function, make the memory pool free non-pooled
- * memory, the specified destructor function must call <code>free()</code>
- * by itself. But keep in mind, that you then MUST NOT use this destructor
- * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it
- * would cause a double-free.
- * 
- * @param pool the memory pool
- * @param ptr data the destructor is registered for
- * @param destr a pointer to the destructor function
- */
-void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_MEMPOOL_H */
-
--- a/ucx/ucx/properties.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * @file properties.h
- * 
- * Load / store utilities for properties files.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_PROPERTIES_H
-#define	UCX_PROPERTIES_H
-
-#include "ucx.h"
-#include "map.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-/**
- * UcxProperties object for parsing properties data.
- * Most of the fields are for internal use only. You may configure the
- * properties parser, e.g. by changing the used delimiter or specifying 
- * up to three different characters that shall introduce comments.
- */
-typedef struct {
-    /**
-     * Input buffer (don't set manually).
-     * Automatically set by calls to ucx_properties_fill().
-     */
-    char   *buffer;
-    
-    /**
-     * Length of the input buffer (don't set manually).
-     * Automatically set by calls to ucx_properties_fill().
-     */
-    size_t buflen;
-    
-    /**
-     * Current buffer position (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t pos;
-    
-    /**
-     * Internal temporary buffer (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    char   *tmp;
-    
-    /**
-     * Internal temporary buffer length (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t tmplen;
-    
-    /**
-     * Internal temporary buffer capacity (don't set manually).
-     * Used by ucx_properties_next().
-     */
-    size_t tmpcap;
-    
-    /**
-     * Parser error code.
-     * This is always 0 on success and a nonzero value on syntax errors.
-     * The value is set by ucx_properties_next().
-     */
-    int    error;
-    
-    /**
-     * The delimiter that shall be used.
-     * This is '=' by default.
-     */
-    char   delimiter;
-    
-    /**
-     * The first comment character.
-     * This is '#' by default.
-     */
-    char   comment1;
-    
-    /**
-     * The second comment character.
-     * This is not set by default.
-     */
-    char   comment2;
-    
-    /**
-     * The third comment character.
-     * This is not set by default.
-     */
-    char   comment3;
-} UcxProperties;
-
-
-/**
- * Constructs a new UcxProperties object.
- * @return a pointer to the new UcxProperties object
- */
-UcxProperties *ucx_properties_new();
-
-/**
- * Destroys a UcxProperties object.
- * @param prop the UcxProperties object to destroy
- */
-void ucx_properties_free(UcxProperties *prop);
-
-/**
- * Sets the input buffer for the properties parser.
- * 
- * After calling this function, you may parse the data by calling
- * ucx_properties_next() until it returns 0. The function ucx_properties2map()
- * is a convenience function that reads as much data as possible by using this
- * function.
- * 
- * 
- * @param prop the UcxProperties object
- * @param buf a pointer to the new buffer
- * @param len the payload length of the buffer
- * @see ucx_properties_next()
- * @see ucx_properties2map()
- */
-void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
-
-/**
- * Retrieves the next key/value-pair.
- * 
- * This function returns a nonzero value as long as there are key/value-pairs
- * found. If no more key/value-pairs are found, you may refill the input buffer
- * with ucx_properties_fill().
- * 
- * <b>Attention:</b> the sstr_t.ptr pointers of the output parameters point to
- * memory within the input buffer of the parser and will get invalid some time.
- * If you want long term copies of the key/value-pairs, use sstrdup() after
- * calling this function.
- * 
- * @param prop the UcxProperties object
- * @param name a pointer to the sstr_t that shall contain the property name
- * @param value a pointer to the sstr_t that shall contain the property value
- * @return Nonzero, if a key/value-pair was successfully retrieved
- * @see ucx_properties_fill()
- */
-int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value);
-
-/**
- * Retrieves all available key/value-pairs and puts them into a UcxMap.
- * 
- * This is done by successive calls to ucx_properties_next() until no more
- * key/value-pairs can be retrieved.
- * 
- * The memory for the map values is allocated by the map's own allocator.
- * 
- * @param prop the UcxProperties object
- * @param map the target map
- * @return The UcxProperties.error code (i.e. 0 on success).
- * @see ucx_properties_fill()
- * @see UcxMap.allocator
- */
-int ucx_properties2map(UcxProperties *prop, UcxMap *map);
-
-/**
- * Loads a properties file to a UcxMap.
- * 
- * This is a convenience function that reads data from an input
- * stream until the end of the stream is reached.
- * 
- * @param map the map object to write the key/value-pairs to
- * @param file the <code>FILE*</code> stream to read from
- * @return 0 on success, or a non-zero value on error
- * 
- * @see ucx_properties_fill()
- * @see ucx_properties2map()
- */
-int ucx_properties_load(UcxMap *map, FILE *file);
-
-/**
- * Stores a UcxMap to a file.
- * 
- * The key/value-pairs are written by using the following format:
- * 
- * <code>[key] = [value]\\n</code>
- * 
- * @param map the map to store
- * @param file the <code>FILE*</code> stream to write to
- * @return 0 on success, or a non-zero value on error
- */
-int ucx_properties_store(UcxMap *map, FILE *file);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_PROPERTIES_H */
-
--- a/ucx/ucx/stack.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-/**
- * @file stack.h
- * 
- * Default stack memory allocation implementation.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_STACK_H
-#define	UCX_STACK_H
-
-#include "ucx.h"
-#include "allocator.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-
-/**
- * UCX stack structure.
- */
-typedef struct {
-    /** UcxAllocator based on this stack */
-    UcxAllocator allocator;
-    
-    /** Stack size. */
-    size_t size;
-    
-    /** Pointer to the bottom of the stack */
-    char *space;
-    
-    /** Pointer to the top of the stack */
-    char *top;
-} UcxStack;
-
-/**
- * Metadata for each UCX stack element.
- */
-struct ucx_stack_metadata {
-    /**
-     * Location of the previous element (<code>NULL</code> if this is the first)
-     */
-    char *prev;
-    
-    /** Size of this element */
-    size_t size;
-};
-
-/**
- * Initializes UcxStack structure with memory.
- * 
- * @param stack a pointer to an uninitialized stack structure
- * @param space the memory area that shall be managed
- * @param size size of the memory area
- * @return a new UcxStack structure
- */
-void ucx_stack_init(UcxStack *stack, char* space, size_t size);
-
-/**
- * Allocates stack memory.
- * 
- * @param stack a pointer to the stack
- * @param n amount of memory to allocate
- * @return a pointer to the allocated memory or <code>NULL</code> on stack
- * overflow
- * @see ucx_allocator_malloc()
- */
-void *ucx_stack_malloc(UcxStack *stack, size_t n);
-
-/**
- * Allocates memory with #ucx_stack_malloc() and copies the specified data if
- * the allocation was successful.
- * 
- * @param stack a pointer to the stack
- * @param n amount of memory to allocate
- * @param data a pointer to the data to copy
- * @return a pointer to the allocated memory
- * @see ucx_stack_malloc
- */
-void *ucx_stack_push(UcxStack *stack, size_t n, const void *data);
-
-/**
- * Allocates an array of stack memory
- * 
- * The content of the allocated memory is set to zero.
- * 
- * @param stack a pointer to the stack
- * @param nelem amount of elements to allocate
- * @param elsize amount of memory per element
- * @return a pointer to the allocated memory
- * @see ucx_allocator_calloc()
- */
-void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
-
-/**
- * Allocates memory with #ucx_stack_calloc() and copies the specified data if
- * the allocation was successful.
- * 
- * @param stack a pointer to the stack
- * @param nelem amount of elements to allocate
- * @param elsize amount of memory per element
- * @param data a pointer to the data
- * @return a pointer to the allocated memory
- * @see ucx_stack_calloc
- */
-void *ucx_stack_pusharr(UcxStack *stack,
-        size_t nelem, size_t elsize, const void *data);
-
-/**
- * Reallocates memory on the stack.
- * 
- * Shrinking memory is always safe. Extending memory can be very expensive. 
- * 
- * @param stack the stack
- * @param ptr a pointer to the memory that shall be reallocated
- * @param n the new size of the memory
- * @return a pointer to the new location of the memory
- * @see ucx_allocator_realloc()
- */
-void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n);
-
-/**
- * Frees memory on the stack.
- * 
- * Freeing stack memory behaves in a special way.
- * 
- * If the element, that should be freed, is the top most element of the stack,
- * it is removed from the stack. Otherwise it is marked as freed. Marked
- * elements are removed, when they become the top most elements of the stack.
- * 
- * @param stack a pointer to the stack
- * @param ptr a pointer to the memory that shall be freed
- */
-void ucx_stack_free(UcxStack *stack, void *ptr);
-
-
-/**
- * Returns the size of the top most element.
- * @param stack a pointer to the stack
- * @return the size of the top most element
- */
-#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\
-                                  (stack)->top - 1)->size : 0)
-
-/**
- * Removes the top most element from the stack and copies the content to <code>
- * dest</code>, if specified.
- * 
- * Use #ucx_stack_topsize()# to get the amount of memory that must be available
- * at the location of <code>dest</code>.
- * 
- * @param stack a pointer to the stack
- * @param dest the location where the contents shall be written to, or <code>
- * NULL</code>, if the element shall only be removed.
- * @see ucx_stack_free
- * @see ucx_stack_popn
- */
-#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1)
-
-/**
- * Removes the top most element from the stack and copies the content to <code>
- * dest</code>.
- * 
- * This function copies at most <code>n</code> bytes to the destination, but
- * the element is always freed as a whole.
- * If the element was larger than <code>n</code>, the remaining data is lost.
- * 
- * @param stack a pointer to the stack
- * @param dest the location where the contents shall be written to
- * @param n copies at most n bytes to <code>dest</code>
- * @see ucx_stack_pop
- */
-void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
-
-/**
- * Returns the remaining available memory on the specified stack.
- * 
- * @param stack a pointer to the stack
- * @return the remaining available memory
- */
-size_t ucx_stack_avail(UcxStack *stack);
-
-/**
- * Checks, if the stack is empty.
- * 
- * @param stack a pointer to the stack
- * @return nonzero, if the stack is empty, zero otherwise
- */
-#define ucx_stack_empty(stack) (!(stack)->top)
-
-/**
- * Computes a recommended size for the stack memory area. Note, that
- * reallocations have not been taken into account, so you might need to reserve
- * twice as much memory to allow many reallocations.
- * 
- * @param size the approximate payload
- * @param elems the approximate count of element allocations
- * @return a recommended size for the stack space based on the information
- * provided
- */
-#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \
-                                    (elems + 1))
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_STACK_H */
-
--- a/ucx/ucx/string.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1201 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * Bounded string implementation.
- * 
- * The UCX strings (<code>sstr_t</code>) provide an alternative to C strings.
- * The main difference to C strings is, that <code>sstr_t</code> does <b>not
- * need to be <code>NULL</code>-terminated</b>. Instead the length is stored
- * within the structure.
- * 
- * When using <code>sstr_t</code>, developers must be full aware of what type
- * of string (<code>NULL</code>-terminated) or not) they are using, when 
- * accessing the <code>char* ptr</code> directly.
- * 
- * The UCX string module provides some common string functions, known from
- * standard libc, working with <code>sstr_t</code>.
- * 
- * @file   string.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_STRING_H
-#define	UCX_STRING_H
-
-#include "ucx.h"
-#include "allocator.h"
-#include <stddef.h>
-
-/*
- * Use this macro to disable the shortcuts if you experience macro collision.
- */
-#ifndef UCX_NO_SSTR_SHORTCUTS
-/**
- * Shortcut for a <code>sstr_t struct</code>
- * or <code>scstr_t struct</code> literal.
- */
-#define ST(s) { s, sizeof(s)-1 }
-
-/** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
-#define S(s) sstrn(s, sizeof(s)-1)
-
-/** Shortcut for the conversion of a C string to a <code>scstr_t</code>. */
-#define SC(s) scstrn(s, sizeof(s)-1)
-#endif /* UCX_NO_SSTR_SHORTCUTS */
-
-/*
- * Use this macro to disable the format macros.
- */
-#ifndef UCX_NO_SSTR_FORMAT_MACROS
-/** Expands a sstr_t or scstr_t to printf arguments. */
-#define SFMT(s) (int) (s).length, (s).ptr
-
-/** Format specifier for a sstr_t or scstr_t. */
-#define PRIsstr ".*s"
-#endif /* UCX_NO_SSTR_FORMAT_MACROS */
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-  
-/**
- * The UCX string structure.
- */
-typedef struct {
-   /** A pointer to the string
-    * (<b>not necessarily <code>NULL</code>-terminated</b>) */
-    char *ptr;
-    /** The length of the string */
-    size_t length;
-} sstr_t;
-
-/**
- * The UCX string structure for immutable (constant) strings.
- */
-typedef struct {
-    /** A constant pointer to the immutable string
-     * (<b>not necessarily <code>NULL</code>-terminated</b>) */
-    const char *ptr;
-    /** The length of the string */
-    size_t length;
-} scstr_t;
-
-#ifdef	__cplusplus
-}
-#endif
-
-
-#ifdef __cplusplus
-/**
- * One of two type adjustment functions that return an scstr_t.
- * 
- * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
- * 
- * <b>Do not use this function manually.</b>
- * 
- * @param str some sstr_t
- * @return an immutable (scstr_t) version of the provided string.
- */
-inline scstr_t s2scstr(sstr_t s) {
-    scstr_t c;
-    c.ptr = s.ptr;
-    c.length = s.length;
-    return c;
-}
-
-/**
- * One of two type adjustment functions that return an scstr_t.
- * 
- * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
- * This variant is used, when the string is already immutable and no operation
- * needs to be performed.
- * 
- * <b>Do not use this function manually.</b>
- * 
- * @param str some scstr_t
- * @return the argument itself
- */
-inline scstr_t s2scstr(scstr_t str) {
-    return str;
-}
-
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * @param str some UCX string
- * @return an immutable version of the provided string
- */
-#define SCSTR(s) s2scstr(s)
-#else
-
-/**
- * One of two type adjustment functions that return an scstr_t.
- * 
- * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
- * This variant is used, when the string is already immutable and no operation
- * needs to be performed.
- * 
- * <b>Do not use this function manually.</b>
- * 
- * @param str some scstr_t
- * @return the argument itself
- */
-scstr_t ucx_sc2sc(scstr_t str);
-
-/**
- * One of two type adjustment functions that return an scstr_t.
- * 
- * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
- * 
- * <b>Do not use this function manually.</b>
- * 
- * @param str some sstr_t
- * @return an immutable (scstr_t) version of the provided string.
- */
-scstr_t ucx_ss2sc(sstr_t str);
-
-#if __STDC_VERSION__ >= 201112L
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * @param str some UCX string
- * @return an immutable version of the provided string
- */
-#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str)
-
-#elif defined(__GNUC__) || defined(__clang__)
-
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * @param str some UCX string
- * @return an immutable version of the provided string
- */
-#define SCSTR(str) __builtin_choose_expr( \
-        __builtin_types_compatible_p(typeof(str), sstr_t), \
-        ucx_ss2sc, \
-        ucx_sc2sc)(str)
-
-#elif defined(__sun)
-
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * @param str some UCX string
- * @return the an immutable version of the provided string
- */
-#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \
-	scstr_t ucx_tmp_var_c; \
-	ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\
-	ucx_tmp_var_c.length = ucx_tmp_var_str.length;\
-	ucx_tmp_var_c; })
-#else /* no generics and no builtins */
-
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * 
- * This <b>internal</b> function (ab)uses the C standard an expects one single
- * argument which is then implicitly converted to scstr_t without a warning.
- * 
- * <b>Do not use this function manually.</b>
- * 
- * @return the an immutable version of the provided string
- */
-scstr_t ucx_ss2c_s();
-
-/**
- * Converts a UCX string to an immutable UCX string (scstr_t).
- * @param str some UCX string
- * @return the an immutable version of the provided string
- */
-#define SCSTR(str) ucx_ss2c_s(str)
-#endif /* C11 feature test */
-
-#endif /* C++ */
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-
-/**
- * Creates a new sstr_t based on a C string.
- * 
- * The length is implicitly inferred by using a call to <code>strlen()</code>.
- *
- * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
- * If you do want a copy, use sstrdup() on the return value of this function.
- * 
- * If you need to wrap a constant string, use scstr().
- * 
- * @param cstring the C string to wrap
- * @return a new sstr_t containing the C string
- * 
- * @see sstrn()
- */
-sstr_t sstr(char *cstring);
-
-/**
- * Creates a new sstr_t of the specified length based on a C string.
- *
- * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
- * If you do want a copy, use sstrdup() on the return value of this function.
- * 
- * If you need to wrap a constant string, use scstrn().
- * 
- * @param cstring  the C string to wrap
- * @param length   the length of the string
- * @return a new sstr_t containing the C string
- * 
- * @see sstr()
- * @see S()
- */
-sstr_t sstrn(char *cstring, size_t length);
-
-/**
- * Creates a new scstr_t based on a constant C string.
- * 
- * The length is implicitly inferred by using a call to <code>strlen()</code>.
- *
- * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
- * If you do want a copy, use scstrdup() on the return value of this function.
- * 
- * @param cstring the C string to wrap
- * @return a new scstr_t containing the C string
- * 
- * @see scstrn()
- */
-scstr_t scstr(const char *cstring);
-
-
-/**
- * Creates a new scstr_t of the specified length based on a constant C string.
- *
- * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
- * If you do want a copy, use scstrdup() on the return value of this function. * 
- * 
- * @param cstring  the C string to wrap
- * @param length   the length of the string
- * @return a new scstr_t containing the C string
- * 
- * @see scstr()
- */
-scstr_t scstrn(const char *cstring, size_t length);
-
-/**
- * Returns the accumulated length of all specified strings.
- * 
- * <b>Attention:</b> if the count argument is larger than the count of the
- * specified strings, the behavior is undefined.
- *
- * @param count    the total number of specified strings
- * @param ...      all strings
- * @return the accumulated length of all strings
- */
-size_t scstrnlen(size_t count, ...);
-
-/**
- * Returns the accumulated length of all specified strings.
- * 
- * <b>Attention:</b> if the count argument is larger than the count of the
- * specified strings, the behavior is undefined.
- * 
- * @param count    the total number of specified strings
- * @param ...      all strings
- * @return the cumulated length of all strings
- */
-#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__)
-
-/**
- * Concatenates two or more strings.
- * 
- * The resulting string will be allocated by standard <code>malloc()</code>. 
- * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- *
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param ...     all remaining strings
- * @return the concatenated string
- */
-sstr_t scstrcat(size_t count, scstr_t s1, ...);
-
-/**
- * Concatenates two or more strings.
- * 
- * The resulting string will be allocated by standard <code>malloc()</code>. 
- * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- * 
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param ...     all remaining strings
- * @return the concatenated string
- */
-#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__)
-
-/**
- * Concatenates two or more strings using a UcxAllocator.
- * 
- * The resulting string must be freed by the allocators <code>free()</code>
- * implementation.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- *
- * @param alloc   the allocator to use
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param ...     all remaining strings
- * @return the concatenated string
- * 
- * @see scstrcat()
- */
-sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...);
-
-/**
- * Concatenates two or more strings using a UcxAllocator.
- * 
- * The resulting string must be freed by the allocators <code>free()</code>
- * implementation.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated.
- *
- * @param alloc   the allocator to use
- * @param count   the total number of strings to concatenate
- * @param s1      first string
- * @param ...     all remaining strings
- * @return the concatenated string
- * 
- * @see sstrcat()
- */
-#define sstrcat_a(alloc, count, s1, ...) \
-    scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__)
-
-/**
- * Returns a substring starting at the specified location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
- * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
- * Use sstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * 
- * @see sstrsubsl()
- * @see sstrchr()
- */
-sstr_t sstrsubs(sstr_t string, size_t start);
-
-/**
- * Returns a substring with the given length starting at the specified location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
- * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
- * Use sstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @param length the maximum length of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * with a maximum length of <code>length</code>
- * 
- * @see sstrsubs()
- * @see sstrchr()
- */
-sstr_t sstrsubsl(sstr_t string, size_t start, size_t length);
-
-/**
- * Returns a substring of an immutable string starting at the specified
- * location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
-* input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
- * Use scstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * 
- * @see scstrsubsl()
- * @see scstrchr()
- */
-scstr_t scstrsubs(scstr_t string, size_t start);
-
-/**
- * Returns a substring of an immutable string with a maximum length starting
- * at the specified location.
- * 
- * <b>Attention:</b> the new string references the same memory area as the
- * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
- * Use scstrdup() to get a copy.
- * 
- * @param string input string
- * @param start  start location of the substring
- * @param length the maximum length of the substring
- * @return a substring of <code>string</code> starting at <code>start</code>
- * with a maximum length of <code>length</code>
- * 
- * @see scstrsubs()
- * @see scstrchr()
- */
-scstr_t scstrsubsl(scstr_t string, size_t start, size_t length);
-
-/**
- * Returns a substring starting at the location of the first occurrence of the
- * specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the first location of <code>chr</code>
- * 
- * @see sstrsubs()
- */
-sstr_t sstrchr(sstr_t string, int chr);
-
-/**
- * Returns a substring starting at the location of the last occurrence of the
- * specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the last location of <code>chr</code>
- * 
- * @see sstrsubs()
- */
-sstr_t sstrrchr(sstr_t string, int chr);
-
-/**
- * Returns an immutable substring starting at the location of the first
- * occurrence of the specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the first location of <code>chr</code>
- * 
- * @see scstrsubs()
- */
-scstr_t scstrchr(scstr_t string, int chr);
-
-/**
- * Returns an immutable substring starting at the location of the last
- * occurrence of the specified character.
- * 
- * If the string does not contain the character, an empty string is returned.
- * 
- * @param string the string where to locate the character
- * @param chr    the character to locate
- * @return       a substring starting at the last location of <code>chr</code>
- * 
- * @see scstrsubs()
- */
-scstr_t scstrrchr(scstr_t string, int chr);
-
-/**
- * Returns a substring starting at the location of the first occurrence of the
- * specified string.
- * 
- * If the string does not contain the other string, an empty string is returned.
- * 
- * If <code>match</code> is an empty string, the complete <code>string</code> is
- * returned.
- * 
- * @param string the string to be scanned
- * @param match  string containing the sequence of characters to match
- * @return       a substring starting at the first occurrence of
- *               <code>match</code>, or an empty string, if the sequence is not
- *               present in <code>string</code>
- */
-sstr_t scstrsstr(sstr_t string, scstr_t match);
-
-/**
- * Returns a substring starting at the location of the first occurrence of the
- * specified string.
- * 
- * If the string does not contain the other string, an empty string is returned.
- * 
- * If <code>match</code> is an empty string, the complete <code>string</code> is
- * returned.
- * 
- * @param string the string to be scanned
- * @param match  string containing the sequence of characters to match
- * @return       a substring starting at the first occurrence of
- *               <code>match</code>, or an empty string, if the sequence is not
- *               present in <code>string</code>
- */
-#define sstrstr(string, match) scstrsstr(string, SCSTR(match))
-
-/**
- * Returns an immutable substring starting at the location of the
- * first occurrence of the specified immutable string.
- * 
- * If the string does not contain the other string, an empty string is returned.
- * 
- * If <code>match</code> is an empty string, the complete <code>string</code> is
- * returned.
- * 
- * @param string the string to be scanned
- * @param match  string containing the sequence of characters to match
- * @return       a substring starting at the first occurrence of
- *               <code>match</code>, or an empty string, if the sequence is not
- *               present in <code>string</code>
- */
-scstr_t scstrscstr(scstr_t string, scstr_t match);
-
-/**
- * Returns an immutable substring starting at the location of the
- * first occurrence of the specified immutable string.
- * 
- * If the string does not contain the other string, an empty string is returned.
- * 
- * If <code>match</code> is an empty string, the complete <code>string</code> is
- * returned.
- * 
- * @param string the string to be scanned
- * @param match  string containing the sequence of characters to match
- * @return       a substring starting at the first occurrence of
- *               <code>match</code>, or an empty string, if the sequence is not
- *               present in <code>string</code>
- */
-#define sstrscstr(string, match) scstrscstr(string, SCSTR(match))
-
-/**
- * Splits a string into parts by using a delimiter string.
- * 
- * This function will return <code>NULL</code>, if one of the following happens:
- * <ul>
- *   <li>the string length is zero</li>
- *   <li>the delimeter length is zero</li>
- *   <li>the string equals the delimeter</li>
- *   <li>memory allocation fails</li>
- * </ul>
- * 
- * The integer referenced by <code>count</code> is used as input and determines
- * the maximum size of the resulting array, i.e. the maximum count of splits to
- * perform + 1.
- * 
- * The integer referenced by <code>count</code> is also used as output and is
- * set to
- * <ul>
- *   <li>-2, on memory allocation errors</li>
- *   <li>-1, if either the string or the delimiter is an empty string</li>
- *   <li>0, if the string equals the delimiter</li>
- *   <li>1, if the string does not contain the delimiter</li>
- *   <li>the count of array items, otherwise</li>
- * </ul>
- * 
- * If the string starts with the delimiter, the first item of the resulting
- * array will be an empty string.
- * 
- * If the string ends with the delimiter and the maximum list size is not
- * exceeded, the last array item will be an empty string.
- * In case the list size would be exceeded, the last array item will be the
- * remaining string after the last split, <i>including</i> the terminating
- * delimiter.
- * 
- * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
- * items must be manually passed to <code>free()</code>. Use scstrsplit_a() with
- * an allocator to managed memory, to avoid this.
- *
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- * <code>NULL</code> on error
- * 
- * @see scstrsplit_a()
- */
-sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count);
-
-/**
- * Splits a string into parts by using a delimiter string.
- * 
- * This function will return <code>NULL</code>, if one of the following happens:
- * <ul>
- *   <li>the string length is zero</li>
- *   <li>the delimeter length is zero</li>
- *   <li>the string equals the delimeter</li>
- *   <li>memory allocation fails</li>
- * </ul>
- * 
- * The integer referenced by <code>count</code> is used as input and determines
- * the maximum size of the resulting array, i.e. the maximum count of splits to
- * perform + 1.
- * 
- * The integer referenced by <code>count</code> is also used as output and is
- * set to
- * <ul>
- *   <li>-2, on memory allocation errors</li>
- *   <li>-1, if either the string or the delimiter is an empty string</li>
- *   <li>0, if the string equals the delimiter</li>
- *   <li>1, if the string does not contain the delimiter</li>
- *   <li>the count of array items, otherwise</li>
- * </ul>
- * 
- * If the string starts with the delimiter, the first item of the resulting
- * array will be an empty string.
- * 
- * If the string ends with the delimiter and the maximum list size is not
- * exceeded, the last array item will be an empty string.
- * In case the list size would be exceeded, the last array item will be the
- * remaining string after the last split, <i>including</i> the terminating
- * delimiter.
- * 
- * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
- * items must be manually passed to <code>free()</code>. Use sstrsplit_a() with
- * an allocator to managed memory, to avoid this.
- *
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- * <code>NULL</code> on error
- * 
- * @see sstrsplit_a()
- */
-#define sstrsplit(string, delim, count) \
-    scstrsplit(SCSTR(string), SCSTR(delim), count)
-
-/**
- * Performing scstrsplit() using a UcxAllocator.
- * 
- * <i>Read the description of scstrsplit() for details.</i>
- * 
- * The memory for the sstr_t.ptr pointers of the array items and the memory for
- * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
- * function.
- * 
- * @param allocator the UcxAllocator used for allocating memory
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- * <code>NULL</code> on error
- * 
- * @see scstrsplit()
- */
-sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim,
-        ssize_t *count);
-
-/**
- * Performing sstrsplit() using a UcxAllocator.
- * 
- * <i>Read the description of sstrsplit() for details.</i>
- * 
- * The memory for the sstr_t.ptr pointers of the array items and the memory for
- * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
- * function.
- * 
- * @param allocator the UcxAllocator used for allocating memory
- * @param string the string to split
- * @param delim  the delimiter string
- * @param count  IN: the maximum size of the resulting array (0 = no limit),
- *               OUT: the actual size of the array
- * @return a sstr_t array containing the split strings or
- * <code>NULL</code> on error
- * 
- * @see sstrsplit()
- */
-#define sstrsplit_a(allocator, string, delim, count) \
-    scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count)
-
-/**
- * Compares two UCX strings with standard <code>memcmp()</code>.
- * 
- * At first it compares the scstr_t.length attribute of the two strings. The
- * <code>memcmp()</code> function is called, if and only if the lengths match.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the result of
- * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
- */
-int scstrcmp(scstr_t s1, scstr_t s2);
-
-/**
- * Compares two UCX strings with standard <code>memcmp()</code>.
- * 
- * At first it compares the sstr_t.length attribute of the two strings. The
- * <code>memcmp()</code> function is called, if and only if the lengths match.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the result of
- * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
- */
-#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2))
-
-/**
- * Compares two UCX strings ignoring the case.
- * 
- * At first it compares the scstr_t.length attribute of the two strings. If and
- * only if the lengths match, both strings are compared char by char ignoring
- * the case.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the result of the platform
- * specific string comparison function ignoring the case.
- */
-int scstrcasecmp(scstr_t s1, scstr_t s2);
-
-/**
- * Compares two UCX strings ignoring the case.
- * 
- * At first it compares the sstr_t.length attribute of the two strings. If and
- * only if the lengths match, both strings are compared char by char ignoring
- * the case.
- * 
- * @param s1 the first string
- * @param s2 the second string
- * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
- * length of s1 is greater than the length of s2 or the result of the platform
- * specific string comparison function ignoring the case.
- */
-#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2))
-
-/**
- * Creates a duplicate of the specified string.
- * 
- * The new sstr_t will contain a copy allocated by standard
- * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
- * <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated and mutable, regardless of the argument.
- * 
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see scstrdup_a()
- */
-sstr_t scstrdup(scstr_t string);
-
-/**
- * Creates a duplicate of the specified string.
- * 
- * The new sstr_t will contain a copy allocated by standard
- * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
- * <code>free()</code>.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated, regardless of the argument.
- * 
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see sstrdup_a()
- */
-#define sstrdup(string) scstrdup(SCSTR(string))
-
-/**
- * Creates a duplicate of the specified string using a UcxAllocator.
- * 
- * The new sstr_t will contain a copy allocated by the allocators
- * UcxAllocator.malloc() function. So it is implementation depended, whether the
- * returned sstr_t.ptr pointer must be passed to the allocators
- * UcxAllocator.free() function manually.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated and mutable, regardless of the argument.
- * 
- * @param allocator a valid instance of a UcxAllocator
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see scstrdup()
- */
-sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string);
-
-/**
- * Creates a duplicate of the specified string using a UcxAllocator.
- * 
- * The new sstr_t will contain a copy allocated by the allocators
- * UcxAllocator.malloc() function. So it is implementation depended, whether the
- * returned sstr_t.ptr pointer must be passed to the allocators
- * UcxAllocator.free() function manually.
- * 
- * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
- * terminated, regardless of the argument.
- * 
- * @param allocator a valid instance of a UcxAllocator
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see scstrdup()
- */
-#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string))
-
-
-/**
- * Omits leading and trailing spaces.
- * 
- * This function returns a new sstr_t containing a trimmed version of the
- * specified string.
- * 
- * <b>Note:</b> the new sstr_t references the same memory, thus you
- * <b>MUST NOT</b> pass the sstr_t.ptr of the return value to
- * <code>free()</code>. It is also highly recommended to avoid assignments like
- * <code>mystr = sstrtrim(mystr);</code> as you lose the reference to the
- * source string. Assignments of this type are only permitted, if the
- * sstr_t.ptr of the source string does not need to be freed or if another
- * reference to the source string exists.
- * 
- * @param string the string that shall be trimmed
- * @return a new sstr_t containing the trimmed string
- */
-sstr_t sstrtrim(sstr_t string);
-
-/**
- * Omits leading and trailing spaces.
- * 
- * This function returns a new scstr_t containing a trimmed version of the
- * specified string.
- * 
- * <b>Note:</b> the new scstr_t references the same memory, thus you
- * <b>MUST NOT</b> pass the scstr_t.ptr of the return value to
- * <code>free()</code>. It is also highly recommended to avoid assignments like
- * <code>mystr = scstrtrim(mystr);</code> as you lose the reference to the
- * source string. Assignments of this type are only permitted, if the
- * scstr_t.ptr of the source string does not need to be freed or if another
- * reference to the source string exists.
- * 
- * @param string the string that shall be trimmed
- * @return a new scstr_t containing the trimmed string
- */
-scstr_t scstrtrim(scstr_t string);
-
-/**
- * Checks, if a string has a specific prefix.
- * 
- * @param string the string to check
- * @param prefix the prefix the string should have
- * @return 1, if and only if the string has the specified prefix, 0 otherwise
- */
-int scstrprefix(scstr_t string, scstr_t prefix);
-
-/**
- * Checks, if a string has a specific prefix.
- * 
- * @param string the string to check
- * @param prefix the prefix the string should have
- * @return 1, if and only if the string has the specified prefix, 0 otherwise
- */
-#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix))
-
-/**
- * Checks, if a string has a specific suffix.
- * 
- * @param string the string to check
- * @param suffix the suffix the string should have
- * @return 1, if and only if the string has the specified suffix, 0 otherwise
- */
-int scstrsuffix(scstr_t string, scstr_t suffix);
-
-/**
- * Checks, if a string has a specific suffix.
- *
- * @param string the string to check
- * @param suffix the suffix the string should have
- * @return 1, if and only if the string has the specified suffix, 0 otherwise
- */
-#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(suffix))
-
-/**
- * Checks, if a string has a specific prefix, ignoring the case.
- * 
- * @param string the string to check
- * @param prefix the prefix the string should have
- * @return 1, if and only if the string has the specified prefix, 0 otherwise
- */
-int scstrcaseprefix(scstr_t string, scstr_t prefix);
-
-/**
- * Checks, if a string has a specific prefix, ignoring the case.
- * 
- * @param string the string to check
- * @param prefix the prefix the string should have
- * @return 1, if and only if the string has the specified prefix, 0 otherwise
- */
-#define sstrcaseprefix(string, prefix) \
-  scstrcaseprefix(SCSTR(string), SCSTR(prefix))
-
-/**
- * Checks, if a string has a specific suffix, ignoring the case.
- * 
- * @param string the string to check
- * @param suffix the suffix the string should have
- * @return 1, if and only if the string has the specified suffix, 0 otherwise
- */
-int scstrcasesuffix(scstr_t string, scstr_t suffix);
-
-/**
- * Checks, if a string has a specific suffix, ignoring the case.
- *
- * @param string the string to check
- * @param suffix the suffix the string should have
- * @return 1, if and only if the string has the specified suffix, 0 otherwise
- */
-#define sstrcasesuffix(string, suffix) \
-  scstrcasesuffix(SCSTR(string), SCSTR(suffix))
-
-/**
- * Returns a lower case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see scstrdup()).
- * 
- * @param string the input string
- * @return the resulting lower case string
- * @see scstrdup()
- */
-sstr_t scstrlower(scstr_t string);
-
-/**
- * Returns a lower case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see sstrdup()).
- * 
- * @param string the input string
- * @return the resulting lower case string
- */
-#define sstrlower(string) scstrlower(SCSTR(string))
-
-/**
- * Returns a lower case version of a string.
- * 
-  * This function creates a duplicate of the input string, first
- * (see scstrdup_a()).
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting lower case string
- * @see scstrdup_a()
- */
-sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string);
-
-
-/**
- * Returns a lower case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see sstrdup_a()).
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting lower case string
- */
-#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string))
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see scstrdup()).
- * 
- * @param string the input string
- * @return the resulting upper case string
- * @see scstrdup()
- */
-sstr_t scstrupper(scstr_t string);
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see sstrdup()).
- * 
- * @param string the input string
- * @return the resulting upper case string
- */
-#define sstrupper(string) scstrupper(SCSTR(string))
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see scstrdup_a()).
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting upper case string
- * @see scstrdup_a()
- */
-sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string);
-
-/**
- * Returns a upper case version of a string.
- * 
- * This function creates a duplicate of the input string, first
- * (see sstrdup_a()).
- * 
- * @param allocator the allocator used for duplicating the string
- * @param string the input string
- * @return the resulting upper case string
- */
-#define sstrupper_a(allocator, string) scstrupper_a(allocator, string)
-
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The resulting string is allocated by the specified allocator. I.e. it
- * depends on the used allocator, whether the sstr_t.ptr must be freed
- * manually.
- *
- * If allocation fails, the sstr_t.ptr of the return value is NULL.
- *
- * @param allocator the allocator to use
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
- */
-sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
-        scstr_t pattern, scstr_t replacement, size_t replmax);
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The sstr_t.ptr of the resulting string must be freed manually.
- *
- * If allocation fails, the sstr_t.ptr of the return value is NULL.
- *
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
- */
-sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
-        scstr_t replacement, size_t replmax);
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The resulting string is allocated by the specified allocator. I.e. it
- * depends on the used allocator, whether the sstr_t.ptr must be freed
- * manually.
- *
- * @param allocator the allocator to use
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
- */
-#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \
-        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
-            SCSTR(replacement), replmax)
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The sstr_t.ptr of the resulting string must be freed manually.
- *
- * If allocation fails, the sstr_t.ptr of the return value is NULL.
- *
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
- */
-#define sstrreplacen(str, pattern, replacement, replmax) \
-        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax)
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The resulting string is allocated by the specified allocator. I.e. it
- * depends on the used allocator, whether the sstr_t.ptr must be freed
- * manually.
- *
- * If allocation fails, the sstr_t.ptr of the return value is NULL.
- *
- * @param allocator the allocator to use
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
- */
-#define sstrreplace_a(allocator, str, pattern, replacement) \
-        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
-            SCSTR(replacement), SIZE_MAX)
-
-/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most <code>replmax</code> occurrences.
- *
- * The sstr_t.ptr of the resulting string must be freed manually.
- *
- * If allocation fails, the sstr_t.ptr of the return value is NULL.
- *
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
- */
-#define sstrreplace(str, pattern, replacement) \
-        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX)
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_STRING_H */
--- a/ucx/ucx/test.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
- 
-/**
- * @file: test.h
- * 
- * UCX Test Framework.
- * 
- * Usage of this test framework:
- *
- * **** IN HEADER FILE: ****
- *
- * <pre>
- * UCX_TEST(function_name);
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
- * </pre>
- *
- * **** IN SOURCE FILE: ****
- * <pre>
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
- *   // tests with UCX_TEST_ASSERT()
- * }
- * 
- * UCX_TEST(function_name) {
- *   // memory allocation and other stuff here
- *   #UCX_TEST_BEGIN
- *   // tests with UCX_TEST_ASSERT() and/or
- *   // calls with UCX_TEST_CALL_SUBROUTINE() here
- *   #UCX_TEST_END
- *   // cleanup of memory here
- * }
- * </pre>
- *
- * <b>Note:</b> if a test fails, a longjump is performed
- * back to the #UCX_TEST_BEGIN macro!
- * 
- * <b>Attention:</b> Do not call own functions within a test, that use
- * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE().
- * 
- *
- * @author Mike Becker
- * @author Olaf Wintermann
- *
- */
-
-#ifndef UCX_TEST_H
-#define	UCX_TEST_H
-
-#include "ucx.h"
-#include <stdio.h>
-#include <string.h>
-#include <setjmp.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-#ifndef __FUNCTION__
-
-/**
- * Alias for the <code>__func__</code> preprocessor macro.
- * Some compilers use <code>__func__</code> and others use __FUNCTION__.
- * We use __FUNCTION__ so we define it for those compilers which use
- * <code>__func__</code>.
- */
-#define __FUNCTION__ __func__
-#endif
-
-/** Type for the UcxTestSuite. */
-typedef struct UcxTestSuite UcxTestSuite;
-
-/** Pointer to a test function. */
-typedef void(*UcxTest)(UcxTestSuite*,FILE*);
-
-/** Type for the internal list of test cases. */
-typedef struct UcxTestList UcxTestList;
-
-/** Structure for the internal list of test cases. */
-struct UcxTestList {
-    
-    /** Test case. */
-    UcxTest test;
-    
-    /** Pointer to the next list element. */
-    UcxTestList *next;
-};
-
-/**
- * A test suite containing multiple test cases.
- */
-struct UcxTestSuite {
-    
-    /** The number of successful tests after the suite has been run. */
-    unsigned int success;
-    
-    /** The number of failed tests after the suite has been run. */
-    unsigned int failure;
-    
-    /**
-     * Internal list of test cases.
-     * Use ucx_test_register() to add tests to this list.
-     */
-    UcxTestList *tests;
-};
-
-/**
- * Creates a new test suite.
- * @return a new test suite
- */
-UcxTestSuite* ucx_test_suite_new();
-
-/**
- * Destroys a test suite.
- * @param suite the test suite to destroy
- */
-void ucx_test_suite_free(UcxTestSuite* suite);
-
-/**
- * Registers a test function with the specified test suite.
- * 
- * @param suite the suite, the test function shall be added to
- * @param test the test function to register
- * @return <code>EXIT_SUCCESS</code> on success or
- * <code>EXIT_FAILURE</code> on failure
- */
-int ucx_test_register(UcxTestSuite* suite, UcxTest test);
-
-/**
- * Runs a test suite and writes the test log to the specified stream.
- * @param suite the test suite to run
- * @param outstream the stream the log shall be written to
- */
-void ucx_test_run(UcxTestSuite* suite, FILE* outstream);
-
-/**
- * Macro for a #UcxTest function header.
- * 
- * Use this macro to declare and/or define a #UcxTest function.
- * 
- * @param name the name of the test function
- */
-#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_)
-
-/**
- * Marks the begin of a test.
- * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b>
- * #UCX_TEST_BEGIN.
- * 
- * @see #UCX_TEST_END
- */
-#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\
-        fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
-        fwrite("... ", 1, 4, _output_);\
-        jmp_buf _env_; \
-        if (!setjmp(_env_)) {
-
-/**
- * Checks a test assertion.
- * If the assertion is correct, the test carries on. If the assertion is not
- * correct, the specified message (terminated by a dot and a line break) is
- * written to the test suites output stream.
- * @param condition the condition to check
- * @param message the message that shall be printed out on failure
- */
-#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \
-        fwrite(message".\n", 1, 2+strlen(message), _output_); \
-        _suite_->failure++; \
-        longjmp(_env_, 1);\
-    }
-
-/**
- * Macro for a test subroutine function header.
- * 
- * Use this to declare and/or define a subroutine that can be called by using
- * UCX_TEST_CALL_SUBROUTINE().
- * 
- * @param name the name of the subroutine
- * @param ... the parameter list
- * 
- * @see UCX_TEST_CALL_SUBROUTINE()
- */
-#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\
-        FILE *_output_, jmp_buf _env_, __VA_ARGS__)
-
-/**
- * Macro for calling a test subroutine.
- * 
- * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this
- * macro.
- * 
- * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN-
- * #UCX_TEST_END-block.
- * 
- * @param name the name of the subroutine
- * @param ... the argument list
- * 
- * @see UCX_TEST_SUBROUTINE()
- */
-#define UCX_TEST_CALL_SUBROUTINE(name,...) \
-        name(_suite_,_output_,_env_,__VA_ARGS__);
-
-/**
- * Marks the end of a test.
- * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b>
- * #UCX_TEST_END.
- * 
- * @see #UCX_TEST_BEGIN
- */
-#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;}
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_TEST_H */
-
--- a/ucx/ucx/ucx.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,204 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-/**
- * Main UCX Header providing most common definitions.
- * 
- * @file   ucx.h
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_H
-#define	UCX_H
-
-/** Major UCX version as integer constant. */
-#define UCX_VERSION_MAJOR   2
-
-/** Minor UCX version as integer constant. */
-#define UCX_VERSION_MINOR   1
-
-/** Version constant which ensures to increase monotonically. */
-#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
-
-#include <stdlib.h>
-#include <stdint.h>
-
-#ifdef _WIN32
-#if !(defined __ssize_t_defined || defined _SSIZE_T_)
-#include <BaseTsd.h>
-typedef SSIZE_T ssize_t;
-#define __ssize_t_defined
-#define _SSIZE_T_
-#endif /* __ssize_t_defined and _SSIZE_T */
-#else /* !_WIN32 */
-#include <sys/types.h>
-#endif /* _WIN32 */
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-    
-
-/**
- * A function pointer to a destructor function.
- * @see ucx_mempool_setdestr()
- * @see ucx_mempool_regdestr()
- */
-typedef void(*ucx_destructor)(void*);
-
-/**
- * Function pointer to a compare function.
- * 
- * The compare function shall take three arguments: the two values that shall be
- * compared and optional additional data.
- * The function shall then return -1 if the first argument is less than the
- * second argument, 1 if the first argument is greater than the second argument
- * and 0 if both arguments are equal. If the third argument is
- * <code>NULL</code>, it shall be ignored.
- */
-typedef int(*cmp_func)(const void*,const void*,void*);
-
-/**
- * Function pointer to a distance function.
- * 
- * The distance function shall take three arguments: the two values for which
- * the distance shall be computed and optional additional data.
- * The function shall then return the signed distance as integer value.
- */
-typedef intmax_t(*distance_func)(const void*,const void*,void*);
-
-/**
- * Function pointer to a copy function.
- * 
- * The copy function shall create a copy of the first argument and may use
- * additional data provided by the second argument. If the second argument is
- * <code>NULL</code>, it shall be ignored.
-
- * <b>Attention:</b> if pointers returned by functions of this type may be
- * passed to <code>free()</code> depends on the implementation of the
- * respective <code>copy_func</code>.
- */
-typedef void*(*copy_func)(const void*,void*);
-
-/**
- * Function pointer to a write function.
- * 
- * The signature of the write function shall be compatible to the signature
- * of standard <code>fwrite</code>, though it may use arbitrary data types for
- * source and destination.
- * 
- * The arguments shall contain (in ascending order): a pointer to the source,
- * the length of one element, the element count and a pointer to the
- * destination.
- */
-typedef size_t(*write_func)(const void*, size_t, size_t, void*);
-
-/**
- * Function pointer to a read function.
- * 
- * The signature of the read function shall be compatible to the signature
- * of standard <code>fread</code>, though it may use arbitrary data types for
- * source and destination.
- * 
- * The arguments shall contain (in ascending order): a pointer to the
- * destination, the length of one element, the element count and a pointer to
- * the source.
- */
-typedef size_t(*read_func)(void*, size_t, size_t, void*);
-
-
-
-#if __GNUC__ >= 5 || defined(__clang__)
-#define UCX_MUL_BUILTIN
-
-#if __WORDSIZE == 32
-/**
- * Alias for <code>__builtin_umul_overflow</code>.
- * 
- * Performs a multiplication of size_t values and checks for overflow.
- * 
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result)
-#else /* __WORDSIZE != 32 */
-/**
- * Alias for <code>__builtin_umull_overflow</code>.
- * 
- * Performs a multiplication of size_t values and checks for overflow.
- * 
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result)
-#endif /* __WORDSIZE */
-
-#else /* no GNUC or clang bultin */
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
-  *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result)
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * This is a custom implementation in case there is no compiler builtin
- * available.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t where the result should be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-int ucx_szmul_impl(size_t a, size_t b, size_t *result);
-
-#endif
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* UCX_H */
-
--- a/ucx/ucx/utils.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,508 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2017 Mike Becker, 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.
- */
-
-/**
- * @file utils.h
- * 
- * Compare, copy and printf functions.
- * 
- * @author Mike Becker
- * @author Olaf Wintermann
- */
-
-#ifndef UCX_UTILS_H
-#define UCX_UTILS_H
-
-#include "ucx.h"
-#include "string.h"
-#include "allocator.h"
-#include <inttypes.h>
-#include <string.h>
-#include <stdarg.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy().
- */
-#define UCX_STREAM_COPY_BUFSIZE 4096
-
-/**
- * Copies a string.
- * @param s the string to copy
- * @param data omitted
- * @return a pointer to a copy of s1 that can be passed to free(void*)
- */
-void *ucx_strcpy(const void *s, void *data);
-
-/**
- * Copies a memory area.
- * @param m a pointer to the memory area
- * @param n a pointer to the size_t containing the size of the memory area
- * @return a pointer to a copy of the specified memory area that can
- * be passed to free(void*)
- */
-void *ucx_memcpy(const void *m, void *n);
-
-
-/**
- * Reads data from a stream and writes it to another stream.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if <code>NULL</code> was
- * provided for <code>buf</code>, this is the size of the buffer that shall be
- * implicitly created
- * @param n the maximum number of bytes that shall be copied
- * @return the total number of bytes copied
-  */
-size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc,
-        char* buf, size_t bufsize, size_t n);
-
-/**
- * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @return total number of bytes copied
- * 
- * @see #UCX_STREAM_COPY_BUFSIZE
- */
-#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1)
-
-/**
- * Shorthand for ucx_stream_bncopy using a default copy buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param n maximum number of bytes that shall be copied
- * @return total number of bytes copied
- */
-#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        NULL, UCX_STREAM_COPY_BUFSIZE, n)
-
-/**
- * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer.
- * 
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if <code>NULL</code> was
- * provided for <code>buf</code>, this is the size of the buffer that shall be
- * implicitly created
- * @return total number of bytes copied
- */
-#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\
-        src, dest, (read_func)rfnc, (write_func)wfnc, \
-        buf, bufsize, (size_t)-1)
-
-/**
- * Wraps the strcmp function.
- * @param s1 string one
- * @param s2 string two
- * @param data omitted
- * @return the result of strcmp(s1, s2)
- */
-int ucx_cmp_str(const void *s1, const void *s2, void *data);
-
-/**
- * Wraps the strncmp function.
- * @param s1 string one
- * @param s2 string two
- * @param n a pointer to the size_t containing the third strncmp parameter
- * @return the result of strncmp(s1, s2, *n)
- */
-int ucx_cmp_strn(const void *s1, const void *s2, void *n);
-
-/**
- * Wraps the sstrcmp function.
- * @param s1 sstr one
- * @param s2 sstr two
- * @param data ignored
- * @return the result of sstrcmp(s1, s2)
- */
-int ucx_cmp_sstr(const void *s1, const void *s2, void *data);
-
-/**
- * Compares two integers of type int.
- * @param i1 pointer to integer one
- * @param i2 pointer to integer two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_int(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type long int.
- * @param i1 pointer to long integer one
- * @param i2 pointer to long integer two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_longint(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type long long.
- * @param i1 pointer to long long one
- * @param i2 pointer to long long two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_longlong(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type int16_t.
- * @param i1 pointer to int16_t one
- * @param i2 pointer to int16_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_int16(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type int32_t.
- * @param i1 pointer to int32_t one
- * @param i2 pointer to int32_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_int32(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type int64_t.
- * @param i1 pointer to int64_t one
- * @param i2 pointer to int64_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_int64(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type unsigned int.
- * @param i1 pointer to unsigned integer one
- * @param i2 pointer to unsigned integer two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_uint(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type unsigned long int.
- * @param i1 pointer to unsigned long integer one
- * @param i2 pointer to unsigned long integer two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_ulongint(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type unsigned long long.
- * @param i1 pointer to unsigned long long one
- * @param i2 pointer to unsigned long long two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type uint16_t.
- * @param i1 pointer to uint16_t one
- * @param i2 pointer to uint16_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_uint16(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type uint32_t.
- * @param i1 pointer to uint32_t one
- * @param i2 pointer to uint32_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_uint32(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two integers of type uint64_t.
- * @param i1 pointer to uint64_t one
- * @param i2 pointer to uint64_t two
- * @param data omitted
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
- */
-int ucx_cmp_uint64(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type int.
- * @param i1 pointer to integer one
- * @param i2 pointer to integer two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_int(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type long int.
- * @param i1 pointer to long integer one
- * @param i2 pointer to long integer two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type long long.
- * @param i1 pointer to long long one
- * @param i2 pointer to long long two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type int16_t.
- * @param i1 pointer to int16_t one
- * @param i2 pointer to int16_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type int32_t.
- * @param i1 pointer to int32_t one
- * @param i2 pointer to int32_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type int64_t.
- * @param i1 pointer to int64_t one
- * @param i2 pointer to int64_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type unsigned int.
- * @param i1 pointer to unsigned integer one
- * @param i2 pointer to unsigned integer two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type unsigned long int.
- * @param i1 pointer to unsigned long integer one
- * @param i2 pointer to unsigned long integer two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type unsigned long long.
- * @param i1 pointer to unsigned long long one
- * @param i2 pointer to unsigned long long two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type uint16_t.
- * @param i1 pointer to uint16_t one
- * @param i2 pointer to uint16_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type uint32_t.
- * @param i1 pointer to uint32_t one
- * @param i2 pointer to uint32_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data);
-
-/**
- * Distance function for integers of type uint64_t.
- * @param i1 pointer to uint64_t one
- * @param i2 pointer to uint64_t two
- * @param data omitted
- * @return i1 minus i2
- */
-intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data);
-
-/**
- * Compares two real numbers of type float.
- * @param f1 pointer to float one
- * @param f2 pointer to float two
- * @param data if provided: a pointer to precision (default: 1e-6f)
- * @return -1, if *f1 is less than *f2, 0 if both are equal,
- * 1 if *f1 is greater than *f2
- */
-
-int ucx_cmp_float(const void *f1, const void *f2, void *data);
-
-/**
- * Compares two real numbers of type double.
- * @param d1 pointer to double one
- * @param d2 pointer to double two
- * @param data if provided: a pointer to precision (default: 1e-14)
- * @return -1, if *d1 is less than *d2, 0 if both are equal,
- * 1 if *d1 is greater than *d2
- */
-int ucx_cmp_double(const void *d1, const void *d2, void *data);
-
-/**
- * Compares two pointers.
- * @param ptr1 pointer one
- * @param ptr2 pointer two
- * @param data omitted
- * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
- * 1 if ptr1 is greater than ptr2
- */
-int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data);
-
-/**
- * Compares two memory areas.
- * @param ptr1 pointer one
- * @param ptr2 pointer two
- * @param n a pointer to the size_t containing the third parameter for memcmp
- * @return the result of memcmp(ptr1, ptr2, *n)
- */
-int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n);
-
-/**
- * A <code>printf()</code> like function which writes the output to a stream by
- * using a write_func().
- * @param stream the stream the data is written to
- * @param wfc the write function
- * @param fmt format string
- * @param ... additional arguments
- * @return the total number of bytes written
- */
-int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...);
-
-/**
- * <code>va_list</code> version of ucx_fprintf().
- * @param stream the stream the data is written to
- * @param wfc the write function
- * @param fmt format string
- * @param ap argument list
- * @return the total number of bytes written
- * @see ucx_fprintf()
- */
-int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap);
-
-/**
- * A <code>printf()</code> like function which allocates space for a sstr_t
- * the result is written to.
- * 
- * <b>Attention</b>: The sstr_t data is allocated with the allocators
- * ucx_allocator_malloc() function. So it is implementation dependent, if
- * the returned sstr_t.ptr pointer must be passed to the allocators
- * ucx_allocator_free() function manually.
- * 
- * <b>Note</b>: The sstr_t.ptr of the return value will <i>always</i> be
- * <code>NULL</code>-terminated.
- * 
- * @param allocator the UcxAllocator used for allocating the result sstr_t
- * @param fmt format string
- * @param ... additional arguments
- * @return a sstr_t containing the formatted string
- */
-sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...);
-
-/**
- * <code>va_list</code> version of ucx_asprintf().
- * 
- * @param allocator the UcxAllocator used for allocating the result sstr_t
- * @param fmt format string
- * @param ap argument list
- * @return a sstr_t containing the formatted string
- * @see ucx_asprintf()
- */
-sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap);
-
-/** Shortcut for ucx_asprintf() with default allocator. */
-#define ucx_sprintf(...) \
-    ucx_asprintf(ucx_default_allocator(), __VA_ARGS__)
-
-/**
- * A <code>printf()</code> like function which writes the output to a
- * UcxBuffer.
- * 
- * @param buffer the buffer the data is written to
- * @param ... format string and additional arguments
- * @return the total number of bytes written
- * @see ucx_fprintf()
- */
-#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \
-        (write_func)ucx_buffer_write, __VA_ARGS__)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* UCX_UTILS_H */
-
--- a/ucx/utils.c	Sun May 23 09:44:43 2021 +0200
+++ b/ucx/utils.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2021 Mike Becker, 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:
@@ -26,423 +26,74 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "ucx/utils.h"
+#include "cx/utils.h"
 
-#include <math.h>
-#include <stdio.h>
-#include <limits.h>
-#include <errno.h>
+#ifndef CX_STREAM_BCOPY_BUF_SIZE
+#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#endif
+
+#ifndef CX_STREAM_COPY_BUF_SIZE
+#define CX_STREAM_COPY_BUF_SIZE 1024
+#endif
 
-/* COPY FUCNTIONS */
-void* ucx_strcpy(const void* s, void* data) {
-    const char *str = (const char*) s;
-    size_t n = 1+strlen(str);
-    char *cpy = (char*) malloc(n);
-    memcpy(cpy, str, n);
-    return cpy;
-}
-
-void* ucx_memcpy(const void* m, void* n) {
-    size_t k = *((size_t*)n);
-    void *cpy = malloc(k);
-    memcpy(cpy, m, k);
-    return cpy;
-}
-
-size_t ucx_stream_bncopy(void *src, void *dest, read_func readfnc,
-        write_func writefnc, char* buf, size_t bufsize, size_t n) {
-    if(n == 0 || bufsize == 0) {
+size_t cx_stream_bncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        char *buf,
+        size_t bufsize,
+        size_t n
+) {
+    if (n == 0) {
         return 0;
     }
-    
-    char *lbuf;    
+
+    char *lbuf;
     size_t ncp = 0;
-    
-    if(buf) {
+
+    if (buf) {
+        if (bufsize == 0) return 0;
         lbuf = buf;
     } else {
-        lbuf = (char*)malloc(bufsize);
-        if(lbuf == NULL) {
+        if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
+        lbuf = malloc(bufsize);
+        if (lbuf == NULL) {
             return 0;
         }
     }
-    
+
     size_t r;
     size_t rn = bufsize > n ? n : bufsize;
-    while((r = readfnc(lbuf, 1, rn, src)) != 0) {
-        r = writefnc(lbuf, 1, r, dest);
+    while ((r = rfnc(lbuf, 1, rn, src)) != 0) {
+        r = wfnc(lbuf, 1, r, dest);
         ncp += r;
         n -= r;
         rn = bufsize > n ? n : bufsize;
-        if(r == 0 || n == 0) {
+        if (r == 0 || n == 0) {
             break;
         }
     }
-    
+
     if (lbuf != buf) {
         free(lbuf);
     }
-    
+
     return ncp;
 }
 
-/* COMPARE FUNCTIONS */
-
-int ucx_cmp_str(const void *s1, const void *s2, void *data) {
-    return strcmp((const char*)s1, (const char*)s2);
-}
-
-int ucx_cmp_strn(const void *s1, const void *s2, void *n) {
-    return strncmp((const char*)s1, (const char*)s2, *((size_t*) n));
-}
-
-int ucx_cmp_sstr(const void *s1, const void *s2, void *data) {
-    sstr_t a = *(const sstr_t*) s1;
-    sstr_t b = *(const sstr_t*) s2;
-    return sstrcmp(a, b);
-}
-
-int ucx_cmp_int(const void *i1, const void *i2, void *data) {
-   int a = *((const int*) i1);
-   int b = *((const int*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_longint(const void *i1, const void *i2, void *data) {
-   long int a = *((const long int*) i1);
-   long int b = *((const long int*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_longlong(const void *i1, const void *i2, void *data) {
-   long long a = *((const long long*) i1);
-   long long b = *((const long long*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_int16(const void *i1, const void *i2, void *data) {
-   int16_t a = *((const int16_t*) i1);
-   int16_t b = *((const int16_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_int32(const void *i1, const void *i2, void *data) {
-   int32_t a = *((const int32_t*) i1);
-   int32_t b = *((const int32_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_int64(const void *i1, const void *i2, void *data) {
-   int64_t a = *((const int64_t*) i1);
-   int64_t b = *((const int64_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_uint(const void *i1, const void *i2, void *data) {
-   unsigned int a = *((const unsigned int*) i1);
-   unsigned int b = *((const unsigned int*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) {
-   unsigned long int a = *((const unsigned long int*) i1);
-   unsigned long int b = *((const unsigned long int*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) {
-   unsigned long long a = *((const unsigned long long*) i1);
-   unsigned long long b = *((const unsigned long long*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_uint16(const void *i1, const void *i2, void *data) {
-   uint16_t a = *((const uint16_t*) i1);
-   uint16_t b = *((const uint16_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_uint32(const void *i1, const void *i2, void *data) {
-   uint32_t a = *((const uint32_t*) i1);
-   uint32_t b = *((const uint32_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_uint64(const void *i1, const void *i2, void *data) {
-   uint64_t a = *((const uint64_t*) i1);
-   uint64_t b = *((const uint64_t*) i2);
-   if (a == b) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const int*) i1);
-   intmax_t b = *((const int*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const long int*) i1);
-   intmax_t b = *((const long int*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const long long*) i1);
-   intmax_t b = *((const long long*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const int16_t*) i1);
-   intmax_t b = *((const int16_t*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const int32_t*) i1);
-   intmax_t b = *((const int32_t*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) {
-   intmax_t a = *((const int64_t*) i1);
-   intmax_t b = *((const int64_t*) i2);
-   return a - b;
-}
-
-intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const unsigned int*) i1);
-   uintmax_t b = *((const unsigned int*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+size_t cx_stream_ncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        size_t n
+) {
+    char buf[CX_STREAM_COPY_BUF_SIZE];
+    return cx_stream_bncopy(src, dest, rfnc, wfnc,
+                            buf, CX_STREAM_COPY_BUF_SIZE, n);
 }
 
-intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const unsigned long int*) i1);
-   uintmax_t b = *((const unsigned long int*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
-}
-
-intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const unsigned long long*) i1);
-   uintmax_t b = *((const unsigned long long*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
-}
-
-intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const uint16_t*) i1);
-   uintmax_t b = *((const uint16_t*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
-}
-
-intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const uint32_t*) i1);
-   uintmax_t b = *((const uint32_t*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
-}
-
-intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) {
-   uintmax_t a = *((const uint64_t*) i1);
-   uintmax_t b = *((const uint64_t*) i2);
-   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
-}
-
-int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) {
-   float a = *((const float*) f1);
-   float b = *((const float*) f2);
-   float e = !epsilon ? 1e-6f : *((float*)epsilon);
-   if (fabsf(a - b) < e) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) {
-   double a = *((const double*) d1);
-   double b = *((const double*) d2);
-   double e = !epsilon ? 1e-14 : *((double*)epsilon);
-   if (fabs(a - b) < e) {
-       return 0;
-   } else {
-       return a < b ? -1 : 1;
-   }
-}
-
-int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) {
-    const intptr_t p1 = (const intptr_t) ptr1;
-    const intptr_t p2 = (const intptr_t) ptr2;
-    if (p1 == p2) {
-        return 0;
-    } else {
-        return p1  < p2 ? -1 : 1;
-    }
-}
-
-int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) {
-    return memcmp(ptr1, ptr2, *((size_t*)n));
-}
-
-/* PRINTF FUNCTIONS */
-
-#ifdef va_copy
-#define UCX_PRINTF_BUFSIZE 256
-#else
-#pragma message("WARNING: C99 va_copy macro not supported by this platform" \
-                " - limiting ucx_*printf to 2 KiB")
-#define UCX_PRINTF_BUFSIZE 0x800
+#ifndef CX_SZMUL_BUILTIN
+#include "szmul.c"
 #endif
-
-int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...) {
-    int ret;
-    va_list ap;
-    va_start(ap, fmt);
-    ret = ucx_vfprintf(stream, wfc, fmt, ap);
-    va_end(ap);
-    return ret;
-}
-
-int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap) {
-    char buf[UCX_PRINTF_BUFSIZE];
-#ifdef va_copy
-    va_list ap2;
-    va_copy(ap2, ap);
-    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
-    if (ret < 0) {
-        return ret;
-    } else if (ret < UCX_PRINTF_BUFSIZE) {
-        return (int)wfc(buf, 1, ret, stream);
-    } else {
-        if (ret == INT_MAX) {
-            errno = ENOMEM;
-            return -1;
-        }
-        
-        int len = ret + 1;
-        char *newbuf = (char*)malloc(len);
-        if (!newbuf) {
-            return -1;
-        }
-        
-        ret = vsnprintf(newbuf, len, fmt, ap2);
-        if (ret > 0) {
-            ret = (int)wfc(newbuf, 1, ret, stream);
-        }
-        free(newbuf);
-    }
-    return ret;
-#else
-    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
-    if (ret < 0) {
-        return ret;
-    } else if (ret < UCX_PRINTF_BUFSIZE) {
-        return (int)wfc(buf, 1, ret, stream);
-    } else {
-        errno = ENOMEM;
-        return -1;
-    }
-#endif
-}
-
-sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...) {
-    va_list ap;
-    sstr_t ret;
-    va_start(ap, fmt);
-    ret = ucx_vasprintf(allocator, fmt, ap);
-    va_end(ap);
-    return ret;
-}
-
-sstr_t ucx_vasprintf(UcxAllocator *a, const char *fmt, va_list ap) {
-    sstr_t s;
-    s.ptr = NULL;
-    s.length = 0;
-    char buf[UCX_PRINTF_BUFSIZE];
-#ifdef va_copy
-    va_list ap2;
-    va_copy(ap2, ap);
-    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
-    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
-        s.ptr = (char*)almalloc(a, ret + 1);
-        if (s.ptr) {
-            s.length = (size_t)ret;
-            memcpy(s.ptr, buf, ret);
-            s.ptr[s.length] = '\0';
-        }
-    } else if (ret == INT_MAX) {
-        errno = ENOMEM;
-    } else  {
-        int len = ret + 1;
-        s.ptr = (char*)almalloc(a, len);
-        if (s.ptr) {
-            ret = vsnprintf(s.ptr, len, fmt, ap2);
-            if (ret < 0) {
-                free(s.ptr);
-                s.ptr = NULL;
-            } else {
-                s.length = (size_t)ret;
-            }
-        }
-    }
-#else
-    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
-    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
-        s.ptr = (char*)almalloc(a, ret + 1);
-        if (s.ptr) {
-            s.length = (size_t)ret;
-            memcpy(s.ptr, buf, ret);
-            s.ptr[s.length] = '\0';
-        }
-    } else {
-        errno = ENOMEM;
-    }
-#endif
-    return s;
-}
--- a/ui/Makefile	Sun May 23 09:44:43 2021 +0200
+++ b/ui/Makefile	Sat Jan 04 16:38:48 2025 +0100
@@ -33,7 +33,7 @@
 
 include common/objs.mk
 
-UI_LIB = ../build/lib/libuitk.$(LIB_EXT)
+UI_LIB = ../build/lib/libuitk$(LIB_EXT)
 
 include $(TOOLKIT)/objs.mk
 OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
@@ -43,5 +43,5 @@
 include $(TOOLKIT)/Makefile
 
 $(COMMON_OBJPRE)%.o: common/%.c
-	$(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+	$(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $<
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/EventData.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#import "../ui/toolkit.h"
+#import "../common/context.h"
+
+@interface EventData : NSObject
+@property UiObject    *obj;
+@property ui_callback callback;
+@property void        *userdata;
+@property void        *data;
+@property int         value;
+
+- (EventData*)init:(ui_callback)cb userdata:(void*)userdata;
+
+- (void)handleEvent:(id)sender;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/EventData.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#import "EventData.h"
+
+@implementation EventData
+
+- (EventData*)init:(ui_callback)cb userdata:(void*)userdata {
+    _callback = cb;
+    _userdata = userdata;
+    return self;
+}
+
+- (void)handleEvent:(id)sender {
+    if(self.callback) {
+        UiEvent event;
+        event.obj = self.obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = self.data;
+        event.intval = self.value;
+        self.callback(&event, self.userdata);
+    }
+}
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/GridLayout.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+
+#import "Container.h"
+
+#import <cx/array_list.h>
+
+typedef struct GridElm {
+    NSView *view;
+    int margin;
+    int x;
+    int y;
+    int colspan;
+    int rowspan;
+    BOOL hexpand;
+    BOOL vexpand;
+    BOOL hfill;
+    BOOL vfill;
+} GridElm;
+
+typedef struct GridDef {
+    int size;
+    int pos;
+    int preferred_size;
+    BOOL extend;
+} GridDef;
+
+@interface GridLayout : NSView<Container>
+
+@property int columnspacing;
+@property int rowspacing;
+@property CxList *children;
+@property NSSize preferredSize;
+
+@property NSButton *test;
+
+@property int x;
+@property int y;
+@property int cols;
+@property int rows;
+
+- (GridLayout*)init;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/GridLayout.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+#import "GridLayout.h"
+
+
+
+@implementation GridLayout
+
+@synthesize label=_label;
+@synthesize uilayout=_uilayout;
+@synthesize newline=_newline;
+
+- (GridLayout*)init {
+    self = [super init];
+    _columnspacing = 0;
+    _rowspacing = 0;
+    _children = cxArrayListCreateSimple(sizeof(GridElm), 32);
+    
+    return self;
+}
+
+/*
+- (void) layout {
+    [super layout];
+    
+    NSRect r1 = _test.frame;
+    NSSize s1 = _test.intrinsicContentSize;
+    NSEdgeInsets e1 = _test.alignmentRectInsets;
+    
+    printf("fuck\n");
+}
+ */
+
+
+- (void) layout {
+    int ncols = _cols+1;
+    int nrows = _rows+1;
+    
+    GridDef *coldef = calloc(ncols, sizeof(GridDef));
+    GridDef *rowdef = calloc(nrows, sizeof(GridDef));
+    
+    NSRect viewFrame = self.frame;
+    
+    int colspacing = _columnspacing;
+    int rowspacing = _rowspacing;
+    
+    CxIterator i = cxListIterator(_children);
+    cx_foreach(GridElm *, elm, i) {
+        NSSize size = elm->view.intrinsicContentSize;
+        NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+        if(size.width != NSViewNoIntrinsicMetric) {
+            CGFloat width = size.width + alignment.left + alignment.right;
+            if(width > coldef[elm->x].preferred_size) {
+                coldef[elm->x].preferred_size = width;
+            }
+        }
+        if(size.height != NSViewNoIntrinsicMetric) {
+            CGFloat height = size.height + alignment.top + alignment.right;
+            //CGFloat height = size.height;
+            if(height > rowdef[elm->y].preferred_size) {
+                rowdef[elm->y].preferred_size = height;
+            }
+        }
+        
+        if(elm->hexpand) {
+            coldef[elm->x].extend = TRUE;
+        }
+        if(elm->vexpand) {
+            rowdef[elm->y].extend = TRUE;
+        }
+    }
+    
+    int col_ext = 0;
+    int row_ext = 0;
+    
+    int preferred_width = 0;
+    int preferred_height = 0;
+    for(int j=0;j<ncols;j++) {
+        preferred_width += coldef[j].preferred_size + colspacing;
+        if(coldef[j].extend) {
+            col_ext++;
+        }
+    }
+    for(int j=0;j<nrows;j++) {
+        preferred_height += rowdef[j].preferred_size + rowspacing;
+        if(rowdef[j].extend) {
+            row_ext++;
+        }
+    }
+    
+    _preferredSize.width = preferred_width;
+    _preferredSize.height = preferred_height;
+    
+    
+    int hremaining = viewFrame.size.width - preferred_width;
+    int vremaining = viewFrame.size.height - preferred_height;
+    int hext = hremaining/col_ext;
+    int vext = vremaining/row_ext;
+    
+    for(int j=0;j<ncols;j++) {
+        GridDef *col = &coldef[j];
+        if(col->extend) {
+            col->size = col->preferred_size + hext;
+        } else {
+            col->size = col->preferred_size;
+        }
+    }
+    for(int j=0;j<nrows;j++) {
+        GridDef *row = &rowdef[j];
+        if(row->extend) {
+            row->size = row->preferred_size + vext;
+        } else {
+            row->size = row->preferred_size;
+        }
+    }
+    
+    int pos = 0;
+    for(int j=0;j<ncols;j++) {
+        coldef[j].pos = pos;
+        pos += coldef[j].size + colspacing;
+    }
+    pos = 0;
+    for(int j=0;j<nrows;j++) {
+        rowdef[j].pos = pos;
+        pos += rowdef[j].size + rowspacing;
+    }
+    
+    i = cxListIterator(_children);
+    cx_foreach(GridElm *, elm, i) {
+        //NSSize size = elm->view.intrinsicContentSize;
+        GridDef *col = &coldef[elm->x];
+        GridDef *row = &rowdef[elm->y];
+        
+        NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+        NSRect frame;
+        frame.size.width = col->size;
+        frame.size.height = row->size;
+        frame.origin.x = col->pos - (alignment.left+alignment.right)/2;
+        frame.origin.y = viewFrame.size.height - row->pos - frame.size.height + ((alignment.top+alignment.right)/2);
+        elm->view.frame = frame;
+    }
+    
+    free(coldef);
+    free(rowdef);
+}
+ 
+
+- (NSSize)intrinsicContentSize {
+    return self.preferredSize;
+}
+
+- (void) addView:(NSView*)view fill:(BOOL)fill {
+    if(_newline) {
+        _y++;
+        _x = 0;
+        _newline = FALSE;
+    }
+    
+    GridElm elm;
+    elm.x = _x;
+    elm.y = _y;
+    elm.margin = 0;
+    elm.colspan = _uilayout.colspan;
+    elm.rowspan = _uilayout.rowspan;
+    elm.hfill = _uilayout.hfill;
+    elm.vfill = _uilayout.vfill;
+    elm.hexpand = _uilayout.hexpand;
+    elm.vexpand = _uilayout.vexpand;
+    elm.view = view;
+    cxListAdd(_children, &elm);
+    
+    [self addSubview:view];
+    self.needsLayout = YES;
+    
+    if(_x > _cols) {
+        _cols = _x;
+    }
+    if(_y > _rows) {
+        _rows = _y;
+    }
+    _x++;
+}
+
+- (void) dealloc {
+    cxListDestroy(_children);
+}
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/MainWindow.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+#import "../ui/window.h"
+
+@interface MainWindow : NSWindow
+
+@property UiObject *uiobj;
+
+- (MainWindow*)init:(UiObject*)obj;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/MainWindow.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#import "MainWindow.h"
+#import "Container.h"
+#import "GridLayout.h"
+#import "../common/object.h"
+
+@implementation MainWindow
+
+- (MainWindow*)init:(UiObject*)obj {
+    self.uiobj = obj;
+    NSRect frame = NSMakeRect(300, 200, 600, 500);
+    
+    self = [self initWithContentRect:frame
+                           styleMask:NSWindowStyleMaskTitled |
+            NSWindowStyleMaskResizable |
+            NSWindowStyleMaskClosable |
+            NSWindowStyleMaskMiniaturizable
+                             backing:NSBackingStoreBuffered
+                               defer:false];
+    
+    // create a vertical stackview as default container
+    //BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
+    GridLayout *vbox = [[GridLayout alloc] init];
+    vbox.translatesAutoresizingMaskIntoConstraints = false;
+    [self.contentView addSubview:vbox];
+    [NSLayoutConstraint activateConstraints:@[
+        [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4],
+        [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
+        [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
+        [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
+    ]];
+    
+    uic_object_push_container(obj, ui_create_container(obj, vbox));
+    
+    return self;
+}
+
+@end
--- a/ui/cocoa/Makefile	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/Makefile	Sat Jan 04 16:38:48 2025 +0100
@@ -27,7 +27,7 @@
 #
 
 $(COCOA_OBJPRE)%.o: cocoa/%.m
-	$(CC) -o $@ -c $(CFLAGS) $<
+	$(CC) -o $@ -c -I../ucx -fobjc-arc $(CFLAGS) $(TK_CFLAGS) $<
 
 $(UI_LIB): $(OBJ)
 	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiJob.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiJob.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#import "UiJob.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/WindowManager.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+
+@interface WindowManager : NSObject<NSWindowDelegate>
+
+@property NSMutableArray<NSWindow*> *windows;
+
++ (WindowManager*) sharedWindowManager;
+
+- (WindowManager*)init;
+
+- (void)addWindow:(NSWindow*)win;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/WindowManager.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#import "WindowManager.h"
+
+@implementation WindowManager
+
+static WindowManager *instance = nil;
+
++ (WindowManager*) sharedWindowManager {
+    if(instance == nil) {
+        instance = [[WindowManager alloc] init];
+    }
+    return instance;
+}
+
+- (WindowManager*)init {
+    _windows = [[NSMutableArray alloc] initWithCapacity:16];
+    return self;
+}
+
+- (void)addWindow:(NSWindow*)win {
+    [_windows addObject:win];
+    [win setDelegate:self];
+}
+
+- (void) windowWillClose:(NSNotification *) notification {
+    NSWindow *window = notification.object;
+    [_windows removeObject:window];
+}
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/appdelegate.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,37 @@
+/*
+ * 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 "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/appdelegate.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#import "AppDelegate.h"
+
+#import "toolkit.h"
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+    ui_cocoa_onstartup();
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+    ui_cocoa_onexit();
+}
+
+
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
+    return YES;
+}
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/button.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+
+#import "../ui/button.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/button.m	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#import "button.h"
+#import "EventData.h"
+#import "Container.h"
+#import <objc/runtime.h>
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+    NSButton *button = [[NSButton alloc] init];
+    if(args.label) {
+        NSString *label = [[NSString alloc] initWithUTF8String:args.label];
+        button.title = label;
+    }
+    
+    if(args.onclick) {
+        EventData *event = [[EventData alloc] init:args.onclick userdata:args.onclickdata];
+        event.obj = obj;
+        button.target = event;
+        button.action = @selector(handleEvent:);
+        objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN);
+    }
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, button, &layout, FALSE);
+    
+    return (__bridge void*)button;
+}
--- a/ui/cocoa/container.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/container.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,19 +26,65 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "../ui/toolkit.h"
 #import "toolkit.h"
 
-typedef void(*ui_container_add_f)(UiContainer*, NSView*);
+#import "../ui/container.h"
+
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
 
-struct UiContainer {
-    NSView* widget;
-    void   (*add)(UiContainer*, NSView*);
-    NSRect (*getframe)(UiContainer*);
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiTri        fill;
+    //UiBool       newline;
+    //char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    //int          width;
+    int          colspan;
+    int          rowspan;
 };
 
-UiContainer* ui_window_container(UiObject *obj, NSWindow *window);
+#define UI_INIT_LAYOUT(args) (UiLayout) {\
+    .fill = args.fill, \
+    .hexpand = args.hexpand, \
+    .vexpand = args.vexpand, \
+    .hfill = args.hfill, \
+    .vfill = args.vfill, \
+    .colspan = args.colspan, \
+    .rowspan = args.rowspan }
+
+
+@protocol Container
+
+@property UiLayout uilayout;
+@property const char *label;
+@property UiBool newline;
 
-NSRect ui_container_getframe(UiContainer *ct);
-void ui_container_add(UiContainer *ct, NSView *view);
+- (void) addView:(NSView*)view fill:(BOOL)fill;
+
+@end
+
+@interface BoxContainer : NSStackView<Container>
+
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing;
 
+@end
+
+
+
+
+
+UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
+
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill);
--- a/ui/cocoa/container.m	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/container.m	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,81 +26,143 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <stdio.h>
-#import <stdlib.h>
+#import "Container.h"
+#import "GridLayout.h"
+
+/* ------------------------- container classes ------------------------- */
 
-#import "container.h"
+@implementation BoxContainer
 
+@synthesize label=_label;
+@synthesize uilayout=_uilayout;
+@synthesize newline=_newline;
 
-UiContainer* ui_window_container(UiObject *obj, NSWindow *window) {
-    UiContainer *ct = ucx_mempool_malloc(
-                                         obj->ctx->mempool,
-                                         sizeof(UiContainer));
-    ct->widget = [window contentView];
-    ct->add = ui_container_add;
-    ct->getframe = ui_container_getframe;
-    return ct;
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing {
+    self = [super init];
+    _label = NULL;
+    _uilayout = (UiLayout){ 0 };
+    _newline = false;
+    
+    self.distribution = NSStackViewDistributionFillProportionally;
+    self.spacing = spacing;
+    
+    self.orientation = orientation;
+    if(orientation == NSUserInterfaceLayoutOrientationHorizontal) {
+        self.alignment = NSLayoutAttributeHeight;
+    } else {
+        self.alignment = NSLayoutAttributeWidth;
+    }
+    
+    
+    return self;
 }
 
-UIWIDGET ui_sidebar(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    NSRect frame = ct->getframe(ct);
+- (void) addView:(NSView*)view fill:(BOOL)fill {
+    if(_uilayout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(_uilayout.fill);
+    }
     
-    // create and add views
-    NSSplitView *splitview = [[NSSplitView alloc] initWithFrame:frame];
-    [splitview setVertical:YES];
-    [splitview setDividerStyle:NSSplitViewDividerStyleThin];
-    ct->add(ct, splitview);
+    [self addArrangedSubview:view];
     
-    NSRect lframe;
-    lframe.origin.x = 0;
-    lframe.origin.y = 0;
-    lframe.size.width = 200;
-    lframe.size.height = frame.size.height;
-    
-    NSRect rframe;
-    rframe.origin.x = 0;
-    rframe.origin.y = 0;
-    rframe.size.width = frame.size.width - 201;
-    rframe.size.height = frame.size.height;
-    
-    NSView *sidebar = [[NSView alloc]initWithFrame:lframe];
-    NSView *contentarea = [[NSView alloc]initWithFrame:rframe];
+    if(self.orientation == NSUserInterfaceLayoutOrientationHorizontal) {
+        [view.heightAnchor constraintEqualToAnchor:self.heightAnchor].active = YES;
+        if(!fill) {
+            [view.widthAnchor constraintEqualToConstant:view.intrinsicContentSize.width].active = YES;
+        }
+    } else {
+        [view.widthAnchor constraintEqualToAnchor:self.widthAnchor].active = YES;
+        if(!fill) {
+            [view.heightAnchor constraintEqualToConstant:view.intrinsicContentSize.height].active = YES;
+        }
+    }
     
-    [splitview addSubview:sidebar];
-    [splitview addSubview:contentarea];
+    // at the moment, only the fill layout option needs to be reset
+    _uilayout.fill = UI_DEFAULT;
+}
+
+@end
+
+
+
+/* -------------------- public container functions --------------------- */
+
+static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, NSUserInterfaceLayoutOrientation orientation) {
+    BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args.spacing];
+    box.translatesAutoresizingMaskIntoConstraints = false;
     
-    // add ui objects for the sidebar and contentarea
-    // the sidebar is added last, so that new views are added first to it
-    UiObject *left = uic_object_new(obj, sidebar);
-    UiContainer *ct1 = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiContainer));
-    ct1->widget = sidebar;
-    ct1->add = ui_container_add;
-    ct1->getframe = ui_container_getframe;
-    left->container = ct1;
+    // add box to the parent
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, box, &layout, TRUE);
+    
+    // add new box to the obj container chain
+    uic_object_push_container(obj, ui_create_container(obj, box));
     
-    UiObject *right = uic_object_new(obj, sidebar);
-    UiContainer *ct2 = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiContainer));
-    ct2->widget = contentarea;
-    ct2->add = ui_container_add;
-    ct2->getframe = ui_container_getframe;
-    right->container = ct2;
+    return (__bridge void*)box;
+}
+
+UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationVertical);
+}
+
+UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationHorizontal);
+}
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    GridLayout *grid = [[GridLayout alloc] init];
+    grid.translatesAutoresizingMaskIntoConstraints = false;
     
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
+    // add box to the parent
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, grid, &layout, TRUE);
     
-    return splitview;
+    // add new box to the obj container chain
+    uic_object_push_container(obj, ui_create_container(obj, grid));
+    
+    return (__bridge void*)grid;
 }
 
 
-NSRect ui_container_getframe(UiContainer *ct) {
-    return [ct->widget frame];
+void ui_container_begin_close(UiObject *obj) {
+    UiContainerX *ct = obj->container_end;
+    ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+    UiContainerX *ct = obj->container_end;
+    if(ct->close) {
+        ui_end_new(obj);
+        return 0;
+    }
+    return 1;
 }
 
-void ui_container_add(UiContainer *ct, NSView *view) {
-    [ct->widget addSubview: view];
+/* ------------------------- private functions ------------------------- */
+
+UiContainerX* ui_create_container(UiObject *obj, id<Container> container) {
+    UiContainerX *ctn = ui_malloc(obj->ctx, sizeof(UiContainerX));
+    ctn->container = (__bridge void*)container;
+    ctn->close = 0;
+    ctn->prev = NULL;
+    ctn->next = NULL;
+    return ctn;
 }
+
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill) {
+    UiContainerX *ctn = obj->container_end;
+    id<Container> container = (__bridge id<Container>)ctn->container;
+    container.uilayout = *layout;
+    [container addView:view fill:fill];
+}
+
+/* ---------------------- public layout functions ----------------------- */
+
+void ui_newline(UiObject *obj) {
+    UiContainerX *ctn = obj->container_end;
+    if(ctn) {
+        id<Container> container = (__bridge id<Container>)ctn->container;
+        container.newline = TRUE;
+    } else {
+        fprintf(stderr, "Error: obj has no container\n");
+    }
+}
--- a/ui/cocoa/graphics.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import "../ui/graphics.h"
-#import "toolkit.h"
-
-
-@interface UiCanvas : NSView {
-    UiObject            *object;
-    ui_drawfunc         callback;
-    void                *userdata;
-}
-
-- (UiObject*) object;
-- (void) setObject:(void*)obj;
-
-- (void*) userdata;
-- (void) setUserdata:(void*)d;
-
-- (ui_drawfunc) callback;
-- (void) setCallback: (ui_drawfunc)f;
-
-
-- (void)drawRect:(NSRect)rect;
-
-@end
-
--- a/ui/cocoa/graphics.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-
-#import "graphics.h"
-#import "container.h"
-#import "../common/context.h"
-
-
-
-@implementation UiCanvas
-
-- (UiObject*) object {
-    return object;
-}
-
-- (void) setObject:(void*)obj {
-    object = obj;
-}
-
-- (void*) userdata {
-    return userdata;
-}
-
-- (void) setUserdata:(void*)d {
-    userdata = d;
-}
-
-- (ui_drawfunc) callback {
-    return callback;
-}
-- (void) setCallback: (ui_drawfunc)f {
-    callback = f;
-}
-
-- (void) drawRect:(NSRect)rect {
-    UiGraphics g;
-    NSRect bounds = [self bounds];
-    g.width = bounds.size.width;
-    g.height = bounds.size.height;
-    
-    UiEvent ev;
-    ev.obj = object;
-    ev.window = object->window;
-    ev.document = object->ctx->document;
-    
-    callback(&ev, &g, userdata);
-}
-
-@end
-
-
-UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    NSRect frame = ct->getframe(ct);
-    
-    UiCanvas *canvas = [[UiCanvas alloc]initWithFrame:frame];
-    [canvas setObject: obj];
-    [canvas setCallback: f];
-    [canvas setUserdata: userdata];
-    ct->add(ct, canvas);
-    
-    return canvas;
-}
-
-
-// drawing functions
-void ui_graphics_color(UiGraphics *gr, int red, int green, int blue) {
-    float r = ((float)red) / 255.f;
-    float g = ((float)green) / 255.f;
-    float b = ((float)blue) / 255.f;
-    
-    NSColor *color = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1];
-    [color set];
-}
-
-void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
-    // translate y
-    y = g->height - y - h;
-    
-    NSRect bounds;
-    bounds.origin.x = x;
-    bounds.origin.y = y;
-    bounds.size.width = w;
-    bounds.size.height = h;
-    
-    if(fill) {
-        NSRectFill(bounds);
-    } else {
-        NSFrameRect(bounds);
-    }
-}
-
-
-
--- a/ui/cocoa/menu.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import "../ui/menu.h"
-#import "toolkit.h"
-#import <ucx/list.h>
-
-typedef struct UiAbstractMenuItem {
-    int  (*update)(id window, void *item);
-    void *item_data;
-} UiAbstractMenuItem;
-
-typedef struct UiMenuItem {
-    NSMenuItem  *item;
-    int         state;
-} UiMenuItem;
-
-typedef struct UiStateItem {
-    NSMenuItem  *item;
-    char        *var;
-} UiStateItem;
-
-typedef struct UiMenuItemList {
-    NSMenu      *menu;
-    NSMenuItem  *first;
-    UiList      *list;
-    int         index;
-    int         oldcount;
-    ui_callback callback;
-    void        *data;
-} UiMenuItemList;
-
-@interface UiMenuDelegate : NSObject <NSMenuDelegate> {
-    UcxList *items; // UiStateItem*
-    UcxList *itemlists; // UiMenuItemList*
-}
-
-- (void) menuNeedsUpdate:(NSMenu*) menu;
-
-- (void) addItem:(NSMenuItem*) item var: (char*)name;
-
-- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data;
-
-- (UcxList*) items;
-
-- (UcxList*) lists;
-
-@end
-
-@interface UiGroupMenuItem : NSMenuItem {
-    NSMutableArray *groups;
-}
-
-- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s;
-
-- (void) addGroup:(int)group;
-
-- (void) checkGroups:(int*)g count:(int)n;
-
-@end
-
-void ui_menu_init();
-UiMenuDelegate* ui_menu_delegate();
-
-int ui_menuitem_get(UiInteger *i);
-void ui_menuitem_set(UiInteger *i, int value);
-
-int ui_update_item(id window, void *data);
-int ui_update_item_list(id window, void *data);
--- a/ui/cocoa/menu.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,319 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-#import <stdarg.h>
-
-#import "menu.h"
-#import "window.h"
-#import "stock.h"
-
-@implementation UiMenuDelegate 
-
-- (UiMenuDelegate*) init {
-    items = NULL;
-    itemlists = NULL;
-    return self;
-}
-
-- (void) menuNeedsUpdate:(NSMenu *) menu {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    [(UiCocoaWindow*)activeWindow updateMenu: menu];
-}
-
-- (void) addItem:(NSMenuItem*) item var: (char*)name {
-    UiStateItem *i = malloc(sizeof(UiStateItem));
-    i->item = item;
-    i->var = name;
-    items = ucx_list_append(items, i);
-}
-
-- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data {
-    UiMenuItemList *itemList = malloc(sizeof(UiMenuItemList));
-    itemList->list = list;
-    itemList->menu = menu;
-    itemList->first = NULL;
-    itemList->index = i;
-    itemList->oldcount = 0;
-    itemList->callback = f;
-    itemList->data = data;
-    itemlists = ucx_list_append(itemlists, itemList);
-}
-
-- (UcxList*) items {
-    return items;
-}
-
-- (UcxList*) lists {
-    return itemlists;
-    
-}
-
-@end
-
-
-@implementation UiGroupMenuItem
-
-- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s {
-    [super initWithTitle:title action:action keyEquivalent:s];
-    groups = [[NSMutableArray alloc]initWithCapacity: 8];
-    return self;
-}
-
-- (void) addGroup:(int)group {
-    NSNumber *groupNumber = [NSNumber numberWithInteger:group];
-    [groups addObject:groupNumber];
-}
-
-- (void) checkGroups:(int*)g count:(int)n {
-    int c = [groups count];
-    
-    char *check = calloc(1, c);
-    for(int i=0;i<n;i++) {
-        for(int k=0;k<c;k++) {
-            NSNumber *groupNumber = [groups objectAtIndex:k];
-            if([groupNumber intValue] == g[i]) {
-                check[k] = 1;
-                break;
-            }
-        }
-    }
-    
-    for(int j=0;j<c;j++) {
-        if(check[j] == 0) {
-            [self setEnabled:NO];
-            return;
-        }
-    }
-    [self setEnabled:YES];
-}
-
-@end
-
-
-//static NSMenu *currentMenu = NULL;
-
-static UcxList *current;
-
-static int currentItemIndex = 0;
-static UiMenuDelegate *delegate;
-
-void ui_menu_init() {
-    delegate = [[UiMenuDelegate alloc]init];
-}
-
-UiMenuDelegate* ui_menu_delegate() {
-    return delegate;
-}
-
-
-void ui_menu(char *title) {
-    NSString *str = [[NSString alloc] initWithUTF8String:title];
-    
-    NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
-    NSMenuItem *menuItem = [[NSApp mainMenu] addItemWithTitle:str
-                                                       action:nil keyEquivalent:@""];
-    [menu setDelegate: delegate];
-    [menu setAutoenablesItems:NO];
-    
-    [[NSApp mainMenu] setSubmenu:menu forItem:menuItem];
-    //currentMenu = menu;
-    currentItemIndex = 0;
-    
-    current = ucx_list_prepend(NULL, menu);
-}
-
-void ui_submenu(char *title) {
-    NSString *str = [[NSString alloc] initWithUTF8String:title];
-    NSMenu *currentMenu = current->data;
-    
-    NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
-    NSMenuItem *menuItem = [currentMenu addItemWithTitle:str
-                                                       action:nil keyEquivalent:@""];
-    [menu setDelegate: delegate];
-    [menu setAutoenablesItems:NO];
-    
-    [currentMenu setSubmenu:menu forItem:menuItem];
-    //currentMenu = menu;
-    currentItemIndex = 0;
-    
-    current = ucx_list_prepend(current, menu);
-}
-
-void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
-        return;
-    }
-    current = ucx_list_remove(current, current);
-}
-
-void ui_menuitem(char *label, ui_callback f, void *data) {
-    ui_menuitem_gr(label, f, data, -1);
-}
-
-void ui_menuitem_st(char *stockid, ui_callback f, void *data) {
-    ui_menuitem_stgr(stockid, f, data, -1);
-}
-
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
-    // create menu item
-    EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
-    NSString *title = [[NSString alloc] initWithUTF8String:label];
-    UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        [item addGroup: group];
-    }
-    va_end(ap);
-    
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem:item];
-    
-    currentItemIndex++;
-}
-
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
-    // create menu item
-    EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
-    UiStockItem *si = ui_get_stock_item(stockid);
-    UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:si->label
-                                action:@selector(handleEvent:)
-                                keyEquivalent:si->keyEquivalent];
-    [item setTarget:event];
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        [item addGroup: group];
-    }
-    va_end(ap);
-    
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem:item];
-    
-    currentItemIndex++;
-}
-
-void ui_checkitem(char *label, ui_callback f, void *data) {
-    EventWrapper *event = [[EventWrapper alloc]initWithData:data callback:f];
-    NSString *str = [[NSString alloc] initWithUTF8String:label];
-    
-    NSMenu *currentMenu = current->data;
-    NSMenuItem *item = [currentMenu addItemWithTitle:str
-                                              action:@selector(handleStateEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    [delegate addItem: item var:NULL];
-    currentItemIndex++;
-}
-
-void ui_checkitem_nv(char *label, char *vname) {
-    EventWrapper *event = [[EventWrapper alloc]initWithData:NULL callback:NULL];
-    NSString *str = [[NSString alloc] initWithUTF8String:label];
-    
-    NSMenu *currentMenu = current->data;
-    NSMenuItem *item = [currentMenu addItemWithTitle:str
-                                              action:@selector(handleStateEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    [delegate addItem: item var:vname];
-    currentItemIndex++;
-}
-
-void ui_menuseparator() {
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem: [NSMenuItem separatorItem]];
-    currentItemIndex++;
-}
-
-void ui_menuitem_list (UiList *items, ui_callback f, void *data) {
-    NSMenu *currentMenu = current->data;
-    [delegate addList:items menu:currentMenu index:currentItemIndex callback:f data:data];
-}
-
-
-
-int ui_menuitem_get(UiInteger *i) {
-    UiMenuItem *item = i->obj;
-    i->value = [item->item state];
-    return i->value;
-}
-
-void ui_menuitem_set(UiInteger *i, int value) {
-    UiMenuItem *item = i->obj;
-    [item->item setState: value];
-    i->value = value;
-    item->state = value;
-}
-
-
-int ui_update_item(UiCocoaWindow *window, void *data) {
-    UiMenuItem *item = data;
-    [item->item setState: item->state];
-    return 0;
-}
-
-int ui_update_item_list(UiCocoaWindow *window, void *data) {
-    UiMenuItemList *itemList = data;
-    UiList *list = itemList->list;
-    
-    for(int r=0;r<itemList->oldcount;r++) {
-        [itemList->menu removeItemAtIndex:itemList->index];
-    }
-    
-    char *str = ui_list_first(list);
-    int i = itemList->index;
-    [itemList->menu insertItem: [NSMenuItem separatorItem] atIndex: i];
-    i++;
-    while(str) {
-        EventWrapper *event = [[EventWrapper alloc]initWithData:itemList->data callback:itemList->callback];
-        [event setIntval: i - itemList->index - 1];
-        
-        NSString *title = [[NSString alloc] initWithUTF8String:str];
-        NSMenuItem *item = [[NSMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
-        [item setTarget:event];
-        
-        [itemList->menu insertItem:item atIndex:i];
-        
-        str = ui_list_next(list);
-        i++;
-    }
-    
-    itemList->oldcount = i - itemList->index;
-    
-    return 0;
-}
--- a/ui/cocoa/objs.mk	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/objs.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -30,16 +30,15 @@
 COCOA_OBJPRE = $(OBJ_DIR)/$(COCOA_SRC_DIR)
 
 COCOAOBJ = toolkit.o
+COCOAOBJ += AppDelegate.o
+COCOAOBJ += GridLayout.o
+COCOAOBJ += EventData.o
+COCOAOBJ += UiJob.o
+COCOAOBJ += MainWindow.o
+COCOAOBJ += WindowManager.o
 COCOAOBJ += window.o
-COCOAOBJ += menu.o
-COCOAOBJ += stock.o
-COCOAOBJ += toolbar.o
-COCOAOBJ += container.o
-COCOAOBJ += text.o
-COCOAOBJ += resource.o
-COCOAOBJ += tree.o
-COCOAOBJ += graphics.o
-
+COCOAOBJ += Container.o
+COCOAOBJ += button.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/resource.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
-
-#import "../ui/toolkit.h"
-#import "../ui/properties.h"
-
-
--- a/ui/cocoa/resource.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-
-#import "resource.h"
-#import "../common/properties.h"
-
-
-
-void ui_load_lang_def(char *locale, char *default_locale) {
-    NSString *localeString = nil;
-    char tmp[6];
-    if(!locale) {
-        NSString* lang = [[NSLocale currentLocale] localeIdentifier];
-        if(lang) {
-            localeString = lang;
-        } else {
-            [[NSString alloc]initWithUTF8String:default_locale];
-        }
-    } else {
-        localeString = [[NSString alloc]initWithUTF8String:locale];
-    }
-    
-    NSString *path = [[NSBundle mainBundle] pathForResource:localeString ofType:@"properties" inDirectory:@"locales"];
-    
-    const char *p = [path UTF8String];
-    
-    if(uic_load_language_file((char*)p)) {
-        if(default_locale) {
-            ui_load_lang_def(default_locale, NULL);
-        } else {
-            // cannot find any language file
-            fprintf(stderr, "Ui Error: Cannot load language.\n");
-            exit(-1);
-        }
-    }
-}
-
-void ui_locales_dir(char *path) {
-    // empty
-}
-
-void ui_pixmaps_dir(char *path) {
-    // empty
-}
\ No newline at end of file
--- a/ui/cocoa/stock.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
-
-#import "toolkit.h"
-#import "../ui/stock.h"
-#import <ucx/map.h>
-
-typedef struct UiStockItem {
-    NSString *label;
-    NSString *keyEquivalent;
-    NSImage  *image;
-} UiStockItem;
-
-void ui_stock_init();
-
-void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image);
-
-UiStockItem* ui_get_stock_item(char *stock_id);
--- a/ui/cocoa/stock.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-
-#import "stock.h"
-#import "../common/properties.h"
-
-static UcxMap *stock_items;
-
-void ui_stock_init() {
-    stock_items = ucx_map_new(64);
-    
-    ui_add_stock_item(UI_STOCK_NEW, @"New", @"n", nil);
-    ui_add_stock_item(UI_STOCK_OPEN, @"Open", @"o", nil);
-    ui_add_stock_item(UI_STOCK_SAVE, @"Save", @"s", nil);
-    ui_add_stock_item(UI_STOCK_SAVE_AS, @"Save as ...", @"", nil);
-    ui_add_stock_item(UI_STOCK_CLOSE, @"Close", @"w", nil);
-    ui_add_stock_item(UI_STOCK_UNDO, @"Undo", @"z", nil);
-    ui_add_stock_item(UI_STOCK_REDO, @"Redo", @"", nil);
-    ui_add_stock_item(UI_STOCK_CUT, @"Cut", @"x", nil);
-    ui_add_stock_item(UI_STOCK_COPY, @"Copy", @"c", nil);
-    ui_add_stock_item(UI_STOCK_PASTE, @"Paste", @"v", nil);
-    ui_add_stock_item(UI_STOCK_DELETE, @"Delete", @"", nil);
-    
-    ui_add_stock_item(UI_STOCK_GO_BACK, @"Back", @"", [NSImage imageNamed: NSImageNameGoLeftTemplate]);
-    ui_add_stock_item(UI_STOCK_GO_FORWARD, @"Forward", @"", [NSImage imageNamed: NSImageNameGoRightTemplate]);
-}
-
-void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image) {
-    UiStockItem *i = malloc(sizeof(UiStockItem));
-    i->label = label;
-    i->keyEquivalent = keyEquivalent;
-    i->image = image;
-    
-    ucx_map_cstr_put(stock_items, stock_id, i);
-}
-
-UiStockItem* ui_get_stock_item(char *stock_id) {
-    UiStockItem *item = ucx_map_cstr_get(stock_items, stock_id);
-    if(item) {
-        char *label = uistr_n(stock_id);
-        if(label) {
-            NSString *str = [[NSString alloc]initWithUTF8String:label];
-            item->label = str;
-        }
-    }
-    return item;
-}
--- a/ui/cocoa/text.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import "../ui/text.h"
-#import "toolkit.h"
-#import <ucx/list.h>
-
-@interface TextChangeMgr : NSObject<NSTextViewDelegate> {
-    UiContext *context;
-    UiText    *value;
-    int       last_length;
-}
-
-- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx;
-
-- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview;
-
-@end
-
-#define UI_TEXTBUF_INSERT 0
-#define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp {
-    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
-    int  start;
-    int  end;
-    int  len;
-    char *text;
-} UiTextBufOp;
-
-
-
-char* ui_textarea_get(UiText *text);
-void ui_textarea_set(UiText *text, char *str);
-char* ui_textarea_getsubstr(UiText *text, int begin, int end);
-void ui_textarea_insert(UiText *text, int pos, char *str);
-int  ui_textarea_position(UiText *text);
-void ui_textarea_selection(UiText *text, int *begin, int *end);
-int ui_textarea_length(UiText *text);
--- a/ui/cocoa/text.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-
-#import "text.h"
-#import "container.h"
-
-@implementation TextChangeMgr
-
-- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx {
-    value = text;
-    context = ctx;
-    last_length = 0;
-    return self;
-}
-
-- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview {
-    return (NSUndoManager*)value->undomgr;
-}
-
-- (NSRange)textView:(NSTextView *)textview
-       willChangeSelectionFromCharacterRange:(NSRange)oldrange
-       toCharacterRange:(NSRange)newrange
-{
-    if(newrange.length != last_length) {
-        if(newrange.length == 0) {
-            ui_unset_group(context, UI_GROUP_SELECTION);
-        } else {
-            ui_set_group(context, UI_GROUP_SELECTION);
-        }
-    }
-    
-    last_length = newrange.length;
-    return newrange;
-}
-
-@end
-
-
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    NSRect frame = ct->getframe(ct);
-    
-    NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
-    [scrollview setHasVerticalScroller:YES];
-    //[scrollvew setHasHorizontalScroller:YES];
-    [scrollview setBorderType:NSNoBorder];
-    //[scrollview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-    
-    //frame.size.width = frame.size.width - 15;
-    NSTextView *textview = [[NSTextView alloc]initWithFrame:frame];
-    [textview setAllowsUndo:TRUE];
-    [textview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-    
-    [textview setFont:[NSFont fontWithName:@"Menlo" size:12]];
-    
-    [scrollview setDocumentView:textview];
-    
-    ct->add(ct, scrollview);
-    
-    // bind value
-    if(value) {
-        value->get = ui_textarea_get;
-        value->set = ui_textarea_set;
-        value->getsubstr = ui_textarea_getsubstr;
-        value->insert = ui_textarea_insert;
-        value->position = ui_textarea_position;
-        value->selection = ui_textarea_selection;
-        value->length = ui_textarea_length;
-        value->value = NULL;
-        value->obj = textview;
-        
-        TextChangeMgr *delegate = [[TextChangeMgr alloc]initWithValue:value context:obj->ctx];
-        [textview setDelegate:delegate];
-        
-        NSUndoManager *undomgr = [[NSUndoManager alloc]init];
-        value->undomgr = undomgr;
-    }
-    
-    return textview;
-}
-
-char* ui_textarea_get(UiText *text) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *str = [[textview textStorage]string];
-    size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-    const char *cstr = [str UTF8String];
-    char *value = malloc(length + 1);
-    memcpy(value, cstr, length);
-    value[length] = '\0';
-    text->value = value;
-    return value;
-}
-
-void ui_textarea_set(UiText *text, char *str) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *s = [[NSString alloc]initWithUTF8String:str];
-    NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
-    [[textview textStorage] setAttributedString:as];
-    text->value = NULL;
-}
-
-char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *str = [[textview textStorage]string];
-    NSRange range;
-    range.location = begin;
-    range.length = end - begin;
-    
-    NSString *substr = [str substringWithRange:range];
-    size_t length = [substr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-    const char *cstr = [substr UTF8String];
-    char *value = malloc(length + 1);
-    memcpy(value, cstr, length);
-    value[length] = '\0';
-    text->value = value;
-    return value;
-}
-
-void ui_textarea_insert(UiText *text, int pos, char *str) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *s = [[NSString alloc]initWithUTF8String:str];
-    NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
-    [[textview textStorage] insertAttributedString:as atIndex: pos];
-    text->value = NULL;
-}
-
-int ui_textarea_position(UiText *text) {
-    return [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue].location;
-}
-
-void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    NSRange range = [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue];
-    *begin = range.location;
-    *end = range.location + range.length;
-}
-
-int ui_textarea_length(UiText *text) {
-    return [[(NSTextView*)text->obj textStorage] length];
-}
-
-void ui_text_undo(UiText *text) {
-    [(NSUndoManager*)text->undomgr undo];
-}
-
-void ui_text_redo(UiText *text) {
-    [(NSUndoManager*)text->undomgr redo];
-}
-
--- a/ui/cocoa/toolbar.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import "../ui/toolbar.h"
-#import "toolkit.h"
-#import <stdarg.h>
-
-
-@protocol UiToolItem
-- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
-                    identifier:(NSString*)identifier
-                        object:(UiObject*)obj;
-
-- (void) addGroup:(int)group;
-
-- (UcxList*) groups;
-
-@end
-
-
-/*
- * UiToolbarStockItem
- *
- * creates a toolbar item from stock description
- */
-@interface UiToolbarStockItem : NSObject <UiToolItem> {
-    char           *name;
-    char           *stockid;
-    ui_callback    callback;
-    void           *userdata;
-    UcxList        *groups;
-    BOOL           isToggleButton;
-}
-
-- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
-                             stockID:(char*)sid
-                            callback:(ui_callback)f
-                            userdata:(void*)data;
-
-- (void) setIsToggleButton:(BOOL)t;
-
-
-@end
-
-/*
- * UiToolbarItem
- *
- * toolbar item with label and icon
- */
-@interface UiToolbarItem : NSObject <UiToolItem> {
-    char           *name;
-    char           *label;
-    // icon
-    ui_callback    callback;
-    void           *userdata;
-    UcxList        *groups;
-    BOOL           isToggleButton;
-}
-
-- (UiToolbarItem*) initWithIdentifier:(char*)identifier
-                                     label:(char*)lbl
-                                  callback:(ui_callback)f
-                                  userdata:(void*)data;
-
-- (void) setIsToggleButton:(BOOL)t;
-
-
-@end
-
-
-
-/*
- * UiToolbarDelegate
- */
-@interface UiToolbarDelegate : NSObject <NSToolbarDelegate> {
-    NSMutableArray      *allowedItems;
-    NSMutableArray      *defaultItems;
-    NSMutableDictionary *items;
-}
-
-- (UiToolbarDelegate*) init;
-
-- (void) addDefault:(NSString*)identifier;
-
-- (void) addItem: (NSString*) identifier
-            item: (NSObject<UiToolItem>*) item;
-
-@end
-
-
-/*
- * UiToolbar
- */
-@interface UiToolbar : NSToolbar {
-    UiObject *obj;
-}
-
-- (UiToolbar*) initWithObject:(UiObject*)object;
-
-- (UiObject*) object;
-
-@end
-
-void ui_toolbar_init();
-void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap);
-NSToolbar* ui_create_toolbar(UiObject *obj);
--- a/ui/cocoa/toolbar.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,358 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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.
- */
-
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-#import <inttypes.h>
-#import <stdarg.h>
-
-#import "toolbar.h"
-#import "window.h"
-#import "stock.h"
-
-
-static UiToolbarDelegate* toolbar_delegate;
-
-/* ---------------------      UiToolbarStockItem     --------------------- */
-
-@implementation UiToolbarStockItem
-
-- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
-                                   stockID:(char*)sid
-                                  callback:(ui_callback)f
-                                  userdata:(void*)data
-{
-    name = identifier;
-    stockid = sid;
-    callback = f;
-    userdata = data;
-    groups = NULL;
-    isToggleButton = NO;
-    return self;
-}
-
-- (void) setIsToggleButton:(BOOL)t {
-    isToggleButton = t;
-}
-
-- (void) addGroup:(int)group {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-
-- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
-                    identifier:(NSString*)identifier
-                        object:(UiObject*)obj
-{
-    UiStockItem *s = ui_get_stock_item(stockid);
-    if(s == nil) {
-        printf("cannot find stock item\n");
-        return nil;
-    }
-    
-    NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
-                            identifier] autorelease];
-    //[item setLabel:[s label]];
-    //[item setPaletteLabel:[s label]];
-    [item setLabel:s->label];
-    [item setPaletteLabel:@"Operation"];
-    
-    // create button ...
-    NSRect frame = NSMakeRect(0, 0, 40, 22);
-    //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
-    NSButton *button = [[NSButton alloc]initWithFrame:frame];
-    //[button setImage:[s buttonImage]];
-    //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
-    if(s->image) {
-        [button setImage:s->image];
-    } else {
-        [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
-    }
-    [button setBezelStyle: NSTexturedRoundedBezelStyle];
-    
-    // event
-    EventWrapper *event = [[EventWrapper alloc]
-                           initWithData:userdata callback:callback];
-    if(isToggleButton) {
-        [button setButtonType: NSPushOnPushOffButton];
-        [button setAction:@selector(handleToggleEvent:)];
-    } else {
-        [button setAction:@selector(handleEvent:)];
-    }
-    [button setTarget:event];
-    
-    if(groups) {
-        uic_add_group_widget(obj->ctx, item, groups);
-    }
-    
-    [item setView:button];
-    return item;
-}
-
-- (UcxList*) groups {
-    return groups;
-}
-
-@end
-
-
-/* ---------------------      UiToolbarItem     --------------------- */
-
-@implementation UiToolbarItem
-
-- (UiToolbarItem*) initWithIdentifier:(char*)identifier
-                                     label:(char*)lbl
-                                  callback:(ui_callback)f
-                                  userdata:(void*)data
-{
-    name = identifier;
-    label = lbl;
-    callback = f;
-    userdata = data;
-    groups = NULL;
-    isToggleButton = NO;
-    return self;
-}
-
-- (void) setIsToggleButton:(BOOL)t {
-    isToggleButton = t;
-}
-
-- (void) addGroup:(int)group {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-
-- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
-                    identifier:(NSString*)identifier
-                        object:(UiObject*)obj
-{
-    NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
-                            identifier] autorelease];
-    //[item setLabel:[s label]];
-    //[item setPaletteLabel:[s label]];
-    NSString *l = [[NSString alloc]initWithUTF8String:label];
-    [item setLabel:l];
-    [item setPaletteLabel:@"Operation"];
-    
-    // create button ...
-    NSRect frame = NSMakeRect(0, 0, 40, 22);
-    //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
-    NSButton *button = [[NSButton alloc]initWithFrame:frame];
-    //[button setImage:[s buttonImage]];
-    //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
-    
-    // TODO: image
-    [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
-    
-    [button setBezelStyle: NSTexturedRoundedBezelStyle];
-    
-    // event
-    EventWrapper *event = [[EventWrapper alloc]
-                           initWithData:userdata callback:callback];
-    if(isToggleButton) {
-        [button setButtonType: NSPushOnPushOffButton];
-        [button setAction:@selector(handleToggleEvent:)];
-    } else {
-        [button setAction:@selector(handleEvent:)];
-    }
-    [button setTarget:event];
-    
-    if(groups) {
-        uic_add_group_widget(obj->ctx, item, groups);
-    }
-    
-    [item setView:button];
-    return item;
-}
-
-- (UcxList*) groups {
-    return groups;
-}
-
-@end
-
-
-/* ---------------------      UiToolbarDelegate      --------------------- */
-
-@implementation UiToolbarDelegate
-
-- (UiToolbarDelegate*) init {
-    allowedItems = [[NSMutableArray alloc]initWithCapacity: 16];
-    defaultItems = [[NSMutableArray alloc]initWithCapacity: 16];
-    items = [[NSMutableDictionary alloc] init];
-    return self;
-}
-
-- (void) addDefault:(NSString*)identifier {
-    [defaultItems addObject: identifier];
-}
-
-- (void) addItem: (NSString*) identifier
-            item: (NSObject<UiToolItem>*) item
-{
-    [allowedItems addObject: identifier];
-    [items setObject: item forKey:identifier];
-}
-
-/*
-- (void) addStockItem:(char*)name
-              stockID:(char*)sid
-             callback:(ui_callback)f
-                 data:(void*)userdata
-{
-    UiToolbarStockItem *item = [[UiToolbarStockItem alloc]initWithData:name
-                                                               stockID:sid callback:f data:userdata];
-    
-    NSString *s = [[NSString alloc]initWithUTF8String:name];
-    [allowedItems addObject: s];
-    [items setObject: item forKey:s];
-}
-*/
-
-// implementation of NSToolbarDelegate methods
-- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
-    NSMutableArray *i = [[NSMutableArray alloc]
-                         initWithCapacity:[allowedItems count] + 3];
-    [i addObject: NSToolbarFlexibleSpaceItemIdentifier];
-    [i addObject: NSToolbarSpaceItemIdentifier];
-    [i addObject: NSToolbarSeparatorItemIdentifier];
-    for(id item in allowedItems) {
-        [i addObject: item];
-    }
-    
-    return i;
-}
-
-- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
-    return defaultItems;
-}
-
-- (NSToolbarItem *) toolbar:(NSToolbar*)toolbar
-         itemForItemIdentifier:(NSString*)identifier
-     willBeInsertedIntoToolbar:(BOOL)flag
-{
-    Protocol *item = @protocol(UiToolItem);
-    item = [items objectForKey: identifier];
-    
-    // get UiObject from toolbar
-    UiObject *obj = [(UiToolbar*)toolbar object];
-    
-    // create new NSToolbarItem
-    return [item createItem:toolbar identifier:identifier object:obj];
-}
-
-@end
-
-
-@implementation UiToolbar
-
-- (UiToolbar*) initWithObject:(UiObject*)object {
-    [self initWithIdentifier: @"MainToolbar"];
-    obj = object;
-    return self;
-}
-
-- (UiObject*) object {
-    return obj;
-}
-
-@end
-
-
-/* ---------------------          functions          --------------------- */
-
-void ui_toolbar_init() {
-    toolbar_delegate = [[UiToolbarDelegate alloc]init];
-}
-
-void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
-    UiToolbarItem *item = [[UiToolbarItem alloc]
-                                initWithIdentifier: name
-                                label: label
-                                callback: f
-                                userdata: udata];
-    
-    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
-    [toolbar_delegate addItem: identifier item: item];
-}
-
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata) {
-    ui_toolitem_stgr(name, stockid, f, udata, -1);
-}
-
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
-    va_list ap;
-    va_start(ap, udata);
-    ui_toolbar_stock_button(name, stockid, NO, f, udata, ap);
-    va_end(ap);
-}
-
-void ui_toolitem_toggle_st(char *name, char *stockid, ui_callback f, void *udata) {
-    ui_toolitem_toggle_stgr(name, stockid, f, udata, -1);
-}
-
-void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
-    va_list ap;
-    va_start(ap, udata);
-    ui_toolbar_stock_button(name, stockid, YES, f, udata, ap);
-    va_end(ap);
-}
-
-
-void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap) {
-    UiToolbarStockItem *item = [[UiToolbarStockItem alloc]
-                                initWithIdentifier: name
-                                stockID: stockid
-                                callback: f
-                                userdata: udata];
-    [item setIsToggleButton: toggle];
-    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
-    [toolbar_delegate addItem: identifier item: item];
-    
-    // add groups
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        [item addGroup: group];
-    }
-}
-
-
-void ui_toolbar_add_default(char *name) {
-    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
-    [toolbar_delegate addDefault: identifier];
-}
-
-NSToolbar* ui_create_toolbar(UiObject *obj) {
-    UiToolbar *toolbar = [[UiToolbar alloc] initWithObject:obj];
-    [toolbar setDelegate: toolbar_delegate];
-    [toolbar setAllowsUserCustomization: true];
-    return toolbar;
-}
-
--- a/ui/cocoa/toolkit.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/toolkit.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -31,58 +31,7 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-
-@interface UiApplicationDelegate : NSObject<NSApplicationDelegate> {
-    
-}
-
-- (void)applicationWillTerminate:(NSNotification*)notification;
-
-- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible;
-
-- (BOOL)application:(NSApplication *)application openFile:(NSString *)filename;
-
-@end
-
-@interface EventWrapper : NSObject {
-    void         *data;
-    ui_callback  callback;
-    int          value;
-}
-
-- (EventWrapper*) initWithData: (void*)data callback:(ui_callback) f;
-
-- (void*) data;
-- (void) setData:(void*)d;
-- (ui_callback) callback;
-- (void) setCallback: (ui_callback)f;
-- (int) intval;
-- (void) setIntval:(int)i;
+void ui_cocoa_onstartup(void);
+void ui_cocoa_onopen(const char *file);
+void ui_cocoa_onexit(void);
 
-- (BOOL)handleEvent:(id)sender;
-- (BOOL)handleStateEvent:(id)sender;
-- (BOOL)handleToggleEvent:(id)sender;
-
-@end
-
-@interface UiThread : NSObject {
-    UiObject      *obj;
-    ui_threadfunc job_func;
-    void          *job_data;
-    ui_callback   finish_callback;
-    void          *finish_data;
-}
-
-- (id) initWithObject:(UiObject*)object;
-- (void) setJobFunction:(ui_threadfunc)func;
-- (void) setJobData:(void*)data;
-- (void) setFinishCallback:(ui_callback)callback;
-- (void) setFinishData:(void*)data;
-
-- (void) start;
-- (void) runJob:(id)n;
-- (void) finish:(id)n;
-
-@end
-
-
--- a/ui/cocoa/toolkit.m	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/toolkit.m	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,321 +26,126 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <stdio.h>
-#import <stdlib.h>
-#import <sys/stat.h>
-#import <sys/types.h>
-#import <errno.h>
-
-#import "../common/context.h"
-#import "../common/document.h"
-#import "../common/properties.h"
+#import "toolkit.h"
 
-#import "toolkit.h"
-#import "window.h"
-#import "menu.h"
-#import "toolbar.h"
-#import "stock.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+#include "../common/threadpool.h"
 
-NSAutoreleasePool *pool;
+#import "AppDelegate.h"
 
-static char *application_name;
+static const char *application_name;
+
+static int app_argc;
+static const char **app_argv;
 
-static ui_callback   appclose_fnc;
-static void          *appclose_udata;
+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   openfile_fnc;
-static void          *openfile_udata;
+/* ------------------- App Init / Event Loop functions ------------------- */
 
-void ui_init(char *appname, int argc, char **argv) {
-    pool = [[NSAutoreleasePool alloc] init];
-    [NSApplication sharedApplication];
-    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+void ui_init(const char *appname, int argc, char **argv) {
+    application_name = appname;
+    app_argc = argc;
+    app_argv = (const char**)argv;
     
-    UiApplicationDelegate *delegate = [[UiApplicationDelegate alloc]init];
-    [NSApp setDelegate: delegate];
-    
-    
+    uic_init_global_context();
+
     uic_docmgr_init();
-    ui_menu_init();
-    ui_toolbar_init();
-    ui_stock_init();
-    
+    uic_menu_init();
+    uic_toolbar_init();
+
     uic_load_app_properties();
+
+    [NSApplication sharedApplication];
+    //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ];
+    //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects];
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
-void ui_exitfunc(ui_callback f, void *userdata) {
-    appclose_fnc = f;
-    appclose_udata = userdata;
-}
-
-void ui_openfilefunc(ui_callback f, void *userdata) {
-    openfile_fnc = f;
-    openfile_udata = userdata;
+void ui_onstartup(ui_callback f, void *userdata) {
+    startup_func = f;
+    startup_data = userdata;
 }
 
-void ui_show(UiObject *obj) {
-    uic_check_group_widgets(obj->ctx);
-    if([obj->widget class] == [UiCocoaWindow class]) {
-        UiCocoaWindow *window = (UiCocoaWindow*)obj->widget;
-        [window makeKeyAndOrderFront:nil];
-    } else {
-        printf("Error: ui_show: Object is not a Window!\n");
-    }
-}
-
-void ui_set_show_all(UIWIDGET widget, int value) {
-    // TODO
-}
-
-void ui_set_visible(UIWIDGET widget, int visible) {
-    // TODO
-}
-
-void ui_set_enabled(UIWIDGET widget, int enabled) {
-    [(id)widget setEnabled: enabled];
+void ui_onopen(ui_callback f, void *userdata) {
+    open_func = f;
+    open_data = userdata;
 }
 
-
-
-void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
-    UiThread *thread = [[UiThread alloc]initWithObject:obj];
-    [thread setJobFunction:tf];
-    [thread setJobData:td];
-    [thread setFinishCallback:f];
-    [thread setFinishData:fd];
-    [thread start];
-}
-
-void ui_main() {
-    [NSApp run];
-    [pool release];
+void ui_onexit(ui_callback f, void *userdata) {
+    exit_func = f;
+    exit_data = userdata;
 }
 
-
-void ui_clipboard_set(char *str) {
-    NSString *string = [[NSString alloc] initWithUTF8String:str];
-    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
-    [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
-    [pasteBoard setString:string forType:NSStringPboardType];
-}
-
-char* ui_clipboard_get() {
-    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
-    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];
-    NSDictionary *options = [NSDictionary dictionary];
-    NSArray *data = [pasteBoard readObjectsForClasses:classes options:options];
-    
-    if(data != nil) {
-        NSString *str = [data componentsJoinedByString: @""];
-        
-        // copy C string
-        size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-        const char *cstr = [str UTF8String];
-        char *value = malloc(length + 1);
-        memcpy(value, cstr, length);
-        value[length] = '\0';
-        
-        return value;
-    } else {
-        return NULL;
+void ui_cocoa_onstartup(void) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(startup_func) {
+        startup_func(&e, startup_data);
     }
 }
 
-
-@implementation UiApplicationDelegate
-
-- (void)applicationWillTerminate:(NSNotification*)notification {
-    printf("terminate\n");
-}
-
-- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible; {
-    if(!visible) {
-        printf("reopen\n");
-    }
-    return NO;
-}
-
-- (BOOL)application:(NSApplication*)application openFile:(NSString*)filename {
-    if(openfile_fnc) {
-        UiEvent event;
-        event.obj = NULL;
-        event.document = NULL;
-        event.window = NULL;
-        event.eventdata = (void*)[filename UTF8String];
-        event.intval = 0;
-        openfile_fnc(&event, openfile_udata);
+void ui_cocoa_onopen(const char *file) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(open_func) {
+        open_func(&e, open_data);
     }
-    
-    return NO;
-}
-
-@end
-
-
-@implementation EventWrapper
-
-- (EventWrapper*) initWithData: (void*)d callback:(ui_callback) f {
-    data = d;
-    callback = f;
-    value = 0;
-    return self;
-}
-
-
-- (void*) data {
-    return data;
-}
-
-- (void) setData:(void*)d {
-    data = d;
-}
-
-
-- (ui_callback) callback {
-    return callback;
-}
-
-- (void) setCallback: (ui_callback)f {
-    callback = f;
-}
-
-- (int) intval {
-    return value;
-}
-
-- (void) setIntval:(int)i {
-    value = i;
-}
-
-
-- (BOOL)handleEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    
-    UiEvent event;
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-        event.intval = value;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    
-    return true;
 }
 
-- (BOOL)handleStateEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    int state = [sender state] ? NSOffState : NSOnState;
-    
-    UiEvent event;
-    event.intval = state;
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-        // if the sender is a menu item, we have to save the state for this
-        // window
-        UiMenuItem *wmi = [(UiCocoaWindow*)activeWindow getMenuItem: sender];
-        if(wmi) {
-            // update state in window data
-            wmi->state = state;
-        }
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    [sender setState: state];
-    
-    return true;
-}
-
-- (BOOL)handleToggleEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    
-    UiEvent event;
-    event.intval = [sender state];
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    
-    return true;
-}
-
-@end
-
-@implementation UiThread
-
-- (id) initWithObject:(UiObject*)object {
-    obj = object;
-    job_func = NULL;
-    job_data = NULL;
-    finish_callback = NULL;
-    finish_data = NULL;
-    return self;
-}
-
-- (void) setJobFunction:(ui_threadfunc)func {
-    job_func = func;
-}
-
-- (void) setJobData:(void*)data {
-    job_data = data;
-}
-
-- (void) setFinishCallback:(ui_callback)callback {
-    finish_callback = callback;
-}
-
-- (void) setFinishData:(void*)data {
-    finish_data = data;
-}
-
-- (void) start {
-    [NSThread detachNewThreadSelector:@selector(runJob:)
-                             toTarget:self
-                           withObject:nil];
-}
-
-- (void) runJob:(id)n {
-    int result = job_func(job_data);
-    if(!result) {
-        [self performSelectorOnMainThread:@selector(finish:)
-                               withObject:nil
-                            waitUntilDone:NO];
+void ui_cocoa_onexit(void) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(exit_func) {
+        exit_func(&e, exit_data);
     }
 }
 
-- (void) finish:(id)n {
-    UiEvent event;
-    event.obj = obj;
-    event.window = obj->window;
-    event.document = obj->ctx->document;
-    event.eventdata = NULL;
-    event.intval = 0;
-    finish_callback(&event, finish_data);
+void ui_main(void) {
+    NSApplicationMain(app_argc, app_argv);
+}
+
+/* ------------------- Window Visibility functions ------------------- */
+
+void ui_show(UiObject *obj) {
+    if(obj->wobj) {
+        NSWindow *window = (__bridge NSWindow*)obj->wobj;
+        [window makeKeyAndOrderFront:nil];
+    }
 }
 
-@end
+void ui_close(UiObject *obj) {
+
+}
+
+/* ------------------- Job Control / Threadpool functions ------------------- */
 
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
 
+}
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+
+}
--- a/ui/cocoa/tree.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
- 
- #import "../ui/tree.h"
- #import "toolkit.h"
- 
- 
-@interface UiTableDataSource : NSObject<NSTableViewDataSource, NSTableViewDelegate> {
-    UiList          *data;
-    UiModelInfo     *info;
-    UiListSelection *lastSelection;
-}
-
-- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo;
-
-- (void)handleDoubleAction:(id)sender;
-
-@end
-
-
-char* ui_type_to_string(UiModelType type, void *data, BOOL *free);
-
--- a/ui/cocoa/tree.m	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,246 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2012 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.
- */
- 
-#import <stdio.h>
-#import <stdlib.h>
- 
-#import "tree.h"
-#import "container.h"
-#import "window.h"
-#import "../common/context.h"
-#import <ucx/utils.h>
-
-@implementation UiTableDataSource
-
-- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo {
-    data = list;
-    info = modelinfo;
-    lastSelection = NULL;
-    return self;
-}
-
-- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableview {
-    return data->count(data);
-}
-
-- (id)tableView:                  (NSTableView*)tableview
-        objectValueForTableColumn:(NSTableColumn*)column
-                              row:(NSInteger)row
-{
-    int column_index = [[column identifier]intValue];
-    
-    void *row_data = data->get(data, row);
-    void *cell_data = info->getvalue(row_data, column_index);
-    
-    BOOL f = false;
-    char *str = ui_type_to_string(info->types[column_index], cell_data, &f);
-    NSString *s = [[NSString alloc]initWithUTF8String:str];
-    return s;
-}
-
-- (void)tableView:(NSTableView *)tableview
-        setObjectValue:(id)object
-        forTableColumn:(NSTableColumn *)column
-                   row:(NSInteger)row
-{
-    int column_index = [[column identifier]intValue];
-    
-    // TODO
-}
-
-- (void)tableViewSelectionDidChange:(NSNotification *)notification {
-    NSTableView *tableview = (NSTableView*)notification.object;
-    
-    // create selection object
-    UiListSelection *selection = malloc(sizeof(UiListSelection));
-    selection->count = [tableview numberOfSelectedRows];
-    
-    selection->rows = calloc(selection->count, sizeof(int));
-    NSIndexSet *indices = [tableview selectedRowIndexes];
-    NSUInteger index = [indices firstIndex];
-    int i=0;
-    while (index!=NSNotFound) {
-        selection->rows[i] = index;
-        index = [indices indexGreaterThanIndex:index];
-        i++;
-    }
-    
-    // create event object
-    UiEvent event;
-    NSWindow *activeWindow = [NSApp keyWindow];
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    event.eventdata = selection;
-    event.intval = selection->count == 0 ? -1 : selection->rows[0];
-    
-    // callback
-    info->selection(&event, info->userdata);
-    
-    // cleanup
-    if(lastSelection) {
-        free(lastSelection->rows);
-        free(lastSelection);
-    }
-    lastSelection = selection;
-}
-
-- (void)handleDoubleAction:(id)sender {
-    // create event object
-    UiEvent event;
-    NSWindow *activeWindow = [NSApp keyWindow];
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    event.eventdata = lastSelection;
-    event.intval = lastSelection->count == 0 ? -1 : lastSelection->rows[0];
-    
-    info->activate(&event, info->userdata);
-}
-
-- (BOOL)tableView:(NSTableView *)tableview isGroupRow:(NSInteger)row {
-    return NO;
-}
-
-@end
-
-
-UIWIDGET ui_table(UiObject *obj, UiList *model, UiModelInfo *modelinfo) {
-    UiContainer *ct = uic_get_current_container(obj);
-    NSRect frame = ct->getframe(ct);
-    
-    NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
-    [scrollview setHasVerticalScroller:YES];
-    //[scrollvew setHasHorizontalScroller:YES];
-    [scrollview setBorderType:NSNoBorder];
-    //[scrollview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-    
-    NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
-    [scrollview setDocumentView:tableview];
-    [tableview setAllowsMultipleSelection: YES];
-    //[tableview setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
-    
-    // add columns
-    for(int i=0;i<modelinfo->columns;i++) {
-        NSString *cid = [[NSString alloc]initWithFormat: @"%d", i];
-        NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:cid];
-        
-        NSString *title = [[NSString alloc]initWithUTF8String: modelinfo->titles[i]];
-        [[column headerCell] setStringValue:title];
-        
-        [tableview addTableColumn:column];
-    }
-    
-    UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:model modelInfo:modelinfo];
-    [tableview setDataSource:source];
-    [tableview setDelegate:source];
-
-    [tableview setDoubleAction:@selector(handleDoubleAction:)];
-    [tableview setTarget:source];
-    
-    
-    ct->add(ct, scrollview);
-    return scrollview;
-}
-
-
-UIWIDGET ui_listview_var(UiObject *obj, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    NSRect frame = ct->getframe(ct);
-    
-    NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
-    [scrollview setHasVerticalScroller:YES];
-    
-    [scrollview setBorderType:NSNoBorder];
-    
-    NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
-    [scrollview setDocumentView:tableview];
-    [tableview setAllowsMultipleSelection: NO];
-    
-    // add single column
-    NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:@"c"];
-    [tableview addTableColumn:column];
-    
-    // create model info
-    UiModelInfo *modelinfo = ui_model_info(obj->ctx, UI_STRING, -1);
-    
-    // add source
-    UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:list->list modelInfo:modelinfo];
-    
-    [tableview setDataSource:source];
-    [tableview setDelegate:source];
-
-    [tableview setDoubleAction:@selector(handleDoubleAction:)];
-    [tableview setTarget:source];
-    
-    ct->add(ct, scrollview);
-    return scrollview;
-}
-
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    UiListPtr *listptr = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
-    listptr->list = list;
-    return ui_listview_var(obj, listptr, getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = var->value;
-        return ui_listview_var(obj, value->listptr, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-
-// TODO: motif code duplicate
-char* ui_type_to_string(UiModelType type, void *data, BOOL *free) {
-    switch(type) {
-        case UI_STRING: *free = FALSE; return data;
-        case UI_INTEGER: {
-            *free = TRUE;
-            int *val = data;
-            sstr_t str = ucx_asprintf(ucx_default_allocator(), "%d", *val);
-            return str.ptr;
-        }
-    }
-    *free = FALSE;
-    return NULL;
-}
--- a/ui/cocoa/window.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/window.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,27 +26,4 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <Cocoa/Cocoa.h>
-#import "../ui/window.h"
-#import <ucx/list.h>
-#import <ucx/map.h>
-
-#import "menu.h"
-
-
-
-@interface UiCocoaWindow : NSWindow {
-    UiObject *uiobj;
-    UcxMap   *menus; // key: NSMenu value: UcxList of UiMenuItem
-    UcxMap   *items; // key: NSMenuItem value: UiMenuItem
-}
-
-- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj;
-- (UiObject*) object;
-- (void) setObject:(UiObject*)obj;
-- (void) setMenuItems:(UcxList*)menuItems;
-- (void) setMenuItemLists:(UcxList*)itemLists;
-- (UiMenuItem*) getMenuItem:(NSMenuItem*)item;
-- (void) updateMenu:(NSMenu*)menu;
-
-@end
+#import "toolkit.h"
\ No newline at end of file
--- a/ui/cocoa/window.m	Sun May 23 09:44:43 2021 +0200
+++ b/ui/cocoa/window.m	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,195 +26,43 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#import "window.h"
 
-#import "window.h"
-#import "menu.h"
-#import "toolbar.h"
-#import "container.h"
-#import <ucx/mempool.h>
-#import "../common/context.h"
-
-static int window_default_width = 600;
-static int window_default_height = 500;
-
-@implementation UiCocoaWindow
+#import "MainWindow.h"
+#import "WindowManager.h"
 
-- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj {
-    self = [self initWithContentRect:frame
-                           styleMask:NSTitledWindowMask |
-                                     NSResizableWindowMask |
-                                     NSClosableWindowMask |
-                                     NSMiniaturizableWindowMask
-                             backing:NSBackingStoreBuffered
-                               defer:false];
-    
-    uiobj = obj;
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
-    menus = ucx_map_new_a(allocator, 8);
-    items = ucx_map_new_a(allocator, 64);
-    
-    return self;
-}
+#include "../ui/window.h"
+#include "../ui/properties.h"
+#include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
 
-- (UiObject*) object {
-    return uiobj;
-}
+#include <cx/mempool.h>
 
-- (void)  setObject:(UiObject*)obj {
-    uiobj = obj;
-}
-
-- (void) setMenuItems:(UcxList*)menuItems {
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+static UiObject* create_window(const char *title, BOOL simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+    obj->ref = 0;
     
-    UCX_FOREACH(elm, menuItems) {
-        UiStateItem *item = elm->data;
-        NSMenu *menu = [item->item menu];
-        
-        // create UiMenuItem which represents an NSMenuItem for a Window
-        UiMenuItem *windowItem = ucx_mempool_malloc(uiobj->ctx->mempool, sizeof(UiMenuItem));
-        windowItem->item = item->item;
-        windowItem->state = 0;
-        if(item->var) {
-            // bind value
-            UiVar *var = uic_connect_var(uiobj->ctx, item->var, UI_VAR_INTEGER);
-            if(var) {
-                UiInteger *value = var->value;
-                value->obj = windowItem;
-                value->get = ui_menuitem_get;
-                value->set = ui_menuitem_set;
-                value = 0;
-            } else {
-                // TODO: error
-            }
-        }
-        
-        // add item
-        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
-        abstractItem->update = ui_update_item;
-        abstractItem->item_data = windowItem;
-        UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
-        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
-        ucx_map_put(menus, ucx_key(&menu, sizeof(void*)), itemList);
-        
-        ucx_map_put(items, ucx_key(&windowItem->item, sizeof(void*)), windowItem);
-    }
-}
-
-- (void) setMenuItemLists:(UcxList*)itemLists {
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
-    
-    UCX_FOREACH(elm, itemLists) {
-        UiMenuItemList *list = elm->data;
-        
-        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
-        abstractItem->update = ui_update_item_list;
-        abstractItem->item_data = list;
-        
-        UcxList *itemList = ucx_map_get(menus, ucx_key(&list->menu, sizeof(void*)));
-        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
-        ucx_map_put(menus, ucx_key(&list->menu, sizeof(void*)), itemList);
-        
-    }
-}
-
-- (UiMenuItem*) getMenuItem:(NSMenuItem*)item {
-    return ucx_map_get(items, ucx_key(&item, sizeof(void*)));
-}
-
-- (void) updateMenu:(NSMenu*)menu {
-    UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
-    UCX_FOREACH(elm, itemList) {
-        UiAbstractMenuItem *item = elm->data;
-        item->update(self, item->item_data);
-    }
-    
-    // update group items
-    // TODO: use only one loop for all items
-    int ngroups = 0;
-    int *groups = ui_active_groups(uiobj->ctx, &ngroups);
-    
-    NSArray *groupItems = [menu itemArray];
-    int count = [groupItems count];
-    for(int i=0;i<count;i++) {
-        id item = [groupItems objectAtIndex:i];
-        if([item class] == [UiGroupMenuItem class]) {
-            [item checkGroups: groups count:ngroups];
-        }
-    }
-    free(groups);
-}
-
-@end
-
-
-/* ------------------------------ public API ------------------------------ */
-
-UiObject* ui_window(char *title, void *window_data) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
     
-    // create native window
-    NSRect frame = NSMakeRect(
-                              300,
-                              200,
-                              window_default_width,
-                              window_default_height);
+    MainWindow *window = [[MainWindow alloc] init:obj];
+    [[WindowManager sharedWindowManager] addWindow:window];
+    window.releasedWhenClosed = false;
     
-    /*
-    UiCocoaWindow *window = [[UiCocoaWindow alloc] initWithContentRect:frame
-                                styleMask:NSTitledWindowMask | NSResizableWindowMask |
-                                NSClosableWindowMask | NSMiniaturizableWindowMask
-                                backing:NSBackingStoreBuffered
-                                defer:false];
-    */
-    UiCocoaWindow *window = [[UiCocoaWindow alloc] init:frame object:obj];
-    
-    NSString *titleStr = [[NSString alloc] initWithUTF8String:title];
-    [window setTitle:titleStr];
-    
-    UiMenuDelegate *menuDelegate = ui_menu_delegate();
-    [window setMenuItems: [menuDelegate items]];
-    [window setMenuItemLists: [menuDelegate lists]];
-    
-    NSToolbar *toolbar = ui_create_toolbar(obj);
-    [window setToolbar: toolbar];
-    
-    obj->widget = (NSView*)window;
-    obj->window = window_data;
-    obj->container = ui_window_container(obj, window);
-    
+    obj->wobj = (__bridge void*)window;
     
     return obj;
 }
 
-void ui_close(UiObject *obj) {
-    // TODO
+UiObject* ui_window(const char *title, void *window_data) {
+    UiObject *obj = create_window(title, FALSE);
+    obj->window = window_data;
+    return obj;
 }
 
-char* ui_openfiledialog(UiObject *obj) {
-    NSOpenPanel* op = [NSOpenPanel openPanel];
-    if ([op runModal] == NSOKButton) {
-        NSArray *urls = [op URLs];
-        NSURL *url = [urls objectAtIndex:0];
-        
-        const char *str = [[url path] UTF8String];
-        return (char*)strdup(str);
-    }
-    return NULL;
+UiObject* ui_simple_window(const char *title, void *window_data) {
+    UiObject *obj = create_window(title, TRUE);
+    obj->window = window_data;
+    return obj;
 }
-
-char* ui_savefiledialog(UiObject *obj) {
-    NSSavePanel* sp = [NSSavePanel savePanel];
-    if ([sp runModal] == NSOKButton) {
-        NSURL *url = [sp URL];
-        
-        const char *str = [[url path] UTF8String];
-        return (char*)strdup(str);
-    }
-    return NULL;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/condvar.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,70 @@
+/*
+ * 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 "condvar.h"
+
+#include <stdlib.h>
+
+UiCondVar* ui_condvar_create(void) {
+    UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar));
+    var->var.data = NULL;
+    var->var.intdata = 0;
+    var->set = 0;
+    pthread_mutex_init(&var->lock, NULL);
+    pthread_cond_init(&var->cond, NULL);
+    return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    if(!p->set) {
+        pthread_cond_wait(&p->cond, &p->lock);
+    }
+    p->set = 0;
+    pthread_mutex_unlock(&p->lock);
+    
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    p->var.data = data;
+    p->var.intdata = intdata;
+    p->set = 1;
+    pthread_cond_signal(&p->cond);
+    pthread_mutex_unlock(&p->lock);
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_destroy(&p->lock);
+    pthread_cond_destroy(&p->cond);
+    free(p);
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/condvar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef UIC_CONDVAR_H
+#define UIC_CONDVAR_H
+
+#include "../ui/toolkit.h"
+
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiPosixCondVar {
+    UiCondVar var;
+    int set;
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+} UiPosixCondVar;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONDVAR_H */
+
--- a/ui/common/context.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/context.c	Sat Jan 04 16:38:48 2025 +0100
@@ -32,15 +32,20 @@
 #include <inttypes.h>
 #include <stdarg.h>
 
+#include <cx/array_list.h>
+#include <cx/compare.h>
+#include <cx/mempool.h>
+
 #include "context.h"
 #include "../ui/window.h"
 #include "document.h"
 #include "types.h"
 
+
 static UiContext* global_context;
 
 void uic_init_global_context(void) {
-    UcxMempool *mp = ucx_mempool_new(32);
+    CxMempool *mp = cxBasicMempoolCreate(32);
     global_context = uic_context(NULL, mp);
 }
 
@@ -48,17 +53,22 @@
     return global_context;
 }
 
-UiContext* uic_context(UiObject *toplevel, UcxMempool *mp) {
-    UiContext *ctx = ucx_mempool_malloc(mp, sizeof(UiContext));
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp) {
+    UiContext *ctx = cxMalloc(mp->allocator, sizeof(UiContext));
     memset(ctx, 0, sizeof(UiContext));
-    ctx->mempool = mp;
+    ctx->mp = mp;
+    ctx->allocator = mp->allocator;
     ctx->obj = toplevel;
-    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16);
+    
+    ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, 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->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document2;
     
-#ifdef UI_GTK
+#if UI_GTK2 || UI_GTK3
     if(toplevel && toplevel->widget) {
         ctx->accel_group = gtk_accel_group_new();
         gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group);
@@ -72,9 +82,14 @@
     return ctx->parent ? uic_root_context(ctx->parent) : ctx;
 }
 
+void uic_context_prepare_close(UiContext *ctx) {
+    cxListClear(ctx->groups);
+    cxListClear(ctx->group_widgets);
+}
+
 void uic_context_attach_document(UiContext *ctx, void *document) {
-    ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document);
-    ctx->document = ctx->documents->data;
+    cxListAdd(ctx->documents, document);
+    ctx->document = document;
     
     UiContext *doc_ctx = ui_document_context(document);
     
@@ -82,25 +97,18 @@
     // as any document variable
     UiContext *var_ctx = ctx;
     while(var_ctx) {
-        if(var_ctx->vars_unbound && var_ctx->vars_unbound->count > 0) {
-            UcxMapIterator i = ucx_map_iterator(var_ctx->vars_unbound);
-            UiVar *var;
-            // rmkeys holds all keys, that shall be removed from vars_unbound
-            UcxKey *rmkeys = calloc(var_ctx->vars_unbound->count, sizeof(UcxKey));
-            size_t numkeys = 0;
-            UCX_MAP_FOREACH(key, var, i) {
-                UiVar *docvar = ucx_map_get(doc_ctx->vars, key);
+        if(var_ctx->vars_unbound &&  cxMapSize(var_ctx->vars_unbound) > 0) {
+            CxIterator i = cxMapIterator(var_ctx->vars_unbound);
+            cx_foreach(CxMapEntry*, entry, i) {
+                printf("attach %s\n", entry->key->data);
+                UiVar *var = entry->value;
+                UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
                 if(docvar) {
                     // bind var to document var
                     uic_copy_binding(var, docvar, TRUE);
-                    rmkeys[numkeys++] = key; // save the key for removal
+                    cxIteratorFlagRemoval(i);
                 }
             }
-            // now that we may have bound some vars to the document,
-            // we can remove them from the unbound map
-            for(size_t k=0;k<numkeys;k++) {
-                ucx_map_remove(var_ctx->vars_unbound, rmkeys[k]);
-            }
         }
         
         var_ctx = ctx->parent;
@@ -108,56 +116,63 @@
 }
 
 static void uic_context_unbind_vars(UiContext *ctx) {
-    UcxMapIterator i = ucx_map_iterator(ctx->vars);
-    UiVar *var;
-    UCX_MAP_FOREACH(key, var, i) {
+    CxIterator i = cxMapIterator(ctx->vars);
+    cx_foreach(CxMapEntry*, entry, i) {
+        UiVar *var = entry->value;
         if(var->from && var->from_ctx) {
             uic_save_var2(var);
             uic_copy_binding(var, var->from, FALSE);
-            ucx_map_put(var->from_ctx->vars_unbound, key, var->from);
+            cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from);
             var->from_ctx = ctx;
         }
     }
     
-    UCX_FOREACH(elm, ctx->documents) {
-        UiContext *subctx = ui_document_context(elm->data);
-        uic_context_unbind_vars(subctx);
+    if(ctx->documents) {
+        i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
+            uic_context_unbind_vars(subctx);
+        }
     }
 }
 
 void uic_context_detach_document2(UiContext *ctx, void *document) {
     // find the document in the documents list
-    UcxList *doc = NULL;
-    UCX_FOREACH(elm, ctx->documents) {
-        if(elm->data == document) {
-            doc = elm;
-            break;
-        }
-    }
-    if(!doc) {
-        return; // document is not a subdocument of this context
+    ssize_t docIndex = cxListFind(ctx->documents, document);
+    if(docIndex < 0) {
+        return;
     }
     
-    ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc);
-    ctx->document = ctx->documents ? ctx->documents->data : NULL;
+    cxListRemove(ctx->documents, docIndex);
+    ctx->document = cxListAt(ctx->documents, 0);
     
     UiContext *docctx = ui_document_context(document);
     uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
 }
 
 void uic_context_detach_all(UiContext *ctx) {
-    UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL);
-    UCX_FOREACH(elm, ls) {
-        ctx->detach_document2(ctx, elm->data);
+    // copy list
+    CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    CxIterator i = cxListIterator(ctx->documents);
+    cx_foreach(void *, doc, i) {
+        cxListAdd(ls, doc);
     }
-    ucx_list_free(ls);
+    
+    // detach documents
+    i = cxListIterator(ls);
+    cx_foreach(void *, doc, i) {
+        ctx->detach_document2(ctx, doc);
+    }
+    
+    cxListDestroy(ls);
 }
 
-static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) {
-    UiVar *var = ucx_map_get(ctx->vars, key);
-    if(!var) {
-        UCX_FOREACH(elm, ctx->documents) {
-            UiContext *subctx = ui_document_context(elm->data);
+static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
+    UiVar *var = cxMapGet(ctx->vars, key);
+    if(!var && ctx->documents) {
+        CxIterator i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
             var = ctx_getvar(subctx, key);
             if(var) {
                 break;
@@ -168,11 +183,18 @@
 }
 
 UiVar* uic_get_var(UiContext *ctx, const char *name) {
-    UcxKey key = ucx_key(name, strlen(name));
+    CxHashKey key = cx_hash_key(name, strlen(name));
     return ctx_getvar(ctx, key);
 }
 
 UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) {
+    if(ctx->vars_unbound) {
+        UiVar *unbound = cxMapGet(ctx->vars_unbound, name);
+        if(unbound) {
+            return unbound;
+        }
+    }
+    
     UiVar *var = uic_get_var(ctx, name);
     if(var) {
         if(var->type == type) {
@@ -188,14 +210,25 @@
     var->from = NULL;
     var->from_ctx = ctx;
 
+    cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var);
+
     if(!ctx->vars_unbound) {
-        ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16);
+        ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
     }
-    ucx_map_cstr_put(ctx->vars_unbound, name, var);
+    cxMapPut(ctx->vars_unbound, name, var);
     
     return var;
 }
 
+UiVar* uic_create_value_var(UiContext* ctx, void* value) {
+    UiVar *var = (UiVar*)ui_malloc(ctx, sizeof(UiVar));
+    var->from = NULL;
+    var->from_ctx = ctx;
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return var;
+}
+
 void* uic_create_value(UiContext *ctx, UiVarType type) {
     void *val = NULL;
     switch(type) {
@@ -224,10 +257,25 @@
             val = ui_range_new(ctx, NULL);
             break;
         }
+        case UI_VAR_GENERIC: {
+            val = ui_generic_new(ctx, NULL);
+        }
     }
     return val;
 }
 
+
+UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) {
+    if (value) {
+        return uic_create_value_var(current, value);
+    }
+    if (varname) {
+        return uic_create_var(toplevel, varname, type);
+    }
+    return NULL;
+}
+
+
 void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) {
     // check type
     if(from->type != to->type) {
@@ -283,11 +331,24 @@
             break;
         }
         case UI_VAR_LIST: {
-            UiList *f = fromvalue;
+            // TODO: not sure how correct this is
+
+            UiList *f = from->value;
             UiList *t = to->value;
-            if(!f->obj) break;
-            uic_list_copy(f, t);
-            t->update(t, -1);
+            if (f->obj) {
+                t->obj = f->obj;
+                t->update = f->update;
+                t->getselection = f->getselection;
+                t->setselection = f->setselection;
+            }
+
+            UiVar tmp = *from;
+            *from = *to;
+            *to = tmp;
+
+            UiList* t2 = to->value;
+            ui_notify(t2->observers, NULL);
+            
             break;
         }
         case UI_VAR_RANGE: {
@@ -300,6 +361,14 @@
             t->set(t, t->value);
             break;
         }
+        case UI_VAR_GENERIC: {
+            UiGeneric *f = fromvalue;
+            UiGeneric *t = to->value;
+            if(!f->obj) break;
+            uic_generic_copy(f, t);
+            t->set(t, t->value, t->type);
+            break;
+        }
     }
 }
 
@@ -312,6 +381,7 @@
         case UI_VAR_TEXT: uic_text_save(var->value); break;
         case UI_VAR_LIST: break;
         case UI_VAR_RANGE: uic_range_save(var->value); break;
+        case UI_VAR_GENERIC: uic_generic_save(var->value); break;
     }
 }
 
@@ -324,6 +394,7 @@
         case UI_VAR_TEXT: uic_text_unbind(var->value); break;
         case UI_VAR_LIST: uic_list_unbind(var->value); break;
         case UI_VAR_RANGE: uic_range_unbind(var->value); break;
+        case UI_VAR_GENERIC: uic_generic_unbind(var->value); break;
     }
 }
 
@@ -358,9 +429,9 @@
     var->value = value;
     var->from = NULL;
     var->from_ctx = ctx;
-    size_t oldcount = ctx->vars->count;
-    ucx_map_cstr_put(ctx->vars, name, var);
-    if(ctx->vars->count != oldcount + 1) {
+    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);
     }
     
@@ -375,8 +446,7 @@
 }
 
 void uic_remove_bound_var(UiContext *ctx, UiVar *var) {
-    // TODO: implement
-    printf("TODO: implement uic_remove_bound_var\n");
+    // TODO
 }
 
 
@@ -395,10 +465,14 @@
     ctx->close_data = udata;
 }
 
+UIEXPORT void ui_context_destroy(UiContext *ctx) {
+    cxMempoolDestroy(ctx->mp);
+}
+
 
 void ui_set_group(UiContext *ctx, int group) {
-    if(ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL) == -1) {
-        ctx->groups = ucx_list_append_a(ctx->mempool->allocator, ctx->groups, (void*)(intptr_t)group);
+    if(cxListFind(ctx->groups, &group) == -1) {
+        cxListAdd(ctx->groups, &group);
     }
     
     // enable/disable group widgets
@@ -406,10 +480,9 @@
 }
 
 void ui_unset_group(UiContext *ctx, int group) {
-    int i = ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL);
+    int i = cxListFind(ctx->groups, &group);
     if(i != -1) {
-        UcxList *elm = ucx_list_get(ctx->groups, i);
-        ctx->groups = ucx_list_remove_a(ctx->mempool->allocator, ctx->groups, elm);
+        cxListRemove(ctx->groups, i);
     }
     
     // enable/disable group widgets
@@ -417,28 +490,16 @@
 }
 
 int* ui_active_groups(UiContext *ctx, int *ngroups) {
-    if(!ctx->groups) {
-        return NULL;
-    }
-    
-    int nelm = ucx_list_size(ctx->groups);
-    int *groups = calloc(sizeof(int), nelm);
-    
-    int i = 0;
-    UCX_FOREACH(elm, ctx->groups) {
-        groups[i++] = (intptr_t)elm->data;
-    }
-    
-    *ngroups = nelm;
-    return groups;
+    *ngroups = cxListSize(ctx->groups);
+    return cxListAt(ctx->groups, 0);
 }
 
 void uic_check_group_widgets(UiContext *ctx) {
     int ngroups = 0;
     int *groups = ui_active_groups(ctx, &ngroups);
     
-    UCX_FOREACH(elm, ctx->group_widgets) {
-        UiGroupWidget *gw = elm->data;
+    CxIterator i = cxListIterator(ctx->group_widgets);
+    cx_foreach(UiGroupWidget *, gw, i) {
         char *check = calloc(1, gw->numgroups);
         
         for(int i=0;i<ngroups;i++) {
@@ -456,64 +517,89 @@
                 break;
             }
         }
+        free(check);
         gw->enable(gw->widget, enable);
     }
-    
-    if(groups) {
-        free(groups);
-    }
 }
 
 void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
     // get groups
-    UcxList *groups = NULL;
+    CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
     va_list ap;
     va_start(ap, enable);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        groups = ucx_list_append(groups, (void*)(intptr_t)group);
+        cxListAdd(groups, &group);
     }
     va_end(ap);
     
     uic_add_group_widget(ctx, widget, enable, groups);
     
-    ucx_list_free(groups);
+    cxListDestroy(groups);
+}
+
+size_t uic_group_array_size(const int *groups) {
+    int i;
+    for(i=0;groups[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_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups) {
-    UcxMempool *mp = ctx->mempool;
-    UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget));
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) {
+    const CxAllocator *a = ctx->allocator;
+    UiGroupWidget gw;
     
-    gw->widget = widget;
-    gw->enable = enable;
-    gw->numgroups = ucx_list_size(groups);
-    gw->groups = ucx_mempool_calloc(mp, gw->numgroups, sizeof(int));
-    int i = 0;
-    UCX_FOREACH(elm, groups) {
-        gw->groups[i++] = (intptr_t)elm->data;
+    gw.widget = widget;
+    gw.enable = enable;
+    gw.numgroups = numgroups;
+    gw.groups = cxCalloc(a, numgroups, sizeof(int));
+    
+    // copy groups
+    if(groups) {
+        memcpy(gw.groups, groups, gw.numgroups * sizeof(int));
     }
     
-    ctx->group_widgets = ucx_list_append_a(
-            mp->allocator,
-            ctx->group_widgets,
-            gw);
+    cxListAdd(ctx->group_widgets, &gw);
+}
+
+void uic_remove_group_widget(UiContext *ctx, void *widget) {
+    (void)cxListFindRemove(ctx->group_widgets, widget);
+}
+
+UIEXPORT void *ui_allocator(UiContext *ctx) {
+    return (void*)ctx->allocator;
+}
+
+void* ui_cx_mempool(UiContext *ctx) {
+    return ctx->mp;
 }
 
 void* ui_malloc(UiContext *ctx, size_t size) {
-    return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL;
+    return ctx ? cxMalloc(ctx->allocator, size) : NULL;
 }
 
 void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) {
-    return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL;
+    return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL;
 }
 
 void ui_free(UiContext *ctx, void *ptr) {
-    if(ctx) {
-        ucx_mempool_free(ctx->mempool, ptr);
+    if(ctx && ptr) {
+        cxFree(ctx->allocator, ptr);
     }
 }
 
 void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
-    return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL;
+    return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
 }
 
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) {
+    if(!ctx) {
+        return NULL;
+    }
+    cxstring s = cx_str(str);
+    cxmutstr d = cx_strdup_a(ctx->allocator, s);
+    return d.ptr;
+}
--- a/ui/common/context.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/context.h	Sat Jan 04 16:38:48 2025 +0100
@@ -30,9 +30,11 @@
 #define	UIC_CONTEXT_H
 
 #include "../ui/toolkit.h"
-#include <ucx/map.h>
-#include <ucx/mempool.h>
-#include <ucx/list.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+#include <cx/list.h>
+#include <cx/linked_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
@@ -52,22 +54,24 @@
     UI_VAR_STRING,
     UI_VAR_TEXT,
     UI_VAR_LIST,
-    UI_VAR_RANGE
+    UI_VAR_RANGE,
+    UI_VAR_GENERIC
 };
 
 struct UiContext {
     UiContext     *parent;
     UiObject      *obj;
-    UcxMempool    *mempool;
+    CxMempool *mp;
+    const CxAllocator *allocator;
     
     void          *document;
-    UcxList       *documents;
+    CxList        *documents;
     
-    UcxMap        *vars; // manually created context vars
-    UcxMap        *vars_unbound; // unbound vars created by widgets
+    CxMap         *vars; // manually created context vars
+    CxMap         *vars_unbound; // unbound vars created by widgets
     
-    UcxList       *groups; // int list
-    UcxList       *group_widgets; // UiGroupWidget* list
+    CxList        *groups; // int list
+    CxList        *group_widgets; // UiGroupWidget list
     
     void (*attach_document)(UiContext *ctx, void *document);
     void (*detach_document2)(UiContext *ctx, void *document); 
@@ -75,8 +79,14 @@
     char          *title;
     
 #ifdef UI_GTK
+#if GTK_CHECK_VERSION(4, 0, 0)
+    GActionMap *action_map;
+#elif UI_GTK2 || UI_GTK3
     GtkAccelGroup *accel_group;
+#endif 
 #endif
+
+
     
     ui_callback   close_callback;
     void          *close_data;
@@ -100,19 +110,24 @@
 
 void uic_init_global_context(void);
 
-UiContext* uic_context(UiObject *toplevel, UcxMempool *mp);
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp);
 UiContext* uic_root_context(UiContext *ctx);
 void uic_context_set_document(UiContext *ctx, void *document); // deprecated
 void uic_context_detach_document(UiContext *ctx); // deprecated
 
+void uic_context_prepare_close(UiContext *ctx);
+
 void uic_context_attach_document(UiContext *ctx, void *document);
 void uic_context_detach_document2(UiContext *ctx, void *document);
 void uic_context_detach_all(UiContext *ctx);
 
 UiVar* uic_get_var(UiContext *ctx, const char *name);
 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);
 
+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_save_var2(UiVar *var);
 void uic_unbind_var(UiVar *var);
@@ -121,9 +136,11 @@
 
 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, UcxList *groups);
-
+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);
 
 #ifdef	__cplusplus
 }
--- a/ui/common/document.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/document.c	Sat Jan 04 16:38:48 2025 +0100
@@ -32,10 +32,16 @@
 
 #include "document.h"
 
-static UcxMap *documents;
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+
+
+static CxMap *documents;
 
 void uic_docmgr_init() {
-    documents = ucx_map_new(32);
+    if (!documents) {
+        documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    }
 }
 
 void ui_set_document(UiObject *obj, void *document) {
@@ -77,25 +83,40 @@
 }
 
 void* ui_document_new(size_t size) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiContext *ctx = ucx_mempool_calloc(mp, 1, sizeof(UiContext));
+    CxMempool *mp = cxMempoolCreate(256, NULL);
+    const CxAllocator *a = mp->allocator;
+    UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
+    ctx->mp = mp;
     ctx->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document2;
-    ctx->mempool = mp;
-    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    ctx->allocator = a;
+    ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
     
-    void *document = ucx_mempool_calloc(mp, 1, size);
-    ucx_map_put(documents, ucx_key(&document, sizeof(void*)), ctx);
+    void *document = cxCalloc(a, size, 1);
+    cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
     return document;
 }
 
 void ui_document_destroy(void *doc) {
-    // TODO
+    UiContext *ctx = ui_document_context(doc);
+    if(ctx) {
+        UiEvent ev;
+        ev.window = NULL;
+        ev.document = doc;
+        ev.obj = NULL;
+        ev.eventdata = NULL;
+        ev.intval = 0;
+
+        if(ctx->close_callback) {
+            ctx->close_callback(&ev, ctx->close_data);
+        }
+        cxMempoolDestroy(ctx->mp);
+    }
 }
 
 UiContext* ui_document_context(void *doc) {
     if(doc) {
-        return ucx_map_get(documents, ucx_key(&doc, sizeof(void*)));
+        return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
     } else {
         return NULL;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/menu.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,331 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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"
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+
+static UiMenuBuilder *current_builder;
+static UiMenuBuilder global_builder;
+
+static int menu_item_counter = 0;
+
+void uic_menu_init(void) {
+    global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = &global_builder;
+}
+
+static void add_menu(UiMenu *menu) {
+    cx_linked_list_add(
+            (void**)&current_builder->menus_begin,
+            (void**)&current_builder->menus_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            menu);
+}
+
+static void add_item(UiMenuItemI *item) {
+    UiMenu *menu = cxListAt(current_builder->current, 0);
+    cx_linked_list_add(
+            (void**)&menu->items_begin,
+            (void**)&menu->items_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            item);
+}
+
+static void mitem_set_id(UiMenuItemI *item) {
+    snprintf(item->id, 8, "%x", menu_item_counter++);
+}
+
+static char* nl_strdup(const char* s) {
+    return s ? strdup(s) : NULL;
+}
+
+int* uic_copy_groups(const int* groups, size_t *ngroups) {
+    *ngroups = 0;
+    if (!groups) {
+        return NULL;
+    }
+
+    size_t n;
+    for (n = 0; groups[n] > -1; n++) { }
+
+    if (ngroups > 0) {
+        int* newarray = calloc(n+1, sizeof(int));
+        memcpy(newarray, groups, n * sizeof(int));
+        newarray[n] = -1;
+        *ngroups = n;
+        return newarray;
+    }
+    return NULL;
+}
+
+void ui_menu_create(const char *label) {   
+    // create menu
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    mitem_set_id(&menu->item);
+    menu->item.prev = NULL;
+    menu->item.next = NULL;
+    menu->item.type = UI_MENU;
+    
+    menu->label        = label;
+    menu->items_begin  = NULL;
+    menu->items_end    = NULL;
+    menu->parent       = NULL;    
+
+    menu->end = 0;
+
+    if (cxListSize(current_builder->current) == 0) {
+        add_menu(menu);
+    }
+    else {
+        add_item((UiMenuItemI*)menu);
+    }
+    uic_add_menu_to_stack(menu);
+}
+
+UIEXPORT void ui_menu_end(void) {
+    cxListRemove(current_builder->current, 0);
+    if(cxListSize(current_builder->current) == 0) {
+        current_builder = &global_builder;
+    }
+}
+
+
+
+void ui_menuitem_create(UiMenuItemArgs args) {
+    UiMenuItem* item = malloc(sizeof(UiMenuItem));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->userdata = args.onclickdata;
+    item->callback = args.onclick;
+    item->groups = uic_copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menuseparator() {
+    UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->id[0] = 0;
+    item->prev = NULL;
+    item->next = NULL;
+    item->type = UI_MENU_SEPARATOR;
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) {
+    UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_CHECK_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->varname = nl_strdup(args.varname);
+    item->userdata = args.onchangedata;
+    item->callback = args.onchange;
+    item->groups = uic_copy_groups(args.groups, &item->ngroups);
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitem_create(UiMenuToggleItemArgs args) {
+    UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_RADIO_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->varname = nl_strdup(args.varname);
+    item->userdata = args.onchangedata;
+    item->callback = args.onchange;
+    item->groups = uic_copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_itemlist_create(UiMenuItemListArgs args) {
+    UiMenuItemList*item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM_LIST;
+    item->getvalue = args.getvalue;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+    item->addseparator = args.addseparator;
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_checkitemlist_create(UiMenuItemListArgs args) {
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_CHECKITEM_LIST;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitemlist_create(UiMenuItemListArgs args) {
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_RADIOITEM_LIST;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+
+    add_item((UiMenuItemI*)item);
+}
+
+
+void uic_add_menu_to_stack(UiMenu* menu) {
+    cxListInsert(current_builder->current, 0, menu);
+}
+
+UiMenu* uic_get_menu_list(void) {
+    return current_builder->menus_begin;
+}
+
+UIEXPORT void ui_menu_close(void) {
+    UiMenu* menu = cxListAt(current_builder->current, 0);
+    menu->end = 1;
+}
+
+UIEXPORT int ui_menu_is_open(void) {
+    UiMenu* menu = cxListAt(current_builder->current, 0);
+    if (menu->end) {
+        ui_menu_end();
+        return 0;
+    }
+    return 1;
+}
+
+
+void ui_contextmenu_builder(UiMenuBuilder **out_builder) {
+    UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder));
+    builder->menus_begin = NULL;
+    builder->menus_end = NULL;
+    builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = builder;
+    *out_builder = builder;
+    
+    ui_menu_create(NULL);
+}
+
+
+
+static void free_menuitem(UiMenuItemI *item) {
+    switch(item->type) {
+        default: break;
+        case UI_MENU: {
+            UiMenu *menu = (UiMenu*)item;
+            UiMenuItemI *m = menu->items_begin;
+            while(m) {
+                UiMenuItemI *next = m->next;
+                free_menuitem(m);
+                m = next;
+            }
+            break;
+        }
+        case UI_MENU_ITEM: {
+            UiMenuItem *i = (UiMenuItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            break;
+        }
+        case UI_MENU_CHECK_ITEM: {
+            UiMenuCheckItem *i = (UiMenuCheckItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            free(i->varname);
+            break;
+        }
+        case UI_MENU_RADIO_ITEM: {
+            UiMenuRadioItem *i = (UiMenuRadioItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            //free(i->varname);
+            break;
+        }
+        case UI_MENU_ITEM_LIST: {
+            break;
+        }
+        case UI_MENU_CHECKITEM_LIST: {
+            break;
+        }
+        case UI_MENU_RADIOITEM_LIST: {
+            break;
+        }
+    }
+    
+    free(item);
+}
+
+void ui_menubuilder_free(UiMenuBuilder *builder) {
+    UiMenuItemI *m = &builder->menus_begin->item;
+    while(m) {
+        UiMenuItemI *next = m->next;
+        free_menuitem(m);
+        m = next;
+    }
+    cxListDestroy(builder->current);
+    free(builder);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/menu.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,141 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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_MENU_H
+#define UIC_MENU_H
+
+#include "../ui/menu.h"
+
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiMenuItemI      UiMenuItemI;
+typedef struct UiMenu           UiMenu;
+typedef struct UiMenuItem       UiMenuItem;
+typedef struct UiMenuCheckItem  UiMenuCheckItem;
+typedef struct UiMenuRadioItem  UiMenuRadioItem;
+typedef struct UiMenuItemList   UiMenuItemList;
+    
+enum UiMenuItemType {
+    UI_MENU = 0,
+    UI_MENU_ITEM,
+    UI_MENU_CHECK_ITEM,
+    UI_MENU_RADIO_ITEM,
+    UI_MENU_ITEM_LIST,
+    UI_MENU_CHECKITEM_LIST,
+    UI_MENU_RADIOITEM_LIST,
+    UI_MENU_SEPARATOR
+};
+
+typedef enum UiMenuItemType UiMenuItemType;
+    
+struct UiMenuItemI {
+    UiMenuItemI    *prev;
+    UiMenuItemI    *next;
+    UiMenuItemType type;
+    char           id[8];
+};
+
+struct UiMenu {
+    UiMenuItemI    item;
+    const char     *label;
+    UiMenuItemI    *items_begin;
+    UiMenuItemI    *items_end;
+    UiMenu         *parent;
+    int            end;
+};
+
+struct UiMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuCheckItem {
+    UiMenuItemI    item;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    char           *varname;
+    ui_callback    callback;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuRadioItem {
+    UiMenuItemI    item;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    char           *varname;
+    ui_callback    callback;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuItemList {
+    UiMenuItemI     item;
+    ui_getvaluefunc getvalue;
+    ui_callback     callback;
+    void            *userdata;
+    char            *varname;
+    UiBool          addseparator;
+};
+
+
+
+struct UiMenuBuilder {
+    UiMenu *menus_begin;
+    UiMenu *menus_end;
+    CxList *current;
+};
+
+void uic_menu_init(void);
+
+UiMenu* uic_get_menu_list(void);
+
+void uic_add_menu_to_stack(UiMenu* menu);
+
+int* uic_copy_groups(const int* groups, size_t *ngroups);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_MENU_H */
+
--- a/ui/common/object.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/object.c	Sat Jan 04 16:38:48 2025 +0100
@@ -32,6 +32,8 @@
 #include "object.h"
 #include "context.h"
 
+#include "../ui/container.h"
+
 void ui_end(UiObject *obj) {
     if(!obj->next) {
         return;
@@ -49,11 +51,51 @@
     }
 }
 
+void ui_end_new(UiObject *obj) {
+    if(!obj->container_end) {
+        return;
+    }
+    UiContainerX *rm = obj->container_end;
+    uic_object_pop_container(obj);
+    ui_free(obj->ctx, rm);
+}
+
+void ui_object_ref(UiObject *obj) {
+    obj->ref++;
+}
+
+int ui_object_unref(UiObject *obj) {
+    // it is possible to have 0 references, in case
+    // a window was created but ui_show was never called
+    if(obj->ref == 0 || --obj->ref == 0) {
+        if(obj->destroy) {
+            obj->destroy(obj);
+        }
+        uic_object_destroy(obj);
+        return 0;
+    }
+    return 1;
+}
+
+void uic_object_destroy(UiObject *obj) {
+    if(obj->ctx->close_callback) {
+        UiEvent ev;
+        ev.window = obj->window;
+        ev.document = obj->ctx->document;
+        ev.obj = obj;
+        ev.eventdata = NULL;
+        ev.intval = 0;
+        obj->ctx->close_callback(&ev, obj->ctx->close_data);
+    }
+    cxMempoolDestroy(obj->ctx->mp);
+}
 
 UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
-    UiContext *ctx = toplevel->ctx;
-    
-    UiObject *newobj = ucx_mempool_calloc(ctx->mempool, 1, sizeof(UiObject));
+    return uic_ctx_object_new(toplevel->ctx, widget);
+}
+
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) {
+    UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject));
     newobj->ctx = ctx;
     newobj->widget = widget;
     
@@ -79,3 +121,23 @@
 UiContainer* uic_get_current_container(UiObject *obj) {
     return uic_current_obj(obj)->container;
 }
+
+void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer) {
+    newcontainer->prev = toplevel->container_end;
+    if(toplevel->container_end) {
+        toplevel->container_end->next = newcontainer;
+        toplevel->container_end = newcontainer;
+    } else {
+        toplevel->container_begin = newcontainer;
+        toplevel->container_end = newcontainer;
+    }
+}
+
+void uic_object_pop_container(UiObject *toplevel) {
+    toplevel->container_end = toplevel->container_end->prev;
+    if(toplevel->container_end) {
+        toplevel->container_end->next = NULL;
+    } else {
+        toplevel->container_begin = NULL;
+    }
+}
--- a/ui/common/object.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/object.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,11 +35,18 @@
 extern "C" {
 #endif
 
+void uic_object_destroy(UiObject *obj);
+    
 UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget);
 void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
 UiObject* uic_current_obj(UiObject *toplevel);
 
-UiContainer* uic_get_current_container(UiObject *obj);
+UiContainer* uic_get_current_container(UiObject *obj); // deprecated
+
+void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer);
+void uic_object_pop_container(UiObject *toplevel);
+
 
 
 #ifdef	__cplusplus
--- a/ui/common/objs.mk	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/objs.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -33,7 +33,13 @@
 COMMON_OBJ += document.o
 COMMON_OBJ += object.o
 COMMON_OBJ += types.o
+COMMON_OBJ += menu.o
 COMMON_OBJ += properties.o
+COMMON_OBJ += menu.o
+COMMON_OBJ += toolbar.o
+COMMON_OBJ += ucx_properties.o
+COMMON_OBJ += threadpool.o
+COMMON_OBJ += condvar.o
 
 TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
 TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
--- a/ui/common/properties.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/properties.c	Sat Jan 04 16:38:48 2025 +0100
@@ -32,13 +32,18 @@
 #include <sys/stat.h>
 #include <errno.h>
 
+#include "../ui/toolkit.h"
+
+
 #include "properties.h"
-#include <ucx/string.h>
-#include <ucx/buffer.h>
-#include <ucx/properties.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
 
-static UiProperties *application_properties;
-static UiProperties *language;
+#include <cx/hash_map.h>
+#include "ucx_properties.h"
+
+static CxMap *application_properties;
+static CxMap *language;
 
 #ifndef UI_COCOA
 
@@ -47,53 +52,66 @@
 
 #endif
 
-char* ui_getappdir() {
+
+char* ui_getappdir(void) {
     if(ui_appname() == NULL) {
         return NULL;
     }
-    
+
     return ui_configfile(NULL);
 }
 
+#ifndef _WIN32
+#define UI_PATH_SEPARATOR '/'
+#define UI_ENV_HOME "HOME"
+#else
+#define UI_PATH_SEPARATOR '\\'
+#define UI_ENV_HOME "USERPROFILE"
+#endif
+
 char* ui_configfile(char *name) {
-    char *appname = ui_appname();
+    const char *appname = ui_appname();
     if(!appname) {
         return NULL;
     }
     
-    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     
     // add base dir
-    char *homeenv = getenv("HOME");
+    char *homeenv = getenv(UI_ENV_HOME);
     if(homeenv == NULL) {
-        ucx_buffer_free(buf);
+        cxBufferDestroy(&buf);
         return NULL;
     }
-    sstr_t home = sstr(homeenv);
+    cxstring home = cx_str(homeenv);
     
-    ucx_buffer_write(home.ptr, 1, home.length, buf);
-    if(home.ptr[home.length-1] != '/') {
-        ucx_buffer_putc(buf, '/');
+    cxBufferWrite(home.ptr, 1, home.length, &buf);
+    if(home.ptr[home.length-1] != UI_PATH_SEPARATOR) {
+        cxBufferPut(&buf, UI_PATH_SEPARATOR);
     }
     
 #ifdef UI_COCOA
     // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/
-    ucx_buffer_puts(buf, "Library/Application Support/");
+    cxBufferPutString(&buf, "Library/Application Support/");
+#elif defined(_WIN32)
+    // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/
+    cxBufferPutString(&buf, "AppData\\Local\\");
 #else
     // app dir is $HOME/.$APPNAME/
-    ucx_buffer_putc(buf, '.');
+    cxBufferPut(&buf, '.');
 #endif
-    ucx_buffer_puts(buf, appname);
-    ucx_buffer_putc(buf, '/');
+    cxBufferPutString(&buf, appname);
+    cxBufferPut(&buf, UI_PATH_SEPARATOR);
     
     // add file name
     if(name) {
-        ucx_buffer_puts(buf, name);
+        cxBufferPutString(&buf, name);
     }
     
-    char *path = buf->space;
-    free(buf);
-    return path;  
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
 }
 
 static int ui_mkdir(char *path) {
@@ -105,7 +123,7 @@
 } 
 
 void uic_load_app_properties() {
-    application_properties = ucx_map_new(128);
+    application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128);
     
     if(!ui_appname()) {
         // applications without name cannot load app properties
@@ -144,58 +162,67 @@
     free(path);
 }
 
-void uic_store_app_properties() {
+int uic_store_app_properties() {
     char *path = ui_configfile("application.properties");
     if(!path) {
-        return;
+        return 1;
     }
     
     FILE *file = fopen(path, "w");
     if(!file) {
         fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path);
         free(path);
-        return;
+        return 1;
     }
     
+    int ret = 0;
     if(ucx_properties_store(application_properties, file)) {
         fprintf(stderr, "Ui Error: Cannot store application properties.\n");
+        ret = 1;
     }
     
     fclose(file);
     free(path);
+    
+    return ret;
 }
 
 
-char* ui_get_property(char *name) {
-    return ucx_map_cstr_get(application_properties, name);
+const char* ui_get_property(const char *name) {
+    return cxMapGet(application_properties, name);
 }
 
-void ui_set_property(char *name, char *value) {
-    ucx_map_cstr_put(application_properties, name, value);
+void ui_set_property(const char *name, const char *value) {
+    cxMapPut(application_properties, name, (char*)value);
 }
 
-void ui_set_default_property(char *name, char *value) {
-    char *v = ucx_map_cstr_get(application_properties, name);
+const char* ui_set_default_property(const char *name, const char *value) {
+    const char *v = cxMapGet(application_properties, name);
     if(!v) {
-        ucx_map_cstr_put(application_properties, name, value);
+        cxMapPut(application_properties, name, (char*)value);
+        v = value;
     }
+    return v;
 }
 
+int ui_properties_store(void) {
+    return uic_store_app_properties();
+}
 
 
 static char* uic_concat_path(const char *base, const char *p, const char *ext) {
     size_t baselen = strlen(base);
     
-    UcxBuffer *buf = ucx_buffer_new(NULL, 32, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     if(baselen > 0) {
-        ucx_buffer_write(base, 1, baselen, buf);
+        cxBufferWrite(base, 1, baselen, buf);
         if(base[baselen - 1] != '/') {
-            ucx_buffer_putc(buf, '/');
+            cxBufferPut(buf, '/');
         }
     }
-    ucx_buffer_write(p, 1, strlen(p), buf);
+    cxBufferWrite(p, 1, strlen(p), buf);
     if(ext) {
-        ucx_buffer_write(ext, 1, strlen(ext), buf);
+        cxBufferWrite(ext, 1, strlen(ext), buf);
     }
     
     char *str = buf->space;
@@ -263,7 +290,7 @@
 #endif
 
 int uic_load_language_file(const char *path) {
-    UcxMap *lang = ucx_map_new(256);
+    CxMap *lang = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256);
     
     FILE *file = fopen(path, "r");
     if(!file) {
@@ -276,7 +303,7 @@
     
     fclose(file);
     
-    ucx_map_rehash(lang);
+    cxMapRehash(lang);
     
     language = lang;
     
@@ -292,6 +319,5 @@
     if(!language) {
         return NULL;
     }
-    return ucx_map_cstr_get(language, name);
+    return cxMapGet(language, name);
 }
-
--- a/ui/common/properties.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/properties.h	Sat Jan 04 16:38:48 2025 +0100
@@ -30,8 +30,8 @@
 #define	UIC_PROPERTIES_H
 
 #include "../ui/properties.h"
-#include <ucx/map.h>
-#include <ucx/list.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
@@ -44,7 +44,7 @@
 #endif
 
 void uic_load_app_properties();
-void uic_store_app_properties();
+int uic_store_app_properties();
 
 int uic_load_language_file(const char *path);
 char* uic_get_image_path(const char *imgfilename);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/threadpool.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,195 @@
+/*
+ * 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 "threadpool.h"
+#include "context.h"
+
+#ifndef _WIN32
+
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+static threadpool_job kill_job;
+
+UiThreadpool* threadpool_new(int min, int max) {
+    UiThreadpool *pool = malloc(sizeof(UiThreadpool));
+    pool->queue = NULL;
+    pool->queue_len = 0;
+    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) {
+    /* create pool threads */
+    for(int i=0;i<pool->min_threads;i++) {
+        pthread_t t;
+        if (pthread_create(&t, NULL, threadpool_func, pool) != 0) {
+            fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno));
+            return 1;
+        }
+    }
+    return 0;
+}
+
+void* threadpool_func(void *data) {
+    UiThreadpool *pool = (UiThreadpool*)data;
+    
+    for(;;) {
+        threadpool_job *job = threadpool_get_job(pool);
+        if(job == &kill_job) {
+            break;
+        }
+        
+        job->callback(job->data);
+
+        free(job);
+    }
+    return NULL;
+}
+
+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);
+    return job;
+}
+
+void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) {
+    // TODO: handle errors
+    
+    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);
+}
+
+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) {
+    UiThreadpool *pool = threadpool_new(nthreads, nthreads);
+    threadpool_start(pool); // TODO: check return value
+    return pool;
+}
+
+void ui_threadpool_destroy(UiThreadpool* pool) {
+    
+}
+
+static int ui_threadpool_job_finish(void *data) {
+    UiJob *job = data;
+    UiEvent event;
+    event.obj = job->obj;
+    event.window = job->obj->window;
+    event.document = job->obj->ctx->document;
+    event.intval = 0;
+    event.eventdata = NULL;
+    job->finish_callback(&event, job->finish_data);
+    free(job);
+    return 0;
+}
+
+static void* ui_threadpool_job_func(void *data) {
+    UiJob *job = data;
+    if (!job->job_func(job->job_data) && job->finish_callback) {
+        ui_call_mainthread(ui_threadpool_job_finish, job);
+    } else {
+        free(job);
+    }
+    return NULL;
+}
+
+void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {
+    UiJob* job = malloc(sizeof(UiJob));
+    job->obj = obj;
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = f;
+    job->finish_data = fd;
+    threadpool_run(pool, ui_threadpool_job_func, job);
+}
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/threadpool.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#ifndef UIC_THREADPOOL_H
+#define UIC_THREADPOOL_H
+
+#include "../ui/toolkit.h"
+
+#ifndef _WIN32
+#include <pthread.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+    
+typedef struct UiJob {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+} UiJob;
+    
+    
+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;
+};
+
+struct _threadpool_job {
+    job_callback_f      callback;
+    void                *data;
+};
+
+struct _pool_queue {
+    threadpool_job   *job;
+    pool_queue_t     *next;
+};
+
+UiThreadpool* threadpool_new(int min, int max);
+int threadpool_start(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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_THREADPOOL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/toolbar.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,148 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "toolbar.h"
+#include "menu.h"
+
+#include <string.h>
+
+static CxMap* toolbar_items;
+
+static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right
+
+static UiToolbarMenuItem* ui_appmenu;
+
+
+void uic_toolbar_init(void) {
+    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+}
+
+static char* nl_strdup(const char* str) {
+    return str ? strdup(str) : NULL;
+}
+
+static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args, size_t *ngroups) {
+    UiToolbarItemArgs newargs;
+    newargs.label = nl_strdup(args.label);
+    newargs.stockid = nl_strdup(args.stockid);
+    newargs.icon = nl_strdup(args.icon);
+    newargs.onclick = args.onclick;
+    newargs.onclickdata = args.onclickdata;
+    newargs.groups = uic_copy_groups(args.groups, ngroups);
+    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);
+    cxMapPut(toolbar_items, name, item);
+}
+
+
+static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args, size_t *ngroups) {
+    UiToolbarToggleItemArgs newargs;
+    newargs.label = nl_strdup(args.label);
+    newargs.stockid = nl_strdup(args.stockid);
+    newargs.icon = nl_strdup(args.icon);
+    newargs.varname = nl_strdup(args.varname);
+    newargs.onchange = args.onchange;
+    newargs.onchangedata = args.onchangedata;
+    newargs.groups = uic_copy_groups(args.groups, ngroups);
+    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);
+    cxMapPut(toolbar_items, name, item);
+}
+
+static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) {
+    UiToolbarMenuArgs newargs;
+    newargs.label = nl_strdup(args.label);
+    newargs.stockid = nl_strdup(args.stockid);
+    newargs.icon = nl_strdup(args.icon);
+    return newargs;
+}
+
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) {
+    UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem));
+    item->item.type = UI_TOOLBAR_MENU;
+    memset(&item->menu, 0, sizeof(UiMenu));
+    item->args = menuargs_copy(args);
+
+    item->end = 0;
+
+    if (!name) {
+        // special appmenu
+        ui_appmenu = item;
+    } else {
+        // toplevel menu
+        cxMapPut(toolbar_items, name, item);
+    }
+
+    uic_add_menu_to_stack(&item->menu);
+}
+
+
+CxMap* uic_get_toolbar_items(void) {
+    return toolbar_items;
+}
+
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {
+    if (pos >= 0 && pos < 3) {
+        return toolbar_defaults[pos];
+    }
+    return NULL;
+}
+
+void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) {
+    char* cp = strdup(name);
+    if (pos >= 0 && pos < 3) {
+        cxListAdd(toolbar_defaults[pos], cp);
+    } else {
+        // TODO: error
+    }
+}
+
+UiBool uic_toolbar_isenabled(void) {
+    return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0;
+}
+
+UiToolbarItemI* uic_toolbar_get_item(const char* name) {
+    return cxMapGet(toolbar_items, name);
+}
+
+UiToolbarMenuItem* uic_get_appmenu(void) {
+    return ui_appmenu;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/toolbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,98 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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_TOOLBAR_H
+#define UIC_TOOLBAR_H
+
+#include "../ui/toolbar.h"
+
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+
+#include "menu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolbarItemI      UiToolbarItemI;
+
+typedef struct UiToolbarItem       UiToolbarItem;
+typedef struct UiToolbarToggleItem UiToolbarToggleItem;
+
+typedef struct UiToolbarMenuItem   UiToolbarMenuItem;
+
+enum UiToolbarItemType {
+    UI_TOOLBAR_ITEM = 0,
+    UI_TOOLBAR_TOGGLEITEM,
+    UI_TOOLBAR_MENU
+};
+
+typedef enum UiToolbarItemType UiToolbarItemType;
+
+struct UiToolbarItemI {
+    UiToolbarItemType type;
+};
+
+struct UiToolbarItem {
+    UiToolbarItemI item;
+    UiToolbarItemArgs args;
+    size_t ngroups;
+};
+
+struct UiToolbarToggleItem {
+    UiToolbarItemI item;
+    UiToolbarToggleItemArgs args;
+    size_t ngroups;
+};
+
+struct UiToolbarMenuItem {
+    UiToolbarItemI item;
+    UiMenu menu;
+    UiToolbarMenuArgs args;
+    int end;
+};
+
+
+void uic_toolbar_init(void);
+
+CxMap* uic_get_toolbar_items(void);
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos);
+
+UiBool uic_toolbar_isenabled(void);
+
+UiToolbarItemI* uic_toolbar_get_item(const char* name);
+
+UiToolbarMenuItem* uic_get_appmenu(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TOOLBAR_H */
+
--- a/ui/common/types.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/types.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,11 +31,14 @@
 #include <string.h>
 #include <stdarg.h>
 
-#include <ucx/list.h>
+#include <cx/list.h>
+#include <cx/array_list.h>
 #include "../ui/tree.h"
 #include "types.h"
 #include "context.h"
 
+
+
 UiObserver* ui_observer_new(ui_callback f, void *data) {
     UiObserver *observer = malloc(sizeof(UiObserver));
     observer->callback = f;
@@ -99,10 +102,11 @@
     list->count = ui_list_count;
     list->observers = NULL;
     
-    list->data = NULL;
+    list->data = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 32);
     list->iter = NULL;
     
     list->update = NULL;
+    list->getselection = NULL;
     list->obj = NULL;
     
     if(name) {
@@ -113,54 +117,53 @@
 }
 
 void ui_list_free(UiList *list) {
-    ucx_list_free(list->data);
+    cxListDestroy(list->data);
     free(list);
 }
 
 void* ui_list_first(UiList *list) {
-    UcxList *elm = list->data;
-    list->iter = elm;
-    return elm ? elm->data : NULL;
+    list->iter = (void*)(intptr_t)0;
+    return cxListAt(list->data, 0);
 }
 
 void* ui_list_next(UiList *list) {
-    UcxList *elm = list->iter;
+    intptr_t iter = (intptr_t)list->iter;
+    iter++;
+    void *elm = cxListAt(list->data, iter);
     if(elm) {
-        elm = elm->next;
-        if(elm) {
-            list->iter = elm;
-            return elm->data;
-        }
+        list->iter = (void*)iter;
     }
-    return NULL;
+    return elm;
 }
 
 void* ui_list_get(UiList *list, int i) {
-    UcxList *elm = ucx_list_get(list->data, i);
-    if(elm) {
-        list->iter = elm;
-        return elm->data;
-    } else {
-        return NULL;
-    }
+    return cxListAt(list->data, i);
 }
 
 int ui_list_count(UiList *list) {
-    UcxList *elm = list->data;
-    return (int)ucx_list_size(elm);
+    return cxListSize(list->data);
 }
 
 void ui_list_append(UiList *list, void *data) {
-    list->data = ucx_list_append(list->data, data);
+    cxListAdd(list->data, data);
 }
 
 void ui_list_prepend(UiList *list, void *data) {
-    list->data = ucx_list_prepend(list->data, data);
+    cxListInsert(list->data, 0, data);
+}
+
+void ui_list_remove(UiList *list, int i) {
+    cxListRemove(list->data, i);
 }
 
 void ui_list_clear(UiList *list) {
-    ucx_list_free(list->data);
-    list->data = NULL;
+    cxListClear(list->data);
+}
+
+UIEXPORT void ui_list_update(UiList *list) {
+    if(list->update) {
+        list->update(list, 0);
+    }
 }
 
 void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
@@ -183,42 +186,60 @@
     va_list ap;
     va_start(ap, ctx);
     
-    UcxList *cols = NULL;
+    CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32);
     int type;
     while((type = va_arg(ap, int)) != -1) {
         char *name = va_arg(ap, char*);
         
-        UiColumn *column = malloc(sizeof(UiColumn));
-        column->type = type;
-        column->name = name;
+        UiColumn column;
+        column.type = type;
+        column.name = name;
         
-        cols = ucx_list_append(cols, column);
+        cxListAdd(cols, &column);
     }
     
     va_end(ap);
     
-    size_t len = ucx_list_size(cols);
+    size_t len = cxListSize(cols);
     info->columns = len;
     info->types = ui_calloc(ctx, len, sizeof(UiModelType));
     info->titles = ui_calloc(ctx, len, sizeof(char*));
+    info->columnsize = ui_calloc(ctx, len, sizeof(int));
     
     int i = 0;
-    UCX_FOREACH(elm, cols) {
-        UiColumn *c = elm->data;
+    CxIterator iter = cxListIterator(cols);
+    cx_foreach(UiColumn*, c, iter) {
         info->types[i] = c->type;
         info->titles[i] = c->name;
-        free(c);
         i++;
     }
-    ucx_list_free(cols);
+    cxListDestroy(cols);
     
     return info;
 }
 
+UiModel* ui_model_copy(UiContext *ctx, UiModel* model) {
+    const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+
+    UiModel* newmodel = cxMalloc(a, sizeof(UiModel));
+    *newmodel = *model;
+
+    newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType));
+    memcpy(newmodel->types, model->types, model->columns);
+
+    newmodel->titles = cxCalloc(a, model->columns, sizeof(char*));
+    for (int i = 0; i < model->columns; i++) {
+        newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL;
+    }
+
+    return newmodel;
+}
+
 void ui_model_free(UiContext *ctx, UiModel *mi) {
-    ucx_mempool_free(ctx->mempool, mi->types);
-    ucx_mempool_free(ctx->mempool, mi->titles);
-    ucx_mempool_free(ctx->mempool, mi);
+    const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+    cxFree(a, mi->types);
+    cxFree(a, mi->titles);
+    cxFree(a, mi);
 }
 
 // types
@@ -269,6 +290,75 @@
     return r;
 }
 
+UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name) {
+    UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric));
+    memset(g, 0, sizeof(UiGeneric));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_GENERIC, g);
+    }
+    return g;
+}
+
+
+void ui_int_set(UiInteger* i, int64_t value) {
+    if (i && i->set) {
+        i->set(i, value);
+    }
+}
+
+int64_t ui_int_get(UiInteger* i) {
+    if (i) {
+        return i->get ? i->get(i) : i->value;
+    } else {
+        return 0;
+    }
+}
+
+void ui_double_set(UiDouble* d, double value) {
+    if (d && d->set) {
+        d->set(d, value);
+    }
+}
+
+double ui_double_get(UiDouble* d) {
+    if (d) {
+        return d->get ? d->get(d) : d->value;
+    }
+    else {
+        return 0;
+    }
+}
+
+void ui_string_set(UiString* s, const char* value) {
+    if (s && s->set) {
+        s->set(s, value);
+    }
+}
+
+char* ui_string_get(UiString* s) {
+    if (s) {
+        return s->get ? s->get(s) : s->value.ptr;
+    }
+    else {
+        return 0;
+    }
+}
+
+void ui_text_set(UiText* s, const char* value) {
+    if (s && s->set) {
+        s->set(s, value);
+    }
+}
+
+char* ui_text_get(UiText* s) {
+    if (s) {
+        return s->get ? s->get(s) : s->value.ptr;
+    }
+    else {
+        return 0;
+    }
+}
+
 
 // private functions
 void uic_int_copy(UiInteger *from, UiInteger *to) {
@@ -317,6 +407,12 @@
     to->obj = from->obj;
 }
 
+void uic_generic_copy(UiGeneric *from, UiGeneric *to) {
+    to->get = from->get;
+    to->get_type = from->get_type;
+    to->set = from->set;
+    to->obj = from->obj;
+}
 
 void uic_int_save(UiInteger *i) {
     if(!i->obj) return;
@@ -344,6 +440,11 @@
     r->get(r);
 }
 
+void uic_generic_save(UiGeneric *g) {
+    if(!g->obj) return;
+    g->get(g);
+}
+
 
 void uic_int_unbind(UiInteger *i) {
     i->get = NULL;
@@ -389,3 +490,90 @@
     l->update = NULL;
     l->obj = NULL;
 }
+
+void uic_generic_unbind(UiGeneric *g) {
+    g->get = NULL;
+    g->get_type = NULL;
+    g->set = NULL;
+    g->obj = NULL;
+}
+
+
+UIEXPORT UiListSelection ui_list_getselection(UiList *list) {
+    if (list->getselection) {
+        return list->getselection(list);
+    }
+    return (UiListSelection){ 0, NULL };
+}
+
+UIEXPORT void ui_list_setselection(UiList *list, int index) {
+    if (list->setselection && index >= 0) {
+        UiListSelection sel;
+        sel.count = 1;
+        sel.rows = &index;
+        list->setselection(list, sel);
+    }
+}
+
+UIEXPORT void ui_listselection_free(UiListSelection selection) {
+    if (selection.rows) {
+        free(selection.rows);
+    }
+}
+
+UIEXPORT UiStr ui_str(char *cstr) {
+    return (UiStr) { cstr, NULL };
+}
+
+UIEXPORT UiStr ui_str_free(char *str, void (*freefunc)(void *v)) {
+    return (UiStr) { str, freefunc };
+}
+
+
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list) {
+    char **newlist = calloc(sizeof(char*), list.nfiles);
+    for (int i = 0; i < list.nfiles; i++) {
+        newlist[i] = strdup(list.files[i]);
+    }
+    return (UiFileList) { newlist, list.nfiles };
+}
+
+UIEXPORT void ui_filelist_free(UiFileList list) {
+    for (int i = 0; i < list.nfiles; i++) {
+        free(list.files[i]);
+    }
+    free(list.files);
+}
+
+
+typedef struct UiObserverDestructor {
+    UiList *list;
+    UiObserver *observer;
+} UiObserverDestructor;
+
+static void observer_destructor(UiObserverDestructor *destr) {
+    UiObserver *remove_obs = destr->observer;
+    UiObserver *obs = destr->list->observers;
+    UiObserver *prev = NULL;
+    while(obs) {
+        if(obs == remove_obs) {
+            if(prev) {
+                prev->next = obs->next;
+            } else {
+                destr->list->observers = obs->next;
+            }
+            break;
+        }
+        prev = obs;
+        obs = obs->next;
+    }
+    free(remove_obs);
+}
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer) {
+    CxMempool *mp = ctx->mp;
+    UiObserverDestructor *destr = cxMalloc(mp->allocator, sizeof(UiObserverDestructor));
+    destr->list = list;
+    destr->observer = observer;
+    cxMempoolSetDestructor(destr, (cx_destructor_func)observer_destructor);
+}
--- a/ui/common/types.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/common/types.h	Sat Jan 04 16:38:48 2025 +0100
@@ -42,12 +42,14 @@
 void uic_text_copy(UiText *from, UiText *to);
 void uic_range_copy(UiRange *from, UiRange *to);
 void uic_list_copy(UiList *from, UiList *to);
+void uic_generic_copy(UiGeneric *from, UiGeneric *to);
 
 void uic_int_save(UiInteger *i);
 void uic_double_save(UiDouble *d);
 void uic_string_save(UiString *s);
 void uic_text_save(UiText *t);
 void uic_range_save(UiRange *r);
+void uic_generic_save(UiGeneric *g);
 
 void uic_int_unbind(UiInteger *i);
 void uic_double_unbind(UiDouble *d);
@@ -55,7 +57,10 @@
 void uic_text_unbind(UiText *t);
 void uic_range_unbind(UiRange *r);
 void uic_list_unbind(UiList *l);
-    
+void uic_generic_unbind(UiGeneric *g);
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer);
+  
 #ifdef	__cplusplus
 }
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,263 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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 "ucx_properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxProperties *ucx_properties_new() {
+    UcxProperties *parser = (UcxProperties*)malloc(
+            sizeof(UcxProperties));
+    if(!parser) {
+        return NULL;
+    }
+    
+    parser->buffer = NULL;
+    parser->buflen = 0;
+    parser->pos = 0;
+    parser->tmp = NULL;
+    parser->tmplen = 0;
+    parser->tmpcap = 0;
+    parser->error = 0;
+    parser->delimiter = '=';
+    parser->comment1 = '#';
+    parser->comment2 = 0;
+    parser->comment3 = 0;   
+    
+    return parser;
+}
+
+void ucx_properties_free(UcxProperties *parser) {
+    if(parser->tmp) {
+        free(parser->tmp);
+    }
+    free(parser);
+}
+
+void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
+    parser->buffer = buf;
+    parser->buflen = len;
+    parser->pos = 0;
+}
+
+static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
+    if(parser->tmpcap - parser->tmplen < len) {
+        size_t newcap = parser->tmpcap + len + 64;
+        parser->tmp = (char*)realloc(parser->tmp, newcap);
+        parser->tmpcap = newcap;
+    }
+    memcpy(parser->tmp + parser->tmplen, buf, len);
+    parser->tmplen += len;
+}
+
+int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value)  {   
+    if(parser->tmplen > 0) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        cxstring str = cx_strn(buf, len);
+        cxstring nl = cx_strchr(str, '\n');
+        if(nl.ptr) {
+            size_t newlen = (size_t)(nl.ptr - buf) + 1;
+            parser_tmp_append(parser, buf, newlen);
+            // the tmp buffer contains exactly one line now
+            
+            char *orig_buf = parser->buffer;
+            size_t orig_len = parser->buflen;
+            
+            parser->buffer = parser->tmp;
+            parser->buflen = parser->tmplen;
+            parser->pos = 0;    
+            parser->tmp = NULL;
+            parser->tmpcap = 0;
+            parser->tmplen = 0;
+            // run ucx_properties_next with the tmp buffer as main buffer
+            int ret = ucx_properties_next(parser, name, value);
+            
+            // restore original buffer
+            parser->tmp = parser->buffer;
+            parser->buffer = orig_buf;
+            parser->buflen = orig_len;
+            parser->pos = newlen;
+            
+            /*
+             * if ret == 0 the tmp buffer contained just space or a comment
+             * we parse again with the original buffer to get a name/value
+             * or a new tmp buffer
+             */
+            return ret ? ret : ucx_properties_next(parser, name, value);
+        } else {
+            parser_tmp_append(parser, buf, len);
+            return 0;
+        }
+    } else if(parser->tmp) {
+        free(parser->tmp);
+        parser->tmp = NULL;
+    }
+    
+    char comment1 = parser->comment1;
+    char comment2 = parser->comment2;
+    char comment3 = parser->comment3;
+    char delimiter = parser->delimiter;
+    
+    // get one line and parse it
+    while(parser->pos < parser->buflen) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        
+        /*
+         * First we check if we have at least one line. We also get indices of
+         * delimiter and comment chars
+         */
+        size_t delimiter_index = 0;
+        size_t comment_index = 0;
+        int has_comment = 0;
+
+        size_t i = 0;
+        char c = 0;
+        for(;i<len;i++) {
+            c = buf[i];
+            if(c == comment1 || c == comment2 || c == comment3) {
+                if(comment_index == 0) {
+                    comment_index = i;
+                    has_comment = 1;
+                }
+            } else if(c == delimiter) {
+                if(delimiter_index == 0 && !has_comment) {
+                    delimiter_index = i;
+                }
+            } else if(c == '\n') {
+                break;
+            }
+        }
+
+        if(c != '\n') {
+            // we don't have enough data for a line
+            // store remaining bytes in temporary buffer for next round
+            parser->tmpcap = len + 128;
+            parser->tmp = (char*)malloc(parser->tmpcap);
+            parser->tmplen = len;
+            memcpy(parser->tmp, buf, len);
+            return 0;
+        }
+        
+        cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i);
+        // check line
+        if(delimiter_index == 0) {
+            line = cx_strtrim(line);
+            if(line.length != 0) {
+                parser->error = 1;
+            }
+        } else {
+            cxstring n = cx_strn(buf, delimiter_index);
+            cxstring v = cx_strn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1); 
+            n = cx_strtrim(n);
+            v = cx_strtrim(v);
+            if(n.length != 0 || v.length != 0) {
+                *name = n;
+                *value = v;
+                parser->pos += i + 1;
+                return 1;
+            } else {
+                parser->error = 1;
+            }
+        }
+        
+        parser->pos += i + 1;
+    }
+    
+    return 0;
+}
+
+int ucx_properties2map(UcxProperties *parser, CxMap *map) {
+    cxstring name;
+    cxstring value;
+    while(ucx_properties_next(parser, &name, &value)) {
+        cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value);
+        if(!mutvalue.ptr) {
+            return 1;
+        }
+        if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
+            cxFree(map->collection.allocator, mutvalue.ptr);
+            return 1;
+        }
+    }
+    if (parser->error) {
+        return parser->error;
+    } else {
+        return 0;
+    }
+}
+
+// buffer size is documented - change doc, when you change bufsize!
+#define UCX_PROPLOAD_BUFSIZE  1024
+int ucx_properties_load(CxMap *map, FILE *file) {
+    UcxProperties *parser = ucx_properties_new();
+    if(!(parser && map && file)) {
+        return 1;
+    }
+    
+    int error = 0;
+    size_t r;
+    char buf[UCX_PROPLOAD_BUFSIZE];
+    while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
+        ucx_properties_fill(parser, buf, r);
+        error = ucx_properties2map(parser, map);
+        if (error) {
+            break;
+        }
+    }
+    ucx_properties_free(parser);
+    return error;
+}
+
+int ucx_properties_store(CxMap *map, FILE *file) {
+    CxIterator iter = cxMapIterator(map);
+    cxstring value;
+    size_t written;
+
+    cx_foreach(CxMapEntry *, v, iter) {
+        value = cx_str(v->value);
+
+        written = 0;
+        written += fwrite(v->key->data, 1, v->key->len, file);
+        written += fwrite(" = ", 1, 3, file);
+        written += fwrite(value.ptr, 1, value.length, file);
+        written += fwrite("\n", 1, 1, file);
+
+        if (written != v->key->len + value.length + 4) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,223 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * @file properties.h
+ * 
+ * Load / store utilities for properties files.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define	UCX_PROPERTIES_H
+
+#include <cx/hash_map.h>
+#include <cx/string.h>
+
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying 
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+    /**
+     * Input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    char   *buffer;
+    
+    /**
+     * Length of the input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    size_t buflen;
+    
+    /**
+     * Current buffer position (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t pos;
+    
+    /**
+     * Internal temporary buffer (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    char   *tmp;
+    
+    /**
+     * Internal temporary buffer length (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmplen;
+    
+    /**
+     * Internal temporary buffer capacity (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmpcap;
+    
+    /**
+     * Parser error code.
+     * This is always 0 on success and a nonzero value on syntax errors.
+     * The value is set by ucx_properties_next().
+     */
+    int    error;
+    
+    /**
+     * The delimiter that shall be used.
+     * This is '=' by default.
+     */
+    char   delimiter;
+    
+    /**
+     * The first comment character.
+     * This is '#' by default.
+     */
+    char   comment1;
+    
+    /**
+     * The second comment character.
+     * This is not set by default.
+     */
+    char   comment2;
+    
+    /**
+     * The third comment character.
+     * This is not set by default.
+     */
+    char   comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ * 
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ * 
+ * 
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ * 
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ * 
+ * <b>Attention:</b> the cxstring.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ * 
+ * @param prop the UcxProperties object
+ * @param name a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ * 
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
+ * 
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, CxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ * 
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ * 
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ * 
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(CxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ * 
+ * The key/value-pairs are written by using the following format:
+ * 
+ * <code>[key] = [value]\\n</code>
+ * 
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(CxMap *map, FILE *file);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_PROPERTIES_H */
+
--- a/ui/gtk/button.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/button.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,19 +31,48 @@
 
 #include "button.h"
 #include "container.h"
-#include <ucx/mempool.h>
+#include <cx/allocator.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+void ui_button_set_icon_name(GtkWidget *button, const char *icon) {
+    if(!icon) {
+        return;
+    }
+    
+#ifdef UI_GTK4
+    gtk_button_set_icon_name(GTK_BUTTON(button), icon);
+#else
+#if GTK_CHECK_VERSION(2, 6, 0)
+    GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON);
+    if(image) {
+        gtk_button_set_image(GTK_BUTTON(button), image);
+    }
+#else
+    // TODO
+#endif
+#endif
+}
+
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata,
+        int event_value,
+        bool activate_event)
+{
     GtkWidget *button = gtk_button_new_with_label(label);
+    ui_button_set_icon_name(button, icon);
     
-    if(f) {
+    if(onclick) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = data;
-        event->callback = f;
-        event->value = 0;
+        event->userdata = userdata;
+        event->callback = onclick;
+        event->value = event_value;
+        event->customdata = NULL;
 
         g_signal_connect(
                 button,
@@ -55,11 +84,25 @@
                 "destroy",
                 G_CALLBACK(ui_destroy_userdata),
                 event);
+        if(activate_event) {
+            g_signal_connect(
+                button,
+                "activate",
+                G_CALLBACK(ui_button_clicked),
+                event);
+        }
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, button, FALSE);
-    
+    return button;
+}
+
+UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *button = ui_create_button(obj, args.label, args.icon, 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_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, button, FALSE);
     return button;
 }
 
@@ -86,81 +129,318 @@
     gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE);
 }
 
-void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+void ui_toggled_obs(void *widget, UiVarEventData *event) {
+    UiInteger *i = event->var->value;
     UiEvent e;
     e.obj = event->obj;
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.eventdata = event->var->value;
-    e.intval = gtk_toggle_tool_button_get_active(widget);
+    e.intval = i->get(i);  
     
-    UiInteger *i = event->var->value;
     ui_notify_evt(i->observers, &e);
 }
 
-UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var) {
-    GtkWidget *button = gtk_check_button_new_with_label(label);
+static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_toggle_button_get_active(widget);
+    event->callback(&e, event->userdata);    
+}
+
+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);
+    } else {
+        ui_unset_group(event->obj->ctx, event->value);
+    }
+}
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state)
+{
+    if(label) {
+        gtk_button_set_label(GTK_BUTTON(togglebutton), label);
+    }
+    ui_button_set_icon_name(togglebutton, icon);
     
-    // bind value
-    if(var) {
-        UiInteger *value = var->value;
-        value->obj = GTK_TOGGLE_BUTTON(button);
-        value->get = ui_toggle_button_get;
-        value->set = ui_toggle_button_set;
-        gtk_toggle_button_set_active(value->obj, value->value);
+    ui_bind_togglebutton(
+            obj,
+            togglebutton,
+            ui_toggle_button_get,
+            ui_toggle_button_set,
+            varname,
+            value,
+            (ui_toggled_func)ui_toggled_callback,
+            onchange,
+            onchangedata,
+            (ui_toggled_func)ui_togglebutton_enable_state_callback,
+            enable_state
+            );
+}
+
+void ui_bind_togglebutton(
+        UiObject *obj,
+        GtkWidget *widget,
+        int64_t (*getfunc)(UiInteger*),
+        void (*setfunc)(UiInteger*, int64_t),
+        const char *varname,
+        UiInteger *value,
+        void (*toggled_callback)(void*, void*),
+        ui_callback onchange,
+        void *onchangedata,
+        void (*enable_state_func)(void*, void*),
+        int enable_state)
+{
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER);
+    if (var) {
+        UiInteger* value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = getfunc;
+        value->set = setfunc;
         
         UiVarEventData *event = malloc(sizeof(UiVarEventData));
         event->obj = obj;
         event->var = var;
         event->observers = NULL;
+        event->callback = NULL;
+        event->userdata = NULL;
 
         g_signal_connect(
-                button,
-                "clicked",
+                widget,
+                "toggled",
                 G_CALLBACK(ui_toggled_obs),
                 event);
         g_signal_connect(
-                button,
+                widget,
                 "destroy",
                 G_CALLBACK(ui_destroy_vardata),
                 event);
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, button, FALSE);
+    if(onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = onchangedata;
+        event->callback = onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(toggled_callback),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    if(enable_state > 0) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = NULL;
+        event->callback = NULL;
+        event->value = enable_state;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(enable_state_func),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+}
+
+static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
     
-    return button;
+    ui_setup_togglebutton(
+            current,
+            widget,
+            args.label,
+            args.icon,
+            args.varname,
+            args.value,
+            args.onchange,
+            args.onchangedata,
+            args.enable_group);
+    ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+    return togglebutton_create(obj, gtk_toggle_button_new(), args);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+int64_t ui_check_button_get(UiInteger *integer) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = (int)gtk_check_button_get_active(button);
+    return integer->value;
+}
+
+void ui_check_button_set(UiInteger *integer, int64_t value) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = value;
+    gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_check_button_get_active(widget);
+    event->callback(&e, event->userdata);    
 }
 
-UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) {
-    UiVar *var = NULL;
-    if(value) {
-        var = malloc(sizeof(UiVar));
-        var->value = value;
-        var->type = UI_VAR_SPECIAL;
+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);
+    } else {
+        ui_unset_group(event->obj->ctx, event->value);
     }
-    return ui_checkbox_var(obj, label, var);
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *widget = gtk_check_button_new_with_label(args.label); 
+    ui_bind_togglebutton(
+            obj,
+            widget,
+            ui_check_button_get,
+            ui_check_button_set,
+            args.varname,
+            args.value,
+            (ui_toggled_func)ui_checkbox_callback,
+            args.onchange,
+            args.onchangedata,
+            (ui_toggled_func)ui_checkbox_enable_state,
+            args.enable_group);
+    
+    ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
 }
 
-UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
-    return ui_checkbox_var(obj, label, var);
+#else
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    return togglebutton_create(obj, gtk_check_button_new(), args);
+}
+#endif
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+#ifdef UI_GTK3
+    return NULL; // TODO
+#else
+    return ui_checkbox_create(obj, args);
+#endif
 }
 
+#if GTK_MAJOR_VERSION >= 4
+#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label)
+#define RADIOBUTTON_SET_GROUP(button, group) 
+#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button)
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button))
+#else
+#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label)
+#define RADIOBUTTON_SET_GROUP(button, group) /* noop */
+#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button))
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))
+#endif
 
-UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) {
+static void radiobutton_toggled(void *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = RADIOBUTTON_GET_ACTIVE(widget);
+    event->callback(&e, event->userdata);    
+}
+
+typedef struct UiRadioButtonData {
+    UiInteger *value;
+    UiVarEventData *eventdata;
+    UiBool first;
+} UiRadioButtonData;
+
+static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) {
+    if(data->first) {
+        ui_destroy_vardata(w, data->eventdata);
+        g_slist_free(data->value->obj);
+        data->value->obj = NULL;
+        data->value->get = NULL;
+        data->value->set = NULL;
+    } else {
+        free(data->eventdata);
+    }
+    free(data);
+}
+
+UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
     GSList *rg = NULL;
     UiInteger *rgroup;
     
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    
+    UiBool first = FALSE;
     if(var) {
         rgroup = var->value;
         rg = rgroup->obj;
+        if(!rg) {
+            first = TRUE;
+        }
     }
     
-    GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label);
-    rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
-    
+    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);
     if(rgroup) {
+#if GTK_MAJOR_VERSION >= 4
+        if(rg) {
+            gtk_check_button_set_group(GTK_CHECK_BUTTON(rbutton), rg->data);
+        }
+        rg = g_slist_prepend(rg, rbutton);
+#else
+        gtk_radio_button_set_group(GTK_RADIO_BUTTON(rbutton), rg);
+        rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
+#endif
+        
         rgroup->obj = rg;
         rgroup->get = ui_radiobutton_get;
         rgroup->set = ui_radiobutton_set;
@@ -171,41 +451,53 @@
         event->obj = obj;
         event->var = var;
         event->observers = NULL;
+        event->callback = NULL;
+        event->userdata = NULL;
+        
+        UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData));
+        rbdata->value = rgroup;
+        rbdata->eventdata = event;
+        rbdata->first = first;
         
         g_signal_connect(
                 rbutton,
-                "clicked",
+                "toggled",
                 G_CALLBACK(ui_radio_obs),
                 event);
         g_signal_connect(
                 rbutton,
                 "destroy",
-                G_CALLBACK(ui_destroy_vardata),
+                G_CALLBACK(destroy_radiobutton),
+                rbdata);
+    }
+    
+    if(args.onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = args.onchangedata;
+        event->callback = args.onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                rbutton,
+                "toggled",
+                G_CALLBACK(radiobutton_toggled),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
                 event);
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, rbutton, FALSE);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, rbutton, FALSE);
     
     return rbutton;
 }
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
-    UiVar *var = NULL;
-    if(rgroup) {
-        var = malloc(sizeof(UiVar));
-        var->value = rgroup;
-        var->type = UI_VAR_SPECIAL;
-    }
-    return ui_radiobutton_var(obj, label, var);
-}
-
-UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
-    return ui_radiobutton_var(obj, label, var);
-}
-
-void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) {
     UiInteger *i = event->var->value;
     
     UiEvent e;
@@ -218,6 +510,41 @@
     ui_notify_evt(i->observers, &e);
 }
 
+#if GTK_MAJOR_VERSION >= 4
+int64_t ui_radiobutton_get(UiInteger *value) {
+    int selection = 0;
+    GSList *ls = value->obj;
+    int i = 0;
+    guint len = g_slist_length(ls);
+    while(ls) {
+        if(gtk_check_button_get_active(GTK_CHECK_BUTTON(ls->data))) {
+            selection = len - i - 1;
+            break;
+        }
+        ls = ls->next;
+        i++;
+    }
+    
+    value->value = selection;
+    return selection;
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+    GSList *ls = value->obj;
+    int s = g_slist_length(ls) - 1 - i;
+    int j = 0;
+    while(ls) {
+        if(j == s) {
+            gtk_check_button_set_active(GTK_CHECK_BUTTON(ls->data), TRUE);
+            break;
+        }
+        ls = ls->next;
+        j++;
+    }
+    
+    value->value = i;
+}
+#else
 int64_t ui_radiobutton_get(UiInteger *value) {
     int selection = 0;
     GSList *ls = value->obj;
@@ -251,4 +578,4 @@
     
     value->value = i;
 }
-
+#endif
--- a/ui/gtk/button.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/button.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,18 +36,55 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+    
+void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
 
+typedef void (*ui_toggled_func)(void*, void*);
+
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata,
+        int event_value,
+        bool activate_event);
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state);
+
+void ui_bind_togglebutton(
+        UiObject *obj,
+        GtkWidget *widget,
+        int64_t (*getfunc)(UiInteger*),
+        void (*setfunc)(UiInteger*, int64_t),
+        const char *varname,
+        UiInteger *value,
+        void (*toggled_callback)(void*, void*),
+        ui_callback onchange,
+        void *onchangedata,
+        void (*enable_state_func)(void*, void*),
+        int enable_state);
+    
 // event wrapper
 void ui_button_clicked(GtkWidget *widget, UiEventData *event);
 
 
-void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+void ui_toggled_obs(void *widget, UiVarEventData *event);
 
 UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
 
 UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
 
-void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event);
 
 int64_t ui_radiobutton_get(UiInteger *value);
 void ui_radiobutton_set(UiInteger *value, int64_t i);
--- a/ui/gtk/container.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/container.c	Sat Jan 04 16:38:48 2025 +0100
@@ -32,6 +32,7 @@
 
 #include "container.h"
 #include "toolkit.h"
+#include "headerbar.h"
 
 #include "../common/context.h"
 #include "../common/object.h"
@@ -52,7 +53,7 @@
 }
 
 GtkWidget* ui_gtk_vbox_new(int spacing) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
 #else
     return gtk_vbox_new(FALSE, spacing);
@@ -60,39 +61,62 @@
 }
 
 GtkWidget* ui_gtk_hbox_new(int spacing) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
 #else
     return gtk_hbox_new(FALSE, spacing);
 #endif
 }
 
-/* -------------------- Frame Container (deprecated) -------------------- */
-UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiContainer));
-    ct->widget = frame;
-    ct->add = ui_frame_container_add;
-    return ct;
-}
-
-void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
+GtkWidget* ui_subcontainer_create(
+        UiSubContainerType type,
+        UiObject *newobj,
+        int spacing,
+        int columnspacing,
+        int rowspacing,
+        int margin)
+{
+    GtkWidget *sub = NULL;
+    GtkWidget *add = NULL;
+    switch(type) {
+        default: {
+            sub = ui_gtk_vbox_new(spacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_box_container(newobj, sub, type);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_HBOX: {
+            sub = ui_gtk_hbox_new(spacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_box_container(newobj, sub, type);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_GRID: {
+            sub = ui_create_grid_widget(columnspacing, rowspacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_grid_container(newobj, sub);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_NO_SUB: {
+            break;
+        }
+    }
+    return add;
 }
 
 
 /* -------------------- Box Container -------------------- */
-UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
-    UiBoxContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) {
+    UiBoxContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiBoxContainer));
     ct->container.widget = box;
     ct->container.add = ui_box_container_add;
+    ct->type = type;
     return (UiContainer*)ct;
 }
 
@@ -111,27 +135,40 @@
     }
     
     UiBool expand = fill;
+#if GTK_MAJOR_VERSION >= 4
+    gtk_box_append(GTK_BOX(ct->widget), widget);
+    GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START; 
+    if(bc->type == UI_CONTAINER_VBOX) {
+        gtk_widget_set_valign(widget, align);
+        gtk_widget_set_vexpand(widget, expand);
+        gtk_widget_set_hexpand(widget, TRUE);
+    } else if(bc->type == UI_CONTAINER_HBOX) {
+        gtk_widget_set_halign(widget, align);
+        gtk_widget_set_hexpand(widget, expand);
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+#else
     gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+#endif
     
     ui_reset_layout(ct->layout);
     ct->current = widget;
 }
 
 UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
-    UiGridContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiGridContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiGridContainer));
     ct->container.widget = grid;
     ct->container.add = ui_grid_container_add;
-#ifdef UI_GTK2
-    ct->width = 0;
-    ct->height = 1;
-#endif
+    UI_GTK_V2(ct->width = 0);
+    UI_GTK_V2(ct->height = 1);
     return (UiContainer*)ct;
 }
 
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
 void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
     UiGridContainer *grid = (UiGridContainer*)ct;
     
@@ -143,24 +180,39 @@
     
     int hexpand = FALSE;
     int vexpand = FALSE;
+    int hfill = FALSE;
+    int vfill = FALSE;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
     if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
         hexpand = ct->layout.hexpand;
+        hfill = TRUE;
     }
     if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
         vexpand = ct->layout.vexpand;
+        vfill = TRUE;
+    }
+    if(fill) {
+        hfill = TRUE;
+        vfill = TRUE;
     }
     
-    if(hexpand) {
-        gtk_widget_set_hexpand(widget, TRUE);
+    if(!hfill) {
+        gtk_widget_set_halign(widget, GTK_ALIGN_START);
     }
-    if(vexpand) {
-        gtk_widget_set_vexpand(widget, TRUE);
+    if(!vfill) {
+        gtk_widget_set_valign(widget, GTK_ALIGN_START);
     }
     
-    int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1;
+    gtk_widget_set_hexpand(widget, hexpand);
+    gtk_widget_set_vexpand(widget, vexpand);
     
-    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1);
-    grid->x += gwidth;
+    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+    
+    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan);
+    grid->x += colspan;
     
     ui_reset_layout(ct->layout);
     ct->current = widget;
@@ -187,6 +239,10 @@
     GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
     GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
     
+    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+    // TODO: use colspan/rowspan
+    
     gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
     grid->x++;
     int nw = grid->x > grid->width ? grid->x : grid->width;
@@ -201,9 +257,44 @@
 }
 #endif
 
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = frame;
+    ct->add = ui_frame_container_add;
+    return ct;
+}
+
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    FRAME_SET_CHILD(ct->widget, widget);
+}
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = expander;
+    ct->add = ui_expander_container_add;
+    return ct;
+}
+
+void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    EXPANDER_SET_CHILD(ct->widget, widget);
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: check if the widget implements GtkScrollable
+    SCROLLEDWINDOW_SET_CHILD(ct->widget, widget);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
 UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiContainer));
     ct->widget = scrolledwindow;
@@ -211,20 +302,9 @@
     return ct;
 }
 
-void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    // TODO: check if the widget implements GtkScrollable
-#ifdef UI_GTK3
-    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
-#else
-    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget);
-#endif
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
 UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
-    UiTabViewContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiTabViewContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiTabViewContainer));
     ct->container.widget = tabview;
@@ -233,28 +313,23 @@
 }
 
 void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    gtk_notebook_append_page(
-            GTK_NOTEBOOK(ct->widget),
-            widget,
-            gtk_label_new(ct->layout.label));
+    UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview");
+        return;
+    }
+    data->add_tab(ct->widget, -1, ct->layout.label, widget);
     
     ui_reset_layout(ct->layout);
     ct->current = widget;
 }
 
 
-UIWIDGET ui_vbox(UiObject *obj) {
-    return ui_vbox_sp(obj, 0, 0);
-}
 
-UIWIDGET ui_hbox(UiObject *obj) {
-    return ui_hbox_sp(obj, 0, 0);
-}
-
-static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) {
     GtkWidget *ret = box;
-#ifdef UI_GTK3
-#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+#if GTK_MAJOR_VERSION >= 3
+#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
     gtk_widget_set_margin_start(box, margin);
     gtk_widget_set_margin_end(box, margin);
 #else
@@ -272,73 +347,53 @@
     return ret;
 }
 
-UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
-    UiContainer *ct = uic_get_current_container(obj);
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type) {
+    UiObject *current = uic_current_obj(obj);
+    UiContainer *ct = current->container;
+    UI_APPLY_LAYOUT1(current, args);
     
-    GtkWidget *vbox = ui_gtk_vbox_new(spacing);   
-    GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox;
+    GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
+    ui_set_name_and_style(box, args.name, args.style_class);
+    GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
     ct->add(ct, widget, TRUE);
     
-    UiObject *newobj = uic_object_new(obj, vbox);
-    newobj->container = ui_box_container(obj, vbox);
-    uic_obj_add(obj, newobj);
-    
-    return widget;
-}
-
-UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    GtkWidget *hbox = ui_gtk_hbox_new(spacing);
-    GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox;
-    ct->add(ct, widget, TRUE);
-    
-    UiObject *newobj = uic_object_new(obj, hbox);
-    newobj->container = ui_box_container(obj, hbox);
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, type);
     uic_obj_add(obj, newobj);
     
     return widget;
 }
 
-UIWIDGET ui_grid(UiObject *obj) {
-    return ui_grid_sp(obj, 0, 0, 0);
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, UI_CONTAINER_VBOX);
+}
+
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, UI_CONTAINER_HBOX);
 }
 
-UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
-    UiContainer *ct = uic_get_current_container(obj);
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing) {
+#if GTK_MAJOR_VERSION >= 3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#else
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    gtk_table_set_col_spacings(GTK_TABLE(grid), colspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+#endif
+    return grid;
+}
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
     GtkWidget *widget;
     
-#ifdef UI_GTK3
-    GtkWidget *grid = gtk_grid_new();
-    gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing);
-    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
-#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
-    gtk_widget_set_margin_start(grid, margin);
-    gtk_widget_set_margin_end(grid, margin);
-#else
-    gtk_widget_set_margin_left(grid, margin);
-    gtk_widget_set_margin_right(grid, margin);
-#endif
-    gtk_widget_set_margin_top(grid, margin);
-    gtk_widget_set_margin_bottom(grid, margin);
-    
-    widget = grid;
-#elif defined(UI_GTK2)
-    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
-    
-    gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing);
-    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
-    
-    if(margin > 0) {
-        GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
-        gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
-        gtk_container_add(GTK_CONTAINER(a), grid);
-        widget = a;
-    } else {
-        widget = grid;
-    }
-#endif
-    ct->add(ct, widget, TRUE);
+    GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing);
+    ui_set_name_and_style(grid, args.name, args.style_class);
+    widget = ui_box_set_margin(grid, args.margin);
+    current->container->add(current->container, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, grid);
     newobj->container = ui_grid_container(obj, grid);
@@ -347,47 +402,535 @@
     return widget;
 }
 
-UIWIDGET ui_scrolledwindow(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
-    ct->add(ct, sw, TRUE);
+UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *frame = gtk_frame_new(args.label);
+    UiObject *newobj = uic_object_new(obj, frame);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        FRAME_SET_CHILD(frame, sub);
+    } else {
+        newobj->widget = frame;
+        newobj->container = ui_frame_container(obj, frame);
+    }
+    current->container->add(current->container, frame, FALSE);
+    uic_obj_add(obj, newobj);
+    
+    return frame;
+}
+
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *expander = gtk_expander_new(args.label);
+    gtk_expander_set_expanded(GTK_EXPANDER(expander), args.isexpanded);
+    UiObject *newobj = uic_object_new(obj, expander);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        EXPANDER_SET_CHILD(expander, sub);
+    } else {
+        newobj->widget = expander;
+        newobj->container = ui_expander_container(obj, expander);
+    }
+    current->container->add(current->container, expander, FALSE);
+    uic_obj_add(obj, newobj);
+    
+    return expander;
+}
+
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *sw = SCROLLEDWINDOW_NEW();
+    ui_set_name_and_style(sw, args.name, args.style_class);
+    GtkWidget *widget = ui_box_set_margin(sw, args.margin);
+    current->container->add(current->container, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, sw);
-    newobj->container = ui_scrolledwindow_container(obj, sw);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        SCROLLEDWINDOW_SET_CHILD(sw, sub);
+    } else {
+        newobj->widget = sw;
+        newobj->container = ui_scrolledwindow_container(obj, sw);
+    }
+    
     uic_obj_add(obj, newobj);
     
     return sw;
 }
 
-UIWIDGET ui_tabview(UiObject *obj) {
-    GtkWidget *tabview = gtk_notebook_new();
-    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
-    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE);
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, tabview, TRUE);
-    
-    UiObject *tabviewobj = uic_object_new(obj, tabview);
-    tabviewobj->container = ui_tabview_container(obj, tabview);
-    uic_obj_add(obj, tabviewobj);
-    
-    return tabview;
+
+void ui_notebook_tab_select(UIWIDGET tabview, int tab) {
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_remove(UIWIDGET tabview, int tab) {
+    gtk_notebook_remove_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    gtk_notebook_insert_page(
+            GTK_NOTEBOOK(widget),
+            child,
+            gtk_label_new(name),
+            index);
+}
+
+int64_t ui_notebook_get(UiInteger *i) {
+    GtkNotebook *nb = i->obj;
+    i->value = gtk_notebook_get_current_page(nb);
+    return i->value;
+}
+
+void ui_notebook_set(UiInteger *i, int64_t value) {
+    GtkNotebook *nb = i->obj;
+    gtk_notebook_set_current_page(nb, value);
+    i->value = gtk_notebook_get_current_page(nb);
+}
+
+
+#if GTK_MAJOR_VERSION >= 4
+static int stack_set_page(GtkWidget *stack, int index) {
+    GtkSelectionModel *pages = gtk_stack_get_pages(GTK_STACK(stack));
+    GListModel *list = G_LIST_MODEL(pages);
+    GtkStackPage *page = g_list_model_get_item(list, index);
+    if(page) {
+        gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_page_get_child(page));
+    } else {
+        fprintf(stderr, "UI Error: ui_stack_set value out of bounds\n");
+        return -1;
+    }
+    return index;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+    stack_set_page(tabview, tab);
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+    GtkStack *stack = GTK_STACK(tabview);
+    GtkWidget *current = gtk_stack_get_visible_child(stack);
+    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+    GListModel *list = G_LIST_MODEL(pages);
+    GtkStackPage *page = g_list_model_get_item(list, tab);
+    if(page) {
+        gtk_stack_remove(stack, gtk_stack_page_get_child(page));
+    }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    (void)gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+    GtkStack *stack = GTK_STACK(i->obj);
+    GtkWidget *current = gtk_stack_get_visible_child(stack);
+    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+    GListModel *list = G_LIST_MODEL(pages);
+    int nitems = g_list_model_get_n_items(list);
+    for(int p=0;p<nitems;p++) {
+        GtkStackPage *page = g_list_model_get_item(list, p);
+        GtkWidget *child = gtk_stack_page_get_child(page);
+        if(child == current) {
+            i->value = p;
+            break;
+        }
+    }
+    return i->value;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+    GtkWidget *widget = i->obj;
+    if(stack_set_page(widget, value) >= 0) {
+        i->value = value;
+    }
+}
+#elif GTK_MAJOR_VERSION >= 3
+static GtkWidget* stack_get_child(GtkWidget *stack, int index) {
+    GList *children = gtk_container_get_children(GTK_CONTAINER(stack));
+    if(children) {
+        return g_list_nth_data(children, index);
+    }
+    return NULL;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+    GtkWidget *child = stack_get_child(tabview, tab);
+    if(child) {
+        gtk_stack_set_visible_child(GTK_STACK(tabview), child);
+    }
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+    GtkWidget *child = stack_get_child(tabview, tab);
+    if(child) {
+        gtk_container_remove(GTK_CONTAINER(tabview), child);
+    }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+    GtkWidget *visible = gtk_stack_get_visible_child(GTK_STACK(i->obj));
+    GList *children = gtk_container_get_children(GTK_CONTAINER(i->obj));
+    GList *elm = children;
+    int n = 0;
+    int64_t v = -1;
+    while(elm) {
+        GtkWidget *child = elm->data;
+        if(child == visible) {
+            v = n;
+            break;
+        }
+        
+        elm = elm->next;
+        n++;
+    }
+    g_list_free(children);
+    i->value = v;
+    return v;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+    GtkWidget *child = stack_get_child(i->obj, value);
+    if(child) {
+        gtk_stack_set_visible_child(GTK_STACK(i->obj), child);
+        i->value = value;
+    }
+}
+
+#endif
+
+
+
+
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview) {
+    return g_object_get_data(G_OBJECT(tabview), "ui_tabview");
 }
 
-void ui_tab(UiObject *obj, char *title) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.label = title;
-    ui_vbox(obj);
+typedef int64_t(*ui_tabview_get_func)(UiInteger*);
+typedef void (*ui_tabview_set_func)(UiInteger*, int64_t);
+
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
+    UiGtkTabView *data = malloc(sizeof(UiGtkTabView));
+    data->margin = args.margin;
+    data->spacing = args.spacing;
+    data->columnspacing = args.columnspacing;
+    data->rowspacing = args.rowspacing;
+    
+    ui_tabview_get_func getfunc = NULL;
+    ui_tabview_set_func setfunc = NULL;
+    
+    GtkWidget *widget = NULL;
+    GtkWidget *data_widget = NULL;
+    switch(args.tabview) {
+        case UI_TABVIEW_DOC: {
+            // TODO
+            break;
+        }
+        case UI_TABVIEW_NAVIGATION_SIDE: {
+#if GTK_CHECK_VERSION(3, 10, 0)
+            widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+            GtkWidget *sidebar = gtk_stack_sidebar_new();
+            BOX_ADD(widget, sidebar);
+            GtkWidget *stack = gtk_stack_new();
+            gtk_stack_set_transition_type (GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN);
+            gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(sidebar), GTK_STACK(stack));
+            BOX_ADD_EXPAND(widget, stack);
+            data->select_tab = ui_stack_tab_select;
+            data->remove_tab = ui_stack_tab_remove;
+            data->add_tab = ui_stack_tab_add;
+            getfunc = ui_stack_get;
+            setfunc = ui_stack_set;
+            data_widget = stack;
+#else
+            // TODO
+#endif
+            break;
+        }
+        case UI_TABVIEW_DEFAULT: /* fall through */
+        case UI_TABVIEW_NAVIGATION_TOP: /* fall through */
+        case UI_TABVIEW_INVISIBLE: /* fall through */
+        case UI_TABVIEW_NAVIGATION_TOP2: {
+            widget = gtk_notebook_new();
+            data_widget = widget;
+            data->select_tab = ui_notebook_tab_select;
+            data->remove_tab = ui_notebook_tab_remove;
+            data->add_tab = ui_notebook_tab_add;
+            getfunc = ui_notebook_get;
+            setfunc = ui_notebook_set;
+            if(args.tabview == UI_TABVIEW_INVISIBLE) {
+                gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
+                gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
+            }
+            break;
+        }
+    }
+    
+    UiObject* current = uic_current_obj(obj);
+    if(args.value || args.varname) {
+        UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+        UiInteger *i = var->value;
+        i->get = getfunc;
+        i->set = setfunc;
+        i->obj = data_widget;
+    }
+    
+    g_object_set_data(G_OBJECT(widget), "ui_tabview", data);
+    data->widget = data_widget;
+    data->subcontainer = args.subcontainer;
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, widget);
+    newobj->container = ui_tabview_container(obj, widget);
+    uic_obj_add(obj, newobj);
+    data->obj = newobj;
+    
+    return widget;
+}
+
+void ui_tab_create(UiObject* obj, const char* title) {
+    UiObject* current = uic_current_obj(obj);
+    UiGtkTabView *data = ui_widget_get_tabview_data(current->widget);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    
+    UiObject *newobj = ui_tabview_add(current->widget, title, -1);
+    current->next = newobj;
+}
+
+
+
+void ui_tabview_select(UIWIDGET tabview, int tab) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    data->select_tab(tabview, tab);
+}
+
+void ui_tabview_remove(UIWIDGET tabview, int tab) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    data->remove_tab(tabview, tab);
+}
+
+UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return NULL;
+    }
+    
+    UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject));
+    newobj->ctx = data->obj->ctx;
+    
+    GtkWidget *sub;
+    switch(data->subcontainer) {
+        default: {
+            sub = ui_gtk_vbox_new(data->spacing);
+            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+            break;
+        }
+        case UI_CONTAINER_HBOX: {
+            sub = ui_gtk_hbox_new(data->spacing);
+            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+            break;
+        }
+        case UI_CONTAINER_GRID: {
+            sub = ui_create_grid_widget(data->columnspacing, data->rowspacing);
+            newobj->container = ui_grid_container(newobj, sub);
+            break;
+        }
+    }
+    newobj->widget = sub;
+    GtkWidget *widget = ui_box_set_margin(sub, data->margin);
+    
+    data->add_tab(data->widget, tab_index, name, widget);
+    
+    return newobj;
 }
 
-void ui_select_tab(UIWIDGET tabview, int tab) {
-    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+
+/* -------------------- Headerbar -------------------- */
+
+static void hb_set_part(UiObject *obj, int part) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *headerbar = current->widget;
+    
+    UiHeaderbarContainer *hb = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    memcpy(hb, current->container, sizeof(UiHeaderbarContainer));
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = (UiContainer*)hb;
+    uic_obj_add(obj, newobj);
+    
+    hb->part = part;
+}
+
+void ui_headerbar_start_create(UiObject *obj) {
+    hb_set_part(obj, 0);
+}
+
+void ui_headerbar_center_create(UiObject *obj) {
+    hb_set_part(obj, 2);
+}
+
+void ui_headerbar_end_create(UiObject *obj) {
+    hb_set_part(obj, 1);
+}
+
+UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs args) {
+    UiObject *current = uic_current_obj(obj);
+    UiContainer *ct = current->container;
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *box = ui_gtk_hbox_new(args.alt_spacing);
+    ui_set_name_and_style(box, args.name, args.style_class);
+    ct->add(ct, box, FALSE);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_headerbar_fallback_container(obj, box);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+
+static void hb_fallback_set_part(UiObject *obj, int part) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *headerbar = current->widget;
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = ui_headerbar_container(obj, headerbar);
+    uic_obj_add(obj, newobj);
+    
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container;
+    hb->part = part;
+}
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) {
+    UiHeaderbarContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    ct->container.widget = headerbar;
+    ct->container.add = ui_headerbar_fallback_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+    BOX_ADD(ct->widget, widget);
 }
 
+#if GTK_CHECK_VERSION(3, 10, 0)
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+    GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar");
+    if(!headerbar) {
+        return ui_headerbar_fallback_create(obj, args);
+    }
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = ui_headerbar_container(obj, headerbar);
+    uic_obj_add(obj, newobj);
+    
+    return headerbar;    
+}
+
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) {
+    UiHeaderbarContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    ct->container.widget = headerbar;
+    ct->container.add = ui_headerbar_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+    if(hb->part == 0) {
+        UI_HEADERBAR_PACK_START(ct->widget, widget);
+    } else if(hb->part == 1) {
+        UI_HEADERBAR_PACK_END(ct->widget, widget);
+    } else if(hb->part == 2) {
+        if(!hb->centerbox) {
+            GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+            hb->centerbox = box;
+            UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box);
+        }
+        BOX_ADD(hb->centerbox, widget);
+    }
+}
+
+#else
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+    return ui_headerbar_fallback_create(obj, args);  
+}
+
+#endif
+
+/* -------------------- Sidebar -------------------- */
+
+#ifdef UI_LIBADWAITA
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
+    GtkWidget *sidebar_toolbar_view = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
+    if(!sidebar_toolbar_view) {
+        fprintf(stderr, "Error: window is not configured for sidebar\n");
+        return NULL;
+    }
+    
+    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
+    ui_box_set_margin(box, args.margin);
+    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+#else
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
+    GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
+    
+    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
+    ui_box_set_margin(box, args.margin);
+    BOX_ADD_EXPAND(sidebar_vbox, box);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+#endif
+
 /* -------------------- Splitpane -------------------- */
 
 static GtkWidget* create_paned(UiOrientation orientation) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     switch(orientation) {
         case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
         case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
@@ -401,191 +944,129 @@
     return NULL;
 }
 
-UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) {
-    GtkWidget *paned = create_paned(orientation);
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, paned, TRUE);
-    
-    if(max <= 0) max = INT_MAX;
-    
-    UiPanedContainer *pctn = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiPanedContainer));
-    pctn->container.widget = paned;
-    pctn->container.add = ui_paned_container_add;
-    pctn->current_pane = paned;
-    pctn->orientation = orientation;
-    pctn->max = max;
-    pctn->cur = 0;
-    
-    UiObject *pobj = uic_object_new(obj, paned);
-    pobj->container = (UiContainer*)pctn;
-    
-    uic_obj_add(obj, pobj);
-    
-    return paned;
-}
-
-UIWIDGET ui_hsplitpane(UiObject *obj, int max) {
-    return ui_splitpane(obj, max, UI_HORIZONTAL);
-}
-
-UIWIDGET ui_vsplitpane(UiObject *obj, int max) {
-    return ui_splitpane(obj, max, UI_VERTICAL);
-}
-
-void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    UiPanedContainer *pctn = (UiPanedContainer*)ct;
-    
-    gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE;
-    int width = ct->layout.width;
-    ui_reset_layout(ct->layout);
-    
-    if(pctn->cur == 0) {
-        gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize);
-    } else if(pctn->cur < pctn->max-1) {
-        GtkWidget *nextPane = create_paned(pctn->orientation);
-        gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE);
-        gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize);
-        pctn->current_pane = nextPane;
-    } else if(pctn->cur == pctn->max-1) {
-        gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize);
-        width = 0; // disable potential call of gtk_paned_set_position
-    } else {
-        fprintf(stderr, "Splitpane max reached: %d\n", pctn->max);
-        return;
-    }
-    
-    if(width > 0) {
-        gtk_paned_set_position(GTK_PANED(pctn->current_pane), width);
-    }
-    
-    pctn->cur++;
-}
 
 
-/* -------------------- Sidebar (deprecated) -------------------- */
-UIWIDGET ui_sidebar(UiObject *obj) {
-#ifdef UI_GTK3
-    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
-#else
-    GtkWidget *paned = gtk_hpaned_new();
-#endif
-    gtk_paned_set_position(GTK_PANED(paned), 200);
-    
-    GtkWidget *sidebar = ui_gtk_vbox_new(0);
-    gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE);
-    
-    UiObject *left = uic_object_new(obj, sidebar);
-    UiContainer *ct1 = ui_box_container(obj, sidebar);
-    left->container = ct1;
-    
-    UiObject *right = uic_object_new(obj, sidebar);
-    UiContainer *ct2 = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiContainer));
-    ct2->widget = paned;
-    ct2->add = ui_split_container_add2;
-    right->container = ct2;
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, paned, TRUE);
-    
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
-    
-    return sidebar;
+/* -------------------- ItemList Container -------------------- */
+
+static void remove_item(void *data, void *item) {
+    UiGtkItemListContainer *ct = data;
+    UiObject *obj = item;
+    if(ct->remove_items) {
+        BOX_REMOVE(ct->widget, obj->widget);
+    }
+    uic_object_destroy(obj);
 }
 
-void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    // TODO: remove
-    gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+static void update_itemlist(UiList *list, int c) {
+    UiGtkItemListContainer *ct = list->obj;
     
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    new_items->collection.advanced_destructor = remove_item;
+    new_items->collection.destructor_data = ct;
     
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-
-/* -------------------- Document Tabview -------------------- */
-static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) {
-    GQuark q = g_quark_from_static_string("ui.tab.object");
-    UiObject *tab = g_object_get_qdata(G_OBJECT(page), q);
-    if(!tab) {
-        return;
+    // only create new widgets for new elements, so at first we have
+    // to find which elements are new
+    // check which elements in the list are already in the container
+    void *elm = list->first(list);
+    int j = 0;
+    while(elm) {
+        CxHashKey key = cx_hash_key(&elm, sizeof(void*));
+        UiObject *item_obj = cxMapRemoveAndGet(ct->current_items, key);
+        if(item_obj) {
+            g_object_ref(G_OBJECT(item_obj->widget));
+            BOX_REMOVE(ct->widget, item_obj->widget);
+            cxMapPut(new_items, key, item_obj);
+        }
+        elm = list->next(list);
+        j++;
     }
     
-    //printf("page_change: %d\n", page_num);
-    UiContext *ctx = tab->ctx;
-    uic_context_detach_all(ctx->parent); // TODO: fix?
-    ctx->parent->attach_document(ctx->parent, ctx->document);
-}
-
-UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
-    GtkWidget *tabview = gtk_notebook_new();
-    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    // ct->current_items only contains elements, that are not in the list
+    cxMapDestroy(ct->current_items); // calls destructor remove_item
+    ct->current_items = new_items;
     
-    g_signal_connect(
-                tabview,
-                "switch-page",
-                G_CALLBACK(page_change),
-                NULL);
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, tabview, TRUE);
-    
-    UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane));
-    tabbedpane->ctx = uic_current_obj(obj)->ctx;
-    tabbedpane->widget = tabview;
-    tabbedpane->document = NULL;
-    
-    return tabbedpane;
+    // add all items
+    int index = 0;
+    elm = list->first(list);
+    while(elm) {
+        CxHashKey key = cx_hash_key(&elm, sizeof(void*));
+        UiObject *item_obj = cxMapGet(ct->current_items, key);
+        if(item_obj) {
+            // re-add previously created widget
+            ui_box_container_add(ct->container, item_obj->widget, FALSE);
+        } else {
+            // create new widget and object for this list element
+            CxMempool *mp = cxBasicMempoolCreate(256);
+            const CxAllocator *a = mp->allocator;
+            UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
+            obj->ctx = uic_context(obj, mp);
+            obj->window = NULL;
+            obj->widget = ui_subcontainer_create(
+                    ct->subcontainer,
+                    obj,
+                    ct->spacing,
+                    ct->columnspacing,
+                    ct->rowspacing,
+                    ct->margin);
+            ui_box_container_add(ct->container, obj->widget, FALSE);
+            if(ct->create_ui) {
+                ct->create_ui(obj, index, elm, ct->userdata);
+            }
+            cxMapPut(new_items, key, obj);
+        }
+        elm = list->next(list);
+        index++;
+    }
 }
 
-UiObject* ui_document_tab(UiTabbedPane *view) {
-    GtkWidget *frame = gtk_frame_new(NULL);
-    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
-    // TODO: label
-    gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL);
-    
-    UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject));
-    tab->widget = NULL; // initialization for uic_context()
-    tab->ctx = uic_context(tab, view->ctx->mempool);
-    tab->ctx->parent = view->ctx;
-    tab->ctx->attach_document = uic_context_attach_document;
-    tab->ctx->detach_document2 = uic_context_detach_document2;
-    tab->widget = frame;
-    tab->window = view->ctx->obj->window;
-    tab->container = ui_frame_container(tab, frame);
-    tab->next = NULL;
-    
-    GQuark q = g_quark_from_static_string("ui.tab.object");
-    g_object_set_qdata(G_OBJECT(frame), q, tab);
-    
-    return tab;
+static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) {
+    container->remove_items = FALSE;
+    cxMapDestroy(container->current_items);
+    free(container);
 }
 
-void ui_tab_set_document(UiContext *ctx, void *document) {
-    // TODO: remove?
-    if(ctx->parent->document) {
-        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args) {
+    UiObject *current = uic_current_obj(obj);
+    UiContainer *ct = current->container;
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *box = args.container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
+    ui_set_name_and_style(box, args.name, args.style_class);
+    GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
+    ct->add(ct, widget, TRUE);
+    
+    UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer));
+    container->parent = obj;
+    container->widget = box;
+    container->container = ui_box_container(current, box, args.container);
+    container->create_ui = args.create_ui;
+    container->userdata = args.userdata;
+    container->subcontainer = args.subcontainer;
+    container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    container->current_items->collection.advanced_destructor = remove_item;
+    container->current_items->collection.destructor_data = container;
+    container->margin = args.sub_margin;
+    container->spacing = args.sub_spacing;
+    container->columnspacing = args.sub_columnspacing;
+    container->rowspacing = args.sub_rowspacing;
+    container->remove_items = TRUE;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST);
+    if(var) {
+        UiList *list = var->value;
+        list->obj = container;
+        list->update = update_itemlist;
+        update_itemlist(list, 0);
     }
-    //uic_context_set_document(ctx, document);
-    //uic_context_set_document(ctx->parent, document);
-    //ctx->parent->document = document;
+    g_signal_connect(
+                box,
+                "destroy",
+                G_CALLBACK(destroy_itemlist_container),
+                container);
+    
+    return box;
 }
 
-void ui_tab_detach_document(UiContext *ctx) {
-    // TODO: remove?
-    //uic_context_detach_document(ctx->parent);
-}
 
 
 /*
@@ -610,14 +1091,24 @@
     ct->layout.vexpand = expand;
 }
 
-void ui_layout_width(UiObject *obj, int width) {
+void ui_layout_hfill(UiObject *obj, UiBool fill) {
     UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.width = width;
+    ct->layout.hfill = fill;
 }
 
-void ui_layout_gridwidth(UiObject *obj, int width) {
+void ui_layout_vfill(UiObject *obj, UiBool fill) {
     UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.gridwidth = width;
+    ct->layout.vfill = fill;
+}
+
+void ui_layout_colspan(UiObject* obj, int cols) {
+    UiContainer* ct = uic_get_current_container(obj);
+    ct->layout.colspan = cols;
+}
+
+void ui_layout_rowspan(UiObject* obj, int rows) {
+    UiContainer* ct = uic_get_current_container(obj);
+    ct->layout.rowspan = rows;
 }
 
 void ui_newline(UiObject *obj) {
--- a/ui/gtk/container.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/container.h	Sat Jan 04 16:38:48 2025 +0100
@@ -33,6 +33,9 @@
 #include "../ui/container.h"
 #include <string.h>
 
+#include <cx/allocator.h>
+#include <cx/hash_map.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -60,13 +63,16 @@
     char         *label;
     UiBool       hexpand;
     UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
     int          width;
-    int          gridwidth;
+    int          colspan;
+    int          rowspan;
 };
 
 struct UiContainer {
     GtkWidget *widget;
-    GtkMenu *menu;
+    UIMENU menu;
     GtkWidget *current;
     
     void (*add)(UiContainer*, GtkWidget*, UiBool);
@@ -77,6 +83,7 @@
 
 typedef struct UiBoxContainer {
     UiContainer container;
+    UiSubContainerType type;
     UiBool has_fill;
 } UiBoxContainer;
 
@@ -90,6 +97,7 @@
 #endif
 } UiGridContainer;
 
+/*
 typedef struct UiPanedContainer {
     UiContainer container;
     GtkWidget *current_pane;
@@ -97,23 +105,80 @@
     int max;
     int cur;
 } UiPanedContainer;
+*/
 
 typedef struct UiTabViewContainer {
     UiContainer container;
 } UiTabViewContainer;
 
+typedef void (*ui_select_tab_func)(UIWIDGET widget, int tab);
+typedef void (*ui_add_tab_func)(UIWIDGET widget, int index, const char *name, UIWIDGET child);
+
+typedef struct UiGtkTabView {
+    UiObject *obj;
+    GtkWidget *widget;
+    ui_select_tab_func select_tab;
+    ui_select_tab_func remove_tab;
+    ui_add_tab_func add_tab;
+    UiSubContainerType subcontainer;
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+} UiGtkTabView;
+
+typedef struct UiHeaderbarContainer {
+    UiContainer container;
+    GtkWidget *centerbox;
+    int part;
+    UiHeaderbarAlternative alternative; /* only used by fallback headerbar */
+} UiHeaderbarContainer;
+
+typedef struct UiGtkItemListContainer {
+    UiObject *parent;
+    GtkWidget *widget;
+    UiContainer *container;
+    void (*create_ui)(UiObject *, int, void *, void *);
+    void *userdata;
+    UiSubContainerType subcontainer;
+    CxMap *current_items;
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+    bool remove_items;
+} UiGtkItemListContainer;
+
 GtkWidget* ui_gtk_vbox_new(int spacing);
 GtkWidget* ui_gtk_hbox_new(int spacing);
 
+GtkWidget* ui_subcontainer_create(
+        UiSubContainerType type,
+        UiObject *newobj,
+        int spacing,
+        int columnspacing,
+        int rowspacing,
+        int margin);
+
 UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
 void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
-UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin);
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type);
 void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing);
 UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
 void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander);
+void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
 UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow);
 void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
@@ -125,10 +190,17 @@
 void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill);
 void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
 
-UiObject* ui_add_document_tab(UiDocumentView *view);
-void ui_tab_set_document(UiContext *ctx, void *document);
-void ui_tab_detach_document(UiContext *ctx);
+void ui_gtk_notebook_select_tab(GtkWidget *widget, int tab);
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+#endif
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
 #ifdef	__cplusplus
 }
--- a/ui/gtk/display.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/display.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,12 +31,14 @@
 
 #include "display.h"
 #include "container.h"
-#include <ucx/mempool.h>
 #include "../common/context.h"
 #include "../common/object.h"
+#include "../ui/display.h"
+
+#include <cx/printf.h>
 
 static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
-#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+#if GTK_MAJOR_VERSION >= 4 || (GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16)
     gtk_label_set_xalign(GTK_LABEL(widget), xalign);
     gtk_label_set_yalign(GTK_LABEL(widget), yalign);
 #else
@@ -44,30 +46,108 @@
 #endif
 }
 
-UIWIDGET ui_label(UiObject *obj, char *label) { 
-    GtkWidget *widget = gtk_label_new(label);
+UIWIDGET ui_label_create(UiObject *obj, UiLabelArgs args) {
+    UiObject* current = uic_current_obj(obj);
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, widget, FALSE);
+    const char *css_class = NULL;
+    char *markup = NULL;
+    if(args.label) {
+        #if GTK_MAJOR_VERSION < 3
+            switch(args.style) {
+                case UI_LABEL_STYLE_DEFAULT: break;
+                case UI_LABEL_STYLE_TITLE: {
+                    cxmutstr m = cx_asprintf("<b>%s</b>", args.label);
+                    markup = m.ptr;
+                    args.label = NULL;
+                }
+                case UI_LABEL_STYLE_SUBTITLE: {
+                    break;
+                }
+                case UI_LABEL_STYLE_DIM: {
+                    break;
+                }
+            }
+#       else
+            switch(args.style) {
+                case UI_LABEL_STYLE_DEFAULT: break;
+                case UI_LABEL_STYLE_TITLE: {
+                    css_class = "ui_label_title";
+                    break;
+                }
+                case UI_LABEL_STYLE_SUBTITLE: {
+                    css_class = "subtitle";
+                    break;
+                }
+                case UI_LABEL_STYLE_DIM: {
+                    css_class = "dim-label";
+                    break;
+                }
+            }
+#       endif
+    }
+
+    
+    GtkWidget *widget = gtk_label_new(args.label);
+    if(markup) {
+        gtk_label_set_markup(GTK_LABEL(widget), markup);
+        free(markup);
+    }
+    
+    if(css_class) {
+        WIDGET_ADD_CSS_CLASS(widget, css_class);
+    }
+    
+    switch(args.align) {
+        case UI_ALIGN_DEFAULT: break;
+        case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break;
+        case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break;
+        case UI_ALIGN_CENTER: break; // TODO
+    }
+    
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if(var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_label_get;
+        value->set = ui_label_set;
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
     
     return widget;
 }
 
-UIWIDGET ui_llabel(UiObject *obj, char *label) {
-    UIWIDGET widget = ui_label(obj, label);
-    set_alignment(widget, 0, .5);
-    return widget;
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_LEFT;
+    return ui_label_create(obj, args);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_RIGHT;
+    return ui_label_create(obj, args);
 }
 
-UIWIDGET ui_rlabel(UiObject *obj, char *label) {
-    UIWIDGET widget = ui_label(obj, label);
-    //gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_RIGHT);
-    
-    set_alignment(widget, 1, .5);
-    return widget;
+char* ui_label_get(UiString *s) {
+    if(s->value.ptr) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = g_strdup(gtk_label_get_text(GTK_LABEL(s->obj)));
+    s->value.free = (ui_freefunc)g_free;
+    return s->value.ptr;
 }
 
-UIWIDGET ui_space(UiObject *obj) {
+void ui_label_set(UiString *s, const char *value) {
+    gtk_label_set_text(GTK_LABEL(s->obj), value);
+    if(s->value.ptr) {
+        s->value.free(s->value.ptr);
+        s->value.ptr = NULL;
+        s->value.free = NULL;
+    }
+}
+
+UIWIDGET ui_space_deprecated(UiObject *obj) {
     GtkWidget *widget = gtk_label_new("");
     UiContainer *ct = uic_get_current_container(obj);
     ct->add(ct, widget, TRUE);
@@ -75,8 +155,8 @@
     return widget;
 }
 
-UIWIDGET ui_separator(UiObject *obj) {
-#if UI_GTK3
+UIWIDGET ui_separator_deprecated(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 3
     GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
 #else
     GtkWidget *widget = gtk_hseparator_new();
@@ -89,40 +169,97 @@
 
 /* ------------------------- progress bar ------------------------- */
 
-UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = value;
-    var->type = UI_VAR_SPECIAL;
-    return ui_progressbar_var(obj, var);
-}
+typedef struct UiProgressBarRange {
+    double min;
+    double max;
+} UiProgressBarRange;
 
-UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
-    return ui_progressbar_var(obj, var);
-}
-
-UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var) {
+UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
     GtkWidget *progressbar = gtk_progress_bar_new();
+    if(args.max > args.min) {
+        UiProgressBarRange *range = malloc(sizeof(UiProgressBarRange));
+        range->min = args.min;
+        range->max = args.max;
+        g_signal_connect(
+                progressbar,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                range);
+        g_object_set_data(G_OBJECT(progressbar), "ui_range", range);
+    }
+    
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
     if(var && var->value) {
         UiDouble *value = var->value;
         value->get = ui_progressbar_get;
         value->set = ui_progressbar_set;
         value->obj = progressbar;
-        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 0.5);
+        ui_progressbar_set(value, value->value);
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, progressbar, FALSE);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, progressbar, FALSE);
     
     return progressbar;
 }
 
 double ui_progressbar_get(UiDouble *d) {
-    d->value = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+    UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+    double fraction = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+    if(range) {
+        fraction = range->min + (range->max - range->min) * fraction;
+    }
+    d->value = fraction;
     return d->value;
 }
 
 void ui_progressbar_set(UiDouble *d, double value) {
+    d->value = value;
+    UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+    if(range) {
+        value = (value - range->min) / (range->max - range->min);
+    }
     gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value);
-    d->value = value;
 }
+
+
+/* ------------------------- progress spinner ------------------------- */
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *spinner = gtk_spinner_new();
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    if(var && var->value) {
+        UiInteger *value = var->value;
+        value->get = ui_spinner_get;
+        value->set = ui_spinner_set;
+        value->obj = spinner;
+        ui_spinner_set(value, value->value);
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, spinner, FALSE);
+    
+    return spinner;
+}
+
+int64_t ui_spinner_get(UiInteger *i) {
+    return i->value;
+}
+
+void ui_spinner_set(UiInteger *i, int64_t value) {
+    i->value = value;
+    if(i->obj) {
+        GtkSpinner *spinner = GTK_SPINNER(i->obj);
+        if(value != 0) {
+            gtk_spinner_start(spinner);
+        } else {
+            gtk_spinner_stop(spinner);
+        }
+    }
+}
--- a/ui/gtk/display.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/display.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,9 +36,14 @@
 extern "C" {
 #endif
 
+char* ui_label_get(UiString *s);
+void ui_label_set(UiString *s, const char *value);
+    
 UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
 double ui_progressbar_get(UiDouble *d);
 void ui_progressbar_set(UiDouble *d, double value);
+int64_t ui_spinner_get(UiInteger *i);
+void ui_spinner_set(UiInteger *i, int64_t value);
 
 #ifdef	__cplusplus
 }
--- a/ui/gtk/dnd.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/dnd.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,20 +31,21 @@
 #include <string.h>
 
 #include "dnd.h"
-#include <ucx/buffer.h>
+#include <cx/buffer.h>
+#include <cx/array_list.h>
 
 #ifdef UI_GTK2LEGACY
 static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
-    UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer *buf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     char *uri;
     int i = 0;
     while((uri = uris[i]) != NULL) {
-        ucx_buffer_puts(buf, uri);
-        ucx_buffer_puts(buf, "\r\n");
+        cxBufferPutString(buf, uri);
+        cxBufferPutString(buf, "\r\n");
     }
     GdkAtom type = gdk_atom_intern("text/uri-list", FALSE);
     gtk_selection_data_set(selection_data, type, 8, (guchar*)buf->space, buf->pos);
-    ucx_buffer_free(buf);
+    cxBufferFree(buf);
     return TRUE;
 }
 static char** selection_data_get_uris(GtkSelectionData *selection_data) {
@@ -55,6 +56,7 @@
 #define gtk_selection_data_get_uris selection_data_get_uris
 #endif
 
+/*
 void ui_selection_settext(UiSelection *sel, char *str, int len) {
     // TODO: handle error?
     gtk_selection_data_set_text(sel->data, str, len);
@@ -99,3 +101,195 @@
     }
     return NULL;
 }
+*/
+
+#if GTK_MAJOR_VERSION >= 4
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    if(!sel->providers) {
+        return;
+    }
+    
+    if(len == -1) {
+        len = strlen(str);
+    }
+    GBytes *bytes = g_bytes_new(str, len);
+    GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes);
+    g_bytes_unref(bytes);
+    
+    cxListAdd(sel->providers, &provider);
+}
+
+void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+    if(!sel->providers) {
+        return;
+    }
+    
+    GFile **files = calloc(nelm, sizeof(GFile*));
+    for(int i=0;i<nelm;i++) {
+        GFile *file = uris[i][0] == '/' ? g_file_new_for_path(uris[i]) : g_file_new_for_uri(uris[i]);
+        files[i] = file;
+    }
+    GdkFileList *list = gdk_file_list_new_from_array(files, nelm);
+    
+    GdkContentProvider *provider = gdk_content_provider_new_typed(GDK_TYPE_FILE_LIST, list);
+    cxListAdd(sel->providers, &provider);
+    
+    g_slist_free_full ((GSList*)list, g_object_unref);
+    free(files);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->value) {
+        return NULL;
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) {
+        const char *str = g_value_get_string(sel->value);
+        return str ? strdup(str) : NULL;
+    }
+    
+    return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->value) {
+        return (UiFileList){NULL,0};
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) {
+        GSList *list = g_value_get_boxed(sel->value);
+        if(!list) {
+            return (UiFileList){NULL,0};
+        }
+        guint size = g_slist_length(list);
+        
+        UiFileList flist;
+        flist.nfiles = size;
+        flist.files = calloc(size, sizeof(char*));
+        int i=0;
+        while(list) {
+            GFile *file = list->data;
+            char *uri = g_file_get_uri(file);
+            flist.files[i++] = strdup(uri);
+            g_free(uri);
+            list = list->next;
+        }
+        return flist;
+    }
+    return (UiFileList){NULL,0};
+}
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16);
+    dnd->selected_action = 0;
+    dnd->delete = FALSE;
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    cxListDestroy(dnd->providers);
+    free(dnd);
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+#else
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+    char **uriarray = calloc(nelm+1, sizeof(char*));
+    for(int i=0;i<nelm;i++) {
+        uriarray[i] = uris[i];
+    }
+    uriarray[nelm] = NULL;
+    gtk_selection_data_set_uris(sel->data, uriarray);
+    free(uriarray);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->data) {
+        return NULL;
+    }
+    
+    guchar *text = gtk_selection_data_get_text(sel->data);
+    if(text) {
+        char *textcp = strdup((char*)text);
+        g_free(text);
+        return textcp;
+    }
+    return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->data) {
+        return (UiFileList){NULL,0};
+    }
+    
+    gchar **uris = gtk_selection_data_get_uris(sel->data);
+    if(uris) {
+        size_t al = 32;
+        char **array = malloc(al * sizeof(char*));
+        size_t i = 0;
+        while(uris[i] != NULL) {
+            if(i >= al) {
+                al *= 2;
+                array = realloc(array, al * sizeof(char*));
+            }
+            array[i] = strdup((char*)uris[i]);
+            i++;
+        }
+        g_strfreev(uris);
+        return (UiFileList){array,i};
+    }
+    
+    return (UiFileList){NULL,0};
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    free(dnd);
+}
+
+#endif
+
+UiBool ui_dnd_need_delete(UiDnD *dnd) {
+    return dnd->delete;
+}
+
+void ui_dnd_accept(UiDnD *dnd, UiBool accept) {
+    dnd->accept = accept;
+}
+
--- a/ui/gtk/dnd.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/dnd.h	Sat Jan 04 16:38:48 2025 +0100
@@ -32,12 +32,37 @@
 #include "../ui/dnd.h"
 #include "toolkit.h"
 
+#include <cx/list.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#if GTK_MAJOR_VERSION >= 4
+    
+struct UiDnD {
+    GtkDropTarget *target;
+    const GValue *value;
+    CxList *providers;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
+    
+#else
+   
+struct UiDnD {
+    GdkDragContext *context;
+    GtkSelectionData *data;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
 
+#endif
 
+UiDnD* ui_create_dnd(void);
+void ui_dnd_free(UiDnD *dnd);
 
 #ifdef __cplusplus
 }
--- a/ui/gtk/draw_cairo.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/draw_cairo.c	Sat Jan 04 16:38:48 2025 +0100
@@ -33,12 +33,19 @@
 
 #include "draw_cairo.h"
 
-#ifdef UI_GTK3
-gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+
+#if GTK_MAJOR_VERSION >= 3
+static void ui_drawingarea_draw(
+        GtkDrawingArea *area,
+        cairo_t *cr,
+        int width,
+        int height,
+        gpointer data)
+{
     UiCairoGraphics g;
-    g.g.width = gtk_widget_get_allocated_width(w);
-    g.g.height = gtk_widget_get_allocated_height(w);
-    g.widget = w;
+    g.g.width = width;
+    g.g.height = height;
+    g.widget = GTK_WIDGET(area);
     g.cr = cr;
     
     UiDrawEvent *event = data;
@@ -48,10 +55,18 @@
     ev.document = event->obj->ctx->document;
     
     event->callback(&ev, &g.g, event->userdata);
-    
+}
+#endif
+
+#if UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+    int width = gtk_widget_get_allocated_width(w);
+    int height = gtk_widget_get_allocated_height(w);
+    ui_drawingarea_draw(GTK_DRAWING_AREA(w), cr, width, height, data);
     return FALSE;
 }
-#else
+#endif
+#ifdef UI_GTK2
 gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
     UiCairoGraphics g;
     g.g.width = w->allocation.width;
@@ -74,7 +89,9 @@
 // function from graphics.h
 
 void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 4
+    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), ui_drawingarea_draw, event, NULL);
+#elif GTK_MAJOR_VERSION == 3
     g_signal_connect(G_OBJECT(widget),
             "draw",
             G_CALLBACK(ui_drawingarea_expose),
--- a/ui/gtk/entry.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/entry.c	Sat Jan 04 16:38:48 2025 +0100
@@ -34,67 +34,49 @@
 #include "container.h"
 #include "entry.h"
 
-#include <ucx/mempool.h>
 
-UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = i;
-    var->type = UI_VAR_SPECIAL;
-    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
-}
-
-UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = d;
-    var->type = UI_VAR_SPECIAL;
-    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
-}
-
-UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = r;
-    var->type = UI_VAR_SPECIAL;
-    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
-}
-
-UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
-    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
-}
-
-UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
-    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
-}
-
-UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_RANGE);
-    UiRange *r = var->value;
-    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
-}
-
-UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type) {
+UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args) {
     double min = 0;
     double max = 1000;
-    if(type == UI_VAR_RANGE) {
+    
+    UiObject* current = uic_current_obj(obj);
+    
+    UiVar *var = NULL;
+    if(args.varname) {
+        var = uic_get_var(obj->ctx, args.varname);
+    }
+    
+    if(!var) {
+        if(args.intvalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.intvalue, NULL, UI_VAR_INTEGER);
+        } else if(args.doublevalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.doublevalue, NULL, UI_VAR_DOUBLE);
+        } else if(args.rangevalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.rangevalue, NULL, UI_VAR_RANGE);
+        }
+    }
+    
+    if(var && var->type == UI_VAR_RANGE) {
         UiRange *r = var->value;
         min = r->min;
         max = r->max;
     }
-    if(step == 0) {
-        step = 1;
+    if(args.step == 0) {
+        args.step = 1;
     }
 #ifdef UI_GTK2LEGACY
     if(min == max) {
         max = min + 1;
     }
 #endif
-    GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step);
-    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
+    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);
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args.digits);
+    UiObserver **obs = NULL;
     if(var) {
         double value = 0;
-        UiObserver **obs = NULL;
-        switch(type) {
+        switch(var->type) {
             default: break;
             case UI_VAR_INTEGER: {
                 UiInteger *i = var->value;
@@ -127,26 +109,28 @@
             }
         }
         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
-        
-        UiVarEventData *event = malloc(sizeof(UiVarEventData));
-        event->obj = obj;
-        event->var = var;
-        event->observers = obs;
-        
-        g_signal_connect(
-                spin,
-                "value-changed",
-                G_CALLBACK(ui_spinner_changed),
-                event);
-        g_signal_connect(
-                spin,
-                "destroy",
-                G_CALLBACK(ui_destroy_vardata),
-                event);
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, spin, FALSE);
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->var = var;
+    event->observers = obs;
+    event->callback = args.onchange;
+    event->userdata = args.onchangedata;
+
+    g_signal_connect(
+            spin,
+            "value-changed",
+            G_CALLBACK(ui_spinner_changed),
+            event);
+    g_signal_connect(
+            spin,
+            "destroy",
+            G_CALLBACK(ui_destroy_vardata),
+            event);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, spin, FALSE);
     
     return spin;
 }
@@ -161,15 +145,22 @@
 
 
 void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) {
+    gdouble value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner));
     UiEvent e;
     e.obj = event->obj;
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
-    e.eventdata = event->var->value;
-    e.intval = 0;
+    e.eventdata = &value;
+    e.intval = (int64_t)value;
     
-    UiObserver *observer = *event->observers;
-    ui_notify_evt(observer, &e);
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(event->observers) {
+        UiObserver *observer = *event->observers;
+        ui_notify_evt(observer, &e);
+    }
 }
 
 
--- a/ui/gtk/entry.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/entry.h	Sat Jan 04 16:38:48 2025 +0100
@@ -22,7 +22,6 @@
 extern "C" {
 #endif
 
-UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type);
 void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event);
 
 int64_t ui_spinbutton_getint(UiInteger *i);
--- a/ui/gtk/graphics.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/graphics.c	Sat Jan 04 16:38:48 2025 +0100
@@ -51,6 +51,7 @@
 }
 
 
+#if GTK_MAJOR_VERSION <= 3
 static gboolean widget_button_pressed(
         GtkWidget *widget,
         GdkEvent *event,
@@ -82,14 +83,18 @@
     }
     return TRUE;
 }
+#endif
 
 void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
-#ifdef UI_GTK3
-        *width = gtk_widget_get_allocated_width(drawingarea);
-        *height = gtk_widget_get_allocated_height(drawingarea);
+#if GTK_MAJOR_VERSION >= 4
+    *width = gtk_widget_get_width(drawingarea);
+    *height = gtk_widget_get_height(drawingarea);
+#elif GTK_MAJOR_VERSION == 3
+    *width = gtk_widget_get_allocated_width(drawingarea);
+    *height = gtk_widget_get_allocated_height(drawingarea);
 #else
-        *width = drawingarea->allocation.width;
-        *height = drawingarea->allocation.height;
+    *width = drawingarea->allocation.width;
+    *height = drawingarea->allocation.height;
 #endif
 }
 
@@ -98,12 +103,17 @@
 }
 
 void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO
+#else
     gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK);
     if(f) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
         event->callback = f;
         event->userdata = u;
+        event->customdata = NULL;
+        event->value = 0;
         
         g_signal_connect(G_OBJECT(widget),
                 "button-press-event",
@@ -112,6 +122,7 @@
     } else {
          // TODO: warning
     }
+#endif
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/headerbar.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,174 @@
+/*
+ * 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 "headerbar.h"
+
+#include "button.h"
+#include "menu.h"
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) {
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT);
+    ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER);
+    
+    UiToolbarMenuItem *appmenu = uic_get_appmenu();
+    if(appmenu) {
+        ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
+    }
+    ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT);
+}
+
+static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) {
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            ui_add_headerbar_item(headerbar, box, (UiToolbarItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            ui_add_headerbar_toggleitem(headerbar, box, (UiToolbarToggleItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            ui_add_headerbar_menu(headerbar, box, (UiToolbarMenuItem*)i, obj, pos);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+static void headerbar_add(GtkWidget *headerbar, GtkWidget *box, GtkWidget *item, enum UiToolbarPos pos) {
+    switch(pos) {
+        case UI_TOOLBAR_LEFT: {
+            UI_HEADERBAR_PACK_START(headerbar, item);
+            break;
+        }
+        case UI_TOOLBAR_CENTER: {
+            
+#if GTK_MAJOR_VERSION >= 4
+            gtk_box_append(GTK_BOX(box), item);
+#else
+            gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0);
+#endif
+            break;
+        }
+        case UI_TOOLBAR_RIGHT: {
+            UI_HEADERBAR_PACK_END(headerbar, item);
+            break;
+        }
+    }
+}
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos) {
+    GtkWidget *box = NULL;
+    
+    if(pos == UI_TOOLBAR_CENTER && cxListSize(items) > 0) {
+        box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+        UI_HEADERBAR_SET_TITLE_WIDGET(headerbar, box);
+    }
+    
+    CxIterator i = pos == UI_TOOLBAR_RIGHT ? cxListBackwardsIterator(items) : cxListIterator(items);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        create_item(obj, headerbar, box, item, pos);
+    }
+}
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE);
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    WIDGET_ADD_CSS_CLASS(button, "flat");
+    headerbar_add(headerbar, box, button, pos);
+}
+
+void ui_add_headerbar_toggleitem(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarToggleItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *button = gtk_toggle_button_new();
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    WIDGET_ADD_CSS_CLASS(button, "flat");
+    ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0);
+    headerbar_add(headerbar, box, button, pos);
+}
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    
+    
+#if GTK_MAJOR_VERSION >= 4
+    GtkWidget *menubutton = gtk_menu_button_new();
+    if(item->args.label) {
+        gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label);
+    }
+    if(item->args.icon) {
+        gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); 
+    }
+    
+    if(!item->args.label && !item->args.icon) {
+        gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), "open-menu-symbolic"); 
+    }
+    
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, &item->menu, obj);
+    
+    gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu));
+#else
+    GtkWidget *menubutton = gtk_menu_button_new();
+    
+    // TODO
+    
+    
+#endif
+    
+    headerbar_add(headerbar, box, menubutton, pos);
+}
+    
+#endif // GTK_CHECK_VERSION(3, 10, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/headerbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#ifndef HEADERBAR_H
+#define HEADERBAR_H
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+    
+#ifdef UI_LIBADWAITA
+#define UI_HEADERBAR AdwHeaderBar*
+#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h)
+#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)
+#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)
+#if GTK_MAJOR_VERSION >= 4
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w)
+#endif
+#endif
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar);
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_toggleitem(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarToggleItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+    
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HEADERBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/icon.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,208 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cx/map.h>
+
+#include "toolkit.h"
+#include "icon.h"
+#include "../common/properties.h"
+
+static CxMap *image_map;
+
+static GtkIconTheme *icon_theme;
+
+#if GTK_MAJOR_VERSION >= 4
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_for_display(gdk_display_get_default())
+#else
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_default()
+#endif
+
+void ui_image_init(void) {
+    image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    
+    icon_theme = ICONTHEME_GET_DEFAULT();
+}
+
+// **** deprecated functions ****
+
+GdkPixbuf* ui_get_image(const char *name) {
+    UiImage *img = cxMapGet(image_map, name);
+    if(img) {
+        return img->pixbuf;
+    } else {
+        //ui_add_image(name, name);
+        //return ucx_map_cstr_get(image_map, name);
+        // TODO
+        return NULL;
+    }
+}
+
+// **** deprecated2****
+
+static UiIcon* get_icon(const char *name, int size, int scale) {
+#if GTK_MAJOR_VERSION >= 4
+    GtkIconPaintable *info = gtk_icon_theme_lookup_icon(icon_theme, name, NULL, size, scale, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR);
+#elif defined(UI_SUPPORTS_SCALE)
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
+#else
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+#endif
+    if(info) {
+        UiIcon *icon = malloc(sizeof(UiIcon));
+        icon->info = info;
+        icon->pixbuf = NULL;
+        return icon;
+    }
+    return NULL;
+}
+
+UiIcon* ui_icon(const char* name, size_t size) {
+    return get_icon(name, size, ui_get_scalefactor());
+}
+
+UiIcon* ui_imageicon(const char* file) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(file, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", file);
+        return NULL;
+    }
+    
+    UiIcon *icon = malloc(sizeof(UiIcon));
+    icon->info = NULL;
+    icon->pixbuf = pixbuf;
+    return icon;
+}
+
+void ui_icon_free(UiIcon* icon) {
+    if(icon->info) {
+        g_object_unref(icon->info);
+    }
+    if(icon->pixbuf) {
+        g_object_unref(icon->pixbuf);
+    }
+    free(icon);
+}
+
+UiIcon* ui_foldericon(size_t size) {
+    return ui_icon("folder", size);
+}
+
+UiIcon* ui_fileicon(size_t size) {
+    UiIcon *icon = ui_icon("file", size);
+#if GTK_MAJOR_VERSION >= 4
+    GFile *file = gtk_icon_paintable_get_file(icon->info);
+    char *path = g_file_get_path(file);
+    if(!path) {
+        icon = ui_icon("application-x-generic", size);
+    }
+#else
+    if(!icon) {
+        icon = ui_icon("application-x-generic", size);
+    }
+#endif
+    return icon;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+    return get_icon(name, size, 1);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+    if(!icon->pixbuf) {
+        GFile *file = gtk_icon_paintable_get_file(icon->info);
+        GError *error = NULL;
+        char *path = g_file_get_path(file);
+        icon->pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    }
+    return icon->pixbuf;
+}
+#else
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+    if(!icon->pixbuf) {
+        GError *error = NULL;
+        icon->pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    }
+    return icon->pixbuf;
+}
+#endif
+
+/*
+UiImage* ui_icon_image(UiIcon *icon) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    if(pixbuf) {
+        UiImage *img = malloc(sizeof(UiImage));
+        img->pixbuf = pixbuf;
+        return img;
+    }
+    return NULL;
+}
+*/
+
+/*
+UiImage* ui_image(const char *filename) {
+    return ui_named_image(filename, NULL);
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+    char *path =  uic_get_image_path(filename);
+    if(!path) {
+        fprintf(stderr, "UiError: pixmaps directory not set\n");
+        return NULL;
+    }
+    UiImage *img = ui_load_image_from_path(path, name);
+    free(path);
+    return img;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", path);
+        return NULL;
+    }
+    
+    UiImage *img = malloc(sizeof(UiImage));
+    img->pixbuf = pixbuf;
+    if(name) {
+        cxMapPut(image_map, name, img);
+    }
+    return img;
+}
+*/
+
+void ui_free_image(UiImage *img) {
+    g_object_unref(img->pixbuf);
+    free(img);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/icon.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef ICON_H
+#define	ICON_H
+
+#include "../ui/icons.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+    
+struct UiIcon {
+#if GTK_MAJOR_VERSION >= 4
+    GtkIconPaintable *info;
+#else
+    GtkIconInfo *info;
+#endif
+    GdkPixbuf *pixbuf;
+};
+
+struct UiImage {
+    GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* ICON_H */
+
--- a/ui/gtk/image.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/image.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -26,111 +26,110 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ucx/map.h>
+#include "image.h"
+
+#include "container.h"
+#include "menu.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
 
-#include "toolkit.h"
-#include "image.h"
-#include "../common/properties.h"
-
-static UcxMap *image_map;
-
-static GtkIconTheme *icon_theme;
-
-void ui_image_init(void) {
-    image_map = ucx_map_new(8);
+UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
+    UiObject *current = uic_current_obj(obj);
+    
+    GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW();
+#if GTK_CHECK_VERSION(4, 0, 0)
+    GtkWidget *image = gtk_picture_new();
+#else
+    GtkWidget *image = gtk_image_new();
+#endif
+    
+    ui_set_name_and_style(image, args.name, args.style_class);
     
-    icon_theme = gtk_icon_theme_get_default();
-}
-
-// **** deprecated functions ****
-
-GdkPixbuf* ui_get_image(const char *name) {
-    UiImage *img = ucx_map_cstr_get(image_map, name);
-    if(img) {
-        return img->pixbuf;
-    } else {
-        //ui_add_image(name, name);
-        //return ucx_map_cstr_get(image_map, name);
-        // TODO
-        return NULL;
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *eventbox = gtk_event_box_new();
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
+    gtk_container_add(GTK_CONTAINER(eventbox), image);
+#else
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
+    GtkWidget *eventbox = image;
+#endif
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scrolledwindow, TRUE);
+    
+    UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
+    if(var) {
+        UiGeneric *value = var->value;
+        value->get = ui_imageviewer_get;
+        value->get_type = ui_imageviewer_get_type;
+        value->set = ui_imageviewer_set;
+        value->obj = image;
+        if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) {
+            GdkPixbuf *pixbuf = value->value;
+            value->value = NULL;
+            ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE);
+        }
     }
+    
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
+        ui_widget_set_contextmenu(eventbox, menu);
+    }
+    
+    return scrolledwindow;
 }
 
-// **** new functions ****
-
-static UiIcon* get_icon(const char *name, int size, int scale) {
-#ifdef UI_SUPPORTS_SCALE
-    GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
-#else
-    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
-#endif
-    if(info) {
-        UiIcon *icon = malloc(sizeof(UiIcon));
-        icon->info = info;
-        return icon;
-    }
-    return NULL;
+void* ui_imageviewer_get(UiGeneric *g) {
+    return g->value;
 }
 
-UiIcon* ui_icon(const char *name, int size) {
-    return get_icon(name, size, ui_get_scalefactor());
-}
-
-UiIcon* ui_icon_unscaled(const char *name, int size) {
-    return get_icon(name, size, 1);
+const char* ui_imageviewer_get_type(UiGeneric *g) {
+    
 }
 
-void ui_free_icon(UiIcon *icon) {
-    g_object_unref(icon->info);
-    free(icon);
-}
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
+    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
+        return 1;
+    }
+    
+    // TODO: do we need to free the previous value here?
+    
+    g->value = value;
+    g->type = type;
+    GdkPixbuf *pixbuf = value;
+    
+    if(pixbuf) {
+        int width = gdk_pixbuf_get_width(pixbuf);
+        int height = gdk_pixbuf_get_height(pixbuf);
+        
+#if GTK_CHECK_VERSION(4, 0, 0)
+        GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
+        gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
+#else
+        gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf);
+#endif
+        gtk_widget_set_size_request(g->obj, width, height);
+    }
 
-UiImage* ui_icon_image(UiIcon *icon) {
-    GError *error = NULL;
-    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
-    if(pixbuf) {
-        UiImage *img = malloc(sizeof(UiImage));
-        img->pixbuf = pixbuf;
-        return img;
-    }
-    return NULL;
+    
+    return 0;
 }
 
-UiImage* ui_image(const char *filename) {
-    return ui_named_image(filename, NULL);
-}
+
 
-UiImage* ui_named_image(const char *filename, const char *name) {
-    char *path =  uic_get_image_path(filename);
-    if(!path) {
-        fprintf(stderr, "UiError: pixmaps directory not set\n");
-        return NULL;
-    }
-    UiImage *img = ui_load_image_from_path(path, name);
-    free(path);
-    return img;
-}
-
-UiImage* ui_load_image_from_path(const char *path, const char *name) {
+int ui_image_load_file(UiGeneric *obj, const char *path) {
     GError *error = NULL;
     GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
     if(!pixbuf) {
-        fprintf(stderr, "UiError: Cannot load image: %s\n", path);
-        return NULL;
+        return 1;
     }
     
-    UiImage *img = malloc(sizeof(UiImage));
-    img->pixbuf = pixbuf;
-    if(name) {
-        ucx_map_cstr_put(image_map, name, img);
+    if(obj->set) {
+        obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
+    } else {
+        obj->value = pixbuf;
     }
-    return img;
+    
+    return 0;
 }
-
-void ui_free_image(UiImage *img) {
-    g_object_unref(img->pixbuf);
-    free(img);
-}
--- a/ui/gtk/image.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/image.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2017 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -27,35 +27,23 @@
  */
 
 #ifndef IMAGE_H
-#define	IMAGE_H
+#define IMAGE_H
 
 #include "../ui/image.h"
+#include "toolkit.h"
 
-#ifdef	__cplusplus
+#ifdef __cplusplus
 extern "C" {
 #endif
 
-#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
-#define UI_SUPPORTS_SCALE
-#endif
-
-    
-struct UiIcon {
-    GtkIconInfo *info;
-};
 
-struct UiImage {
-    GdkPixbuf *pixbuf;
-};
+void* ui_imageviewer_get(UiGeneric *g);
+const char* ui_imageviewer_get_type(UiGeneric *g);
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type);
 
-void ui_image_init(void);
-
-GdkPixbuf* ui_get_image(const char *name);
-
-
-#ifdef	__cplusplus
+#ifdef __cplusplus
 }
 #endif
 
-#endif	/* IMAGE_H */
+#endif /* IMAGE_H */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/list.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,1278 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+
+#include <cx/array_list.h>
+#include <cx/linked_list.h>
+
+#include "list.h"
+#include "icon.h"
+#include "menu.h"
+#include "dnd.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
+}
+
+static GtkListStore* create_list_store(UiList *list, UiModel *model) {
+    int columns = model->columns;
+    GType types[2*columns];
+    int c = 0;
+    for(int i=0;i<columns;i++,c++) {
+        switch(model->types[i]) {
+            case UI_STRING: 
+            case UI_STRING_FREE: types[c] = G_TYPE_STRING; break;
+            case UI_INTEGER: types[c] = G_TYPE_INT; break;
+            case UI_ICON: types[c] = G_TYPE_OBJECT; break;
+            case UI_ICON_TEXT: 
+            case UI_ICON_TEXT_FREE: {
+                types[c] = G_TYPE_OBJECT;
+                types[++c] = G_TYPE_STRING;
+            }
+        }
+    }
+    
+    GtkListStore *store = gtk_list_store_newv(c, types);
+    
+    if(list) {
+        void *elm = list->first(list);
+	while(elm) {
+            // insert new row
+            GtkTreeIter iter;
+            gtk_list_store_insert (store, &iter, -1);
+            
+            // set column values
+            int c = 0;
+            for(int i=0;i<columns;i++,c++) {
+                void *data = model->getvalue(elm, c);
+                
+                GValue value = G_VALUE_INIT;
+                switch(model->types[i]) {
+                    case UI_STRING: 
+                    case UI_STRING_FREE: {
+                        g_value_init(&value, G_TYPE_STRING);
+                        g_value_set_string(&value, data);
+                        if(model->types[i] == UI_STRING_FREE) {
+                            free(data);
+                        }
+                        break;
+                    }
+                    case UI_INTEGER: {
+                        g_value_init(&value, G_TYPE_INT);
+                        int *intptr = data;
+                        g_value_set_int(&value, *intptr);
+                        break;
+                    }
+                    case UI_ICON: {
+                        g_value_init(&value, G_TYPE_OBJECT);
+                        UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                        g_value_set_object(&value, icon->info); // TODO: does this work?
+#else
+                        if(!icon->pixbuf && icon->info) {
+                            GError *error = NULL;
+                            GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                            icon->pixbuf = pixbuf;
+                        }
+                        
+                        if(icon->pixbuf) {
+                            g_value_set_object(&value, icon->pixbuf);
+                        }
+#endif
+                        break;
+                    }
+                    case UI_ICON_TEXT:
+                    case UI_ICON_TEXT_FREE: {
+                        UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                        if(icon) {
+                            GValue iconvalue = G_VALUE_INIT;
+                            g_value_init(&iconvalue, G_TYPE_OBJECT);
+                            g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
+                            gtk_list_store_set_value(store, &iter, c, &iconvalue);
+                        }
+#else
+                        GValue pixbufvalue = G_VALUE_INIT;
+                        if(icon) {
+                            if(!icon->pixbuf && icon->info) {
+                                GError *error = NULL;
+                                GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                                icon->pixbuf = pixbuf;
+                            }
+                            g_value_init(&pixbufvalue, G_TYPE_OBJECT);
+                            g_value_set_object(&pixbufvalue, icon->pixbuf);
+                            gtk_list_store_set_value(store, &iter, c, &pixbufvalue);
+                        }
+#endif
+                        c++;
+                        
+                        char *str = model->getvalue(elm, c);
+                        g_value_init(&value, G_TYPE_STRING);
+                        g_value_set_string(&value, str);
+                        if(model->types[i] == UI_ICON_TEXT_FREE) {
+                            free(str);
+                        }
+                        break;
+                    }
+                }
+                
+                gtk_list_store_set_value(store, &iter, c, &value);
+            }
+            
+            // next row
+            elm = list->next(list);
+        }
+    }
+    
+    return store;
+}
+
+
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    // 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);
+    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);
+    
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+#if GTK_MINOR_VERSION >= 8
+    //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    // TODO: implement for older gtk3
+#endif
+#else
+    // TODO: implement for gtk2
+#endif
+    
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
+    UiListView *listview = malloc(sizeof(UiListView));
+    listview->obj = obj;
+    listview->widget = view;
+    listview->var = var;
+    listview->model = model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
+    list->obj = listview;
+    
+    // add callback
+    UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = args.onactivate;
+    event->activatedata = args.onactivatedata;
+    event->selection = args.onselection;
+    event->selectiondata = args.onselectiondata;
+    g_signal_connect(
+            view,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    if(args.onactivate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(args.onselection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view);
+        ui_widget_set_contextmenu(view, menu);
+    }
+    
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, FALSE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    current->container->current = view;
+    
+    return scroll_area;
+}
+
+/*
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+    printf("drag begin\n");
+    
+}
+
+static void drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer udata)
+{
+    printf("drag end\n");
+    
+}
+*/
+
+/*
+static GtkTargetEntry targetentries[] =
+    {
+      { "STRING",        0, 0 },
+      { "text/plain",    0, 1 },
+      { "text/uri-list", 0, 2 },
+    };
+*/
+
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    
+    UiModel *model = args.model;
+    int columns = model ? model->columns : 0;
+    
+    int addi = 0;
+    for(int i=0;i<columns;i++) {
+        GtkTreeViewColumn *column = NULL;
+        if(model->types[i] == UI_ICON_TEXT) {
+            column = gtk_tree_view_column_new();
+            gtk_tree_view_column_set_title(column, model->titles[i]);
+            
+            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+            GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
+            
+            gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
+            gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
+            
+            
+            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
+            gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+            
+            addi++;
+        } else if (model->types[i] == UI_ICON) {
+            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                iconrenderer,
+                "pixbuf",
+                i + addi,
+                NULL);
+        } else {
+            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                renderer,
+                "text",
+                i + addi,
+                NULL);
+        }
+        
+        int colsz = model->columnsize[i];
+        if(colsz > 0) {
+            gtk_tree_view_column_set_fixed_width(column, colsz);
+        } else if(colsz < 0) {
+            gtk_tree_view_column_set_expand(column, TRUE);
+        }
+        
+        gtk_tree_view_column_set_resizable(column, TRUE);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+    }
+    
+    //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+    //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    
+#endif
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
+    //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+    //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+       
+    // add TreeView as observer to the UiList to update the TreeView if the
+    // data changes
+    UiListView *tableview = malloc(sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->widget = view;
+    tableview->var = var;
+    tableview->model = model;
+    tableview->ondragstart = args.ondragstart;
+    tableview->ondragstartdata = args.ondragstartdata;
+    tableview->ondragcomplete = args.ondragcomplete;
+    tableview->ondragcompletedata = args.ondragcompletedata;
+    tableview->ondrop = args.ondrop;
+    tableview->ondropdata = args.ondropsdata;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                tableview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
+    list->obj = tableview;
+    
+    // add callback
+    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = args.onactivate;
+    event->selection = args.onselection;
+    event->activatedata = args.onactivatedata;
+    event->selectiondata = args.onselectiondata;
+    if(args.onactivate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(args.onselection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    // TODO: destroy callback
+    
+    
+    if(args.ondragstart) {
+        ui_listview_add_dnd(tableview, &args);
+    }
+    if(args.ondrop) {
+        ui_listview_enable_drop(tableview, &args);
+    }
+      
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+    if(args.multiselection) {
+        gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+    }
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+    
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area);
+#if GTK_MAJOR_VERSION >= 4
+        ui_widget_set_contextmenu(scroll_area, menu);
+#else
+        ui_widget_set_contextmenu(view, menu);
+#endif
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, FALSE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    current->container->current = view;
+    
+    return scroll_area;
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
+    //printf("drag prepare\n");
+    UiListView *listview = data;
+    
+    UiDnD *dnd = ui_create_dnd();
+    GdkContentProvider *provider = NULL;
+    
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+    
+    size_t numproviders = cxListSize(dnd->providers);
+    if(numproviders > 0) {
+        GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0);
+        provider = gdk_content_provider_new_union(providers, numproviders);
+    }
+    ui_dnd_free(dnd);
+    
+    return provider;
+}
+
+static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) {
+    //printf("drag begin\n");
+}
+
+static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) {
+    //printf("drag end\n");
+    UiListView *listview = user_data;
+    if(listview->ondragcomplete) {
+        UiDnD dnd;
+        dnd.target = NULL;
+        dnd.value = NULL;
+        dnd.providers = NULL;
+        dnd.selected_action = gdk_drag_get_selected_action(drag);
+        dnd.delete = delete_data;
+        dnd.accept = FALSE;
+        
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+static gboolean ui_listview_drop(
+        GtkDropTarget *target,
+        const GValue* value,
+        gdouble x,
+        gdouble y,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.providers = NULL;
+    dnd.target = target;
+    dnd.value = value;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+    
+    return dnd.accept;
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    GtkDragSource *dragsource = gtk_drag_source_new();
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource));
+    g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview);
+    g_signal_connect(
+            dragsource,
+            "drag-begin",
+            G_CALLBACK(ui_listview_drag_begin),
+            listview);
+    g_signal_connect(
+            dragsource,
+            "drag-end",
+            G_CALLBACK(ui_listview_drag_end),
+            listview);
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target));
+    GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING };
+    gtk_drop_target_set_gtypes(target, default_types, 2);
+    g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview);
+}
+
+#else
+
+static GtkTargetEntry targetentries[] =
+{
+    { "STRING",        0, 0 },
+    { "text/plain",    0, 1 },
+    { "text/uri-list", 0, 2 },
+};
+
+static void ui_listview_drag_getdata(
+        GtkWidget* self,
+        GdkDragContext* context,
+        GtkSelectionData* data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+}
+
+static void ui_listview_drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = NULL;
+    dnd.selected_action = gdk_drag_context_get_selected_action(context);
+    dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE;
+    dnd.accept = FALSE;
+    if(listview->ondragcomplete) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(listview->widget),
+            GDK_BUTTON1_MASK,
+            targetentries,
+            2,
+            GDK_ACTION_COPY);
+    
+    g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview);
+    g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview);
+}
+
+
+
+
+static void ui_listview_drag_data_received(
+        GtkWidget *self,
+        GdkDragContext *context,
+        gint x,
+        gint y,
+        GtkSelectionData *data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(listview->widget),
+            targetentries,
+            3,
+            GDK_ACTION_COPY);
+    if(listview->ondrop) {
+        g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview);
+    }
+}
+
+#endif
+
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+    return SCROLLEDWINDOW_GET_CHILD(widget);
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+    int al = 16;
+    char **targets = calloc(16, sizeof(char*));
+    targets[0] = target0;
+    
+    int i = 1;
+    char *target;
+    while((target = va_arg(ap, char*)) != NULL) {
+        if(i >= al) {
+            al *= 2;
+            targets = realloc(targets, al*sizeof(char*));
+        }
+        targets[i] = target;
+        i++;
+    }
+    
+    *nelm = i;
+    return targets;
+}
+
+/*
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+    GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+    for(int i=0;i<nelm;i++) {
+        targets[i].target = str[i];
+    }
+    return targets;
+}
+*/
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    
+    // disabled
+    //ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+    
+    free(targets);
+}
+
+/*
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            GDK_BUTTON1_MASK,
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+*/
+ 
+void ui_listview_update(UiList *list, int i) {
+    UiListView *view = list->obj;
+    GtkListStore *store = create_list_store(list, view->model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
+    g_object_unref(G_OBJECT(store));
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+    UiListView *view = list->obj;
+    UiListSelection selection = ui_listview_selection(
+            gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)),
+            NULL);
+    return selection;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+    GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+    gtk_tree_selection_select_path(sel, path);
+    //g_object_unref(path);
+}
+
+void ui_listview_destroy(GtkWidget *w, UiListView *v) {
+    //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    free(v);
+}
+
+
+void ui_listview_activate_event(
+        GtkTreeView *treeview,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event)
+{
+    UiListSelection selection = ui_listview_selection(
+            gtk_tree_view_get_selection(treeview),
+            event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = &selection;
+    e.intval = selection.count > 0 ? selection.rows[0] : -1;
+    event->activate(&e, event->activatedata);
+    
+    if(selection.count > 0) {
+        free(selection.rows);
+    }
+}
+
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event)
+{
+    UiListSelection selection = ui_listview_selection(treeselection, event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = &selection;
+    e.intval = selection.count > 0 ? selection.rows[0] : -1;
+    event->selection(&e, event->selectiondata);
+    
+    if(selection.count > 0) {
+        free(selection.rows);
+    }
+}
+
+UiListSelection ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event)
+{
+    GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+    
+    UiListSelection ls;
+    ls.count = g_list_length(rows);
+    ls.rows = calloc(ls.count, sizeof(int));
+    GList *r = rows;
+    int i = 0;
+    while(r) {
+        GtkTreePath *path = r->data;
+        ls.rows[i] = ui_tree_path_list_index(path);
+        r = r->next;
+        i++;
+    }
+    return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+    int depth = gtk_tree_path_get_depth(path);
+    if(depth == 0) {
+        fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+        return -1;
+    }
+    int *indices = gtk_tree_path_get_indices(path);
+    return indices[depth - 1];
+}
+
+
+/* --------------------------- ComboBox ---------------------------  */
+
+UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
+    ui_set_name_and_style(combobox, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, combobox, args.groups);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, combobox, FALSE);
+    current->container->current = combobox;
+    return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
+    GtkWidget *combobox = gtk_combo_box_new();
+       
+    UiListView *uicbox = malloc(sizeof(UiListView));
+    uicbox->obj = obj;
+    uicbox->widget = combobox;
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    
+    if(listmodel) {
+        gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
+        g_object_unref(listmodel);
+    }
+    
+    uicbox->var = var;
+    uicbox->model = model;
+    
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_combobox_destroy),
+                uicbox);
+    
+    // bind var
+    if(list) {
+        list->update = ui_combobox_modelupdate;
+        list->getselection = ui_combobox_getselection;
+        list->setselection = ui_combobox_setselection;
+        list->obj = uicbox;
+    }
+    
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+    gtk_cell_layout_set_attributes(
+            GTK_CELL_LAYOUT(combobox),
+            renderer,
+            "text",
+            0,
+            NULL);
+    gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+    
+    // add callback
+    if(f) {
+        UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = udata;
+        event->callback = f;
+        event->value = 0;
+        event->customdata = NULL;
+
+        g_signal_connect(
+                combobox,
+                "changed",
+                G_CALLBACK(ui_combobox_change_event),
+                event);
+    }
+    
+    return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+    UiEvent event;
+    event.obj = e->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = gtk_combo_box_get_active(widget);
+    e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+    UiListView *view = list->obj;
+    GtkListStore *store = create_list_store(view->var->value, view->model);
+    gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
+    g_object_unref(store);
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+    UiListView *combobox = list->obj;
+    UiListSelection ret;
+    ret.rows = malloc(sizeof(int*));
+    ret.count = 1;
+    ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
+    return ret;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    UiListView *combobox = list->obj;
+    if(selection.count > 0) {
+        gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
+    }
+}
+
+
+/* ------------------------------ Source List ------------------------------ */
+
+static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) {
+    cxListDestroy(v->sublists);
+    free(v);
+}
+
+static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) {
+    free(sublist->header);
+    ui_destroy_boundvar(obj->ctx, sublist->var);
+    cxListDestroy(sublist->widgets);
+}
+
+static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {
+    // first rows in sublists have the ui_listbox property
+    UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox");
+    if(!listbox) {
+        return;
+    }
+    
+    UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist");
+    if(!sublist) {
+        return;
+    }
+    
+    if(sublist->separator) {
+        GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        gtk_list_box_row_set_header(row, separator);
+    } else if(sublist->header) {
+        GtkWidget *header = gtk_label_new(sublist->header);
+        gtk_widget_set_halign(header, GTK_ALIGN_START);
+        if(row == listbox->first_row) {
+            WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first");
+        } else {
+            WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header");
+        }
+        gtk_list_box_row_set_header(row, header);
+    }
+} 
+
+#ifdef UI_GTK3
+typedef struct _UiSidebarListBoxClass {
+    GtkListBoxClass parent_class; 
+} UiSidebarListBoxClass;
+
+typedef struct _UiSidebarListBox {
+    GtkListBox parent_instance;
+} UiSidebarListBox;
+
+G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX)
+
+/* Initialize the instance */
+static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) {
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+    gtk_widget_class_set_css_name (widget_class, "placessidebar");
+}
+
+static void ui_sidebar_list_box_init(UiSidebarListBox *self) {
+    
+}
+#endif
+
+UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+#ifdef UI_GTK3
+    GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL);
+#else
+    GtkWidget *listbox = gtk_list_box_new();
+#endif
+    if(!args.style_class) {
+#if GTK_MAJOR_VERSION >= 4
+        WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar");
+#else
+        WIDGET_ADD_CSS_CLASS(listbox, "sidebar");
+#endif
+    }
+    gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    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_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, TRUE);
+    
+    UiListBox *uilistbox = malloc(sizeof(UiListBox));
+    uilistbox->obj = obj;
+    uilistbox->listbox = GTK_LIST_BOX(listbox);
+    uilistbox->getvalue = args.getvalue;
+    uilistbox->onactivate = args.onactivate;
+    uilistbox->onactivatedata = args.onactivatedata;
+    uilistbox->onbuttonclick = args.onbuttonclick;
+    uilistbox->onbuttonclickdata = args.onbuttonclickdata;
+    uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4);
+    uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy;
+    uilistbox->sublists->collection.destructor_data = obj;
+    uilistbox->first_row = NULL;
+    
+    if(args.numsublists == 0 && args.sublists) {
+        args.numsublists = INT_MAX;
+    }
+    for(int i=0;i<args.numsublists;i++) {
+        UiSubList sublist = args.sublists[i];
+        if(!sublist.varname && !sublist.value) {
+            break;
+        }
+        
+        UiListBoxSubList uisublist;
+        uisublist.var = uic_widget_var(
+                obj->ctx,
+                current->ctx,
+                sublist.value,
+                sublist.varname,
+                UI_VAR_LIST);
+        uisublist.numitems = 0;
+        uisublist.header = sublist.header ? strdup(sublist.header) : NULL;
+        uisublist.separator = sublist.separator;
+        uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        uisublist.listbox = uilistbox;
+        uisublist.userdata = sublist.userdata;
+        uisublist.index = i;
+        
+        cxListAdd(uilistbox->sublists, &uisublist);
+        
+        // bind UiList
+        UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1);
+        UiList *list = uisublist.var->value;
+        if(list) {
+            list->obj = sublist_ptr;
+            list->update = ui_listbox_list_update;
+        }
+    }
+    // fill items
+    ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists));
+    
+    // register uilistbox for both widgets, so it doesn't matter which
+    // widget is used later
+    g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox);
+    g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox);
+    
+    // signals
+    g_signal_connect(
+                listbox,
+                "destroy",
+                G_CALLBACK(ui_destroy_sourcelist),
+                uilistbox);
+    
+    if(args.onactivate) {
+        g_signal_connect(
+                listbox,
+                "row-activated",
+                G_CALLBACK(ui_listbox_row_activate),
+                NULL);
+    }
+    
+    return scroll_area;
+}
+
+void ui_listbox_update(UiListBox *listbox, int from, int to) {
+    CxIterator i = cxListIterator(listbox->sublists);
+    size_t pos = 0;
+    cx_foreach(UiListBoxSubList *, sublist, i) {
+        if(i.index < from) {
+            pos += sublist->numitems;
+            continue;
+        }
+        if(i.index > to) {
+            break;
+        }
+        
+        // reload sublist
+        ui_listbox_update_sublist(listbox, sublist, pos);
+        pos += sublist->numitems;
+    }
+}
+
+static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
+    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
+    if(item->icon) {
+        GtkWidget *icon = ICON_IMAGE(item->icon);
+        BOX_ADD(hbox, icon);
+    }
+    GtkWidget *label = gtk_label_new(item->label);
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    BOX_ADD_EXPAND(hbox, label);
+    // TODO: badge, button
+    GtkWidget *row = gtk_list_box_row_new();
+    LISTBOX_ROW_SET_CHILD(row, hbox);
+    
+    // signals 
+    UiEventDataExt *event = malloc(sizeof(UiEventDataExt));
+    memset(event, 0, sizeof(UiEventDataExt));
+    event->obj = listbox->obj;
+    event->customdata0 = sublist;
+    event->customdata1 = sublist->var;
+    event->customdata2 = item->eventdata;
+    event->callback = listbox->onactivate;
+    event->userdata = listbox->onactivatedata;
+    event->callback2 = listbox->onbuttonclick;
+    event->userdata2 = listbox->onbuttonclickdata;
+    event->value0 = index;
+    
+    g_signal_connect(
+            row,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event);
+    
+    return row;
+}
+
+void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) {
+    // clear sublist
+    CxIterator r = cxListIterator(sublist->widgets);
+    cx_foreach(GtkWidget*, widget, r) {
+        LISTBOX_REMOVE(listbox->listbox, widget);
+    }
+    cxListClear(sublist->widgets);
+    
+    sublist->numitems = 0;
+    
+    // create items for each UiList element
+    UiList *list = sublist->var->value;
+    if(!list) {
+        return;
+    }
+    
+    size_t index = 0;
+    void *elm = list->first(list);
+    while(elm) {
+        UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
+        listbox->getvalue(sublist->userdata, elm, index, &item);
+        
+        // create listbox item
+        GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index);
+        if(index == 0) {
+            // first row in the sublist, set ui_listbox data to the row
+            // which is then used by the headerfunc
+            g_object_set_data(G_OBJECT(row), "ui_listbox", listbox);
+            g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist);
+            
+            if(listbox_insert_index == 0) {
+                // first row in the GtkListBox
+                listbox->first_row = GTK_LIST_BOX_ROW(row);
+            }
+        }
+        intptr_t rowindex = listbox_insert_index + index;
+        g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex);
+        gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index);
+        cxListAdd(sublist->widgets, row);
+        
+        // cleanup
+        free(item.label);
+        free(item.icon);
+        free(item.button_label);
+        free(item.button_icon);
+        free(item.badge);
+        
+        // next row
+        elm = list->next(list);
+        index++;
+    }
+    
+    sublist->numitems = cxListSize(sublist->widgets);
+}
+
+void ui_listbox_list_update(UiList *list, int i) {
+    UiListBoxSubList *sublist = list->obj;
+}
+
+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) {
+        return;
+    }
+    UiListBoxSubList *sublist = data->customdata0;
+    
+    UiEvent event;
+    event.obj = data->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = data->customdata2;
+    event.intval = data->value0;
+    
+    if(data->callback) {
+        data->callback(&event, data->userdata);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,137 @@
+/*
+ * 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 TREE_H
+#define	TREE_H
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+
+#include <cx/array_list.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    UiObject    *obj;
+    GtkWidget   *widget;
+    UiVar       *var;
+    UiModel     *model;
+    ui_callback ondragstart;
+    void        *ondragstartdata;
+    ui_callback ondragcomplete;
+    void        *ondragcompletedata;
+    ui_callback ondrop;
+    void        *ondropdata;
+    
+} UiListView;
+
+typedef struct UiTreeEventData {
+    UiObject    *obj;
+    ui_callback activate;
+    ui_callback selection;
+    void        *activatedata;
+    void        *selectiondata;
+} UiTreeEventData;
+
+typedef struct UiListBox UiListBox;
+
+typedef struct UiListBoxSubList {
+    UiVar       *var;
+    size_t      numitems;
+    char        *header;
+    UiBool      separator;
+    CxList      *widgets;
+    UiListBox   *listbox;
+    void        *userdata;
+    size_t      index;
+} UiListBoxSubList;
+
+struct UiListBox {
+    UiObject                 *obj;
+    GtkListBox               *listbox;
+    CxList                   *sublists; // contains UiListBoxSubList elements
+    ui_sublist_getvalue_func getvalue;
+    ui_callback              onactivate;
+    void                     *onactivatedata;
+    ui_callback              onbuttonclick;
+    void                     *onbuttonclickdata;
+    
+    GtkListBoxRow            *first_row;
+};
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
+void ui_listview_update(UiList *list, int i);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v);
+void ui_listview_destroy(GtkWidget *w, UiListView *v);
+
+void ui_listview_activate_event(
+        GtkTreeView *tree_view,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event);
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event);
+UiListSelection ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event);
+int ui_tree_path_list_index(GtkTreePath *path);
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args);
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, 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);
+
+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_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data);
+        
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TREE_H */
+
--- a/ui/gtk/menu.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/menu.c	Sat Jan 04 16:38:48 2025 +0100
@@ -34,189 +34,58 @@
 #include "menu.h"
 #include "toolkit.h"
 #include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/types.h"
 #include "../ui/properties.h"
 #include "../ui/window.h"
 #include "container.h"
 
-static UcxList *menus;
-static UcxList *current;
-
-void ui_menu(char *label) {
-    // free current menu hierarchy
-    ucx_list_free(current);
-    
-    // create menu
-    UiMenu *menu = malloc(sizeof(UiMenu));
-    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
-    
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;    
-    
-    current = ucx_list_prepend(NULL, menu);
-    menus = ucx_list_append(menus, menu);
-    
-}
-
-void ui_submenu(char *label) {
-    UiMenu *menu = malloc(sizeof(UiMenu));
-    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
-    
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;
-    
-    // add submenu to current menu
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, menu);
-    
-    // set the submenu to current menu
-    current = ucx_list_prepend(current, menu);
-}
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
 
-void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
-        return;
-    }
-    current = ucx_list_remove(current, current);
-    //UcxList *c = current;
-}
-
-void ui_menuitem(char *label, ui_callback f, void *userdata) {
-    ui_menuitem_gr(label, f, userdata, -1);
-}
-
-void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) {
-    ui_menuitem_stgr(stockid, f, userdata, -1);
-}
-
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItem *item = malloc(sizeof(UiMenuItem));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
-    
-    item->label = label;
-    item->userdata = userdata;
-    item->callback = f;
-    item->groups = NULL;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
+#if GTK_MAJOR_VERSION <= 3
 
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
-    if(!current) {
-        return;
-    }
-    
-    UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
-    
-    item->stockid = stockid;
-    item->userdata = userdata;
-    item->callback = f;
-    item->groups = NULL;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_menuseparator() {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
-    item->add_to = (ui_menu_add_f)add_menuseparator_widget;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_checkitem(char *label, ui_callback f, void *userdata) {
-    if(!current) {
-        return;
-    }
-    
-    UiCheckItem *item = malloc(sizeof(UiCheckItem));
-    item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
-    item->label = label;
-    item->callback = f;
-    item->userdata = userdata;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_checkitem_nv(char *label, char *vname) {
-    if(!current) {
-        return;
-    }
-    
-    UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
-    item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
-    item->varname = vname;
-    item->label = label;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
-    item->callback = f;
-    item->userdata = userdata;
-    item->list = items;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
 
 // private menu functions
 GtkWidget *ui_create_menubar(UiObject *obj) {
-    if(menus == NULL) {
+    UiMenu *menus_begin = uic_get_menu_list();
+    if(menus_begin == NULL) {
         return NULL;
     }
     
     GtkWidget *mb = gtk_menu_bar_new();
     
-    UcxList *ls = menus;
+    UiMenu *ls = menus_begin;
     while(ls) {
-        UiMenu *menu = ls->data;
-        menu->item.add_to(mb, 0, &menu->item, obj);
+        UiMenu *menu = ls;
+        add_menu_widget(mb, 0, &menu->item, obj);
         
-        ls = ls->next;
+        ls = (UiMenu*)ls->item.next;
     }
     
     return mb;
 }
 
+void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it, obj);
+        it = it->next;
+        index++;
+    }
+}
+
 void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) {
     UiMenu *menu = (UiMenu*)item;
     
@@ -224,15 +93,8 @@
     GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
     
-    UcxList *ls = menu->items;
-    int index = 0;
-    while(ls) {
-        UiMenuItemI *i = ls->data;
-        i->add_to(menu_widget, index, i, obj);
-        
-        ls = ls->next;
-        index++;
-    }
+    ui_add_menu_items(menu_widget, i, menu, obj);
+    
     
     gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item);
 }
@@ -249,6 +111,7 @@
         event->userdata = i->userdata;
         event->callback = i->callback;
         event->value = 0;
+        event->customdata = NULL;
 
         g_signal_connect(
                 widget,
@@ -265,10 +128,14 @@
     gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
     
     if(i->groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, 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);
+        cxListDestroy(groups);
     }
 }
 
+/*
 void add_menuitem_st_widget(
         GtkWidget *parent,
         int index,
@@ -304,6 +171,7 @@
         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
     }
 }
+*/
 
 void add_menuseparator_widget(
         GtkWidget *parent,
@@ -317,7 +185,7 @@
 }
 
 void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
-    UiCheckItem *ci = (UiCheckItem*)item;
+    UiMenuCheckItem *ci = (UiMenuCheckItem*)item;
     GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
     gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
     
@@ -327,7 +195,8 @@
         event->userdata = ci->userdata;
         event->callback = ci->callback;
         event->value = 0;
-
+        event->customdata = NULL;
+        
         g_signal_connect(
                 widget,
                 "toggled",
@@ -341,6 +210,11 @@
     }
 }
 
+void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    // TODO
+}
+
+/*
 void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiCheckItemNV *ci = (UiCheckItemNV*)item;
     GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
@@ -357,27 +231,31 @@
         // TODO: error
     }
 }
+*/
 
 void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item;
-    UcxMempool *mp = obj->ctx->mempool;
+    const CxAllocator *a = obj->ctx->allocator;
     
-    UiActiveMenuItemList *ls = ucx_mempool_malloc(
-            mp,
+    UiActiveMenuItemList *ls = cxMalloc(
+            a,
             sizeof(UiActiveMenuItemList));
     
     ls->object = obj;
     ls->menu = GTK_MENU_SHELL(p);
     ls->index = index;
     ls->oldcount = 0;
-    ls->list = il->list;
+    ls->getvalue = il->getvalue;
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    ls->list = var->value;
+    
     ls->callback = il->callback;
     ls->userdata = il->userdata;
     
-    ls->list->observers = ui_add_observer(
-            ls->list->observers,
-            (ui_callback)ui_update_menuitem_list,
-            ls);
+    UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
+    ls->list->observers = ui_obsvlist_add(ls->list->observers, observer);
+    uic_list_register_observer_destructor(obj->ctx, ls->list, observer);
     
     ui_update_menuitem_list(NULL, ls);
 }
@@ -398,25 +276,29 @@
         }
     }
     
-    char *str = ui_list_first(list->list);
-    if(str) {
+    void* elm = ui_list_first(list->list);
+    if(elm) {
         GtkWidget *widget = gtk_separator_menu_item_new();
         gtk_menu_shell_insert(list->menu, widget, list->index);
         gtk_widget_show(widget);
     }
+    
+    ui_getvaluefunc getvalue = list->getvalue;
     int i = 1;
-    while(str) {
-        GtkWidget *widget = gtk_menu_item_new_with_label(str);
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        
+        GtkWidget *widget = gtk_menu_item_new_with_label(label);
         gtk_menu_shell_insert(list->menu, widget, list->index + i);
         gtk_widget_show(widget);
         
         if(list->callback) {
-            // TODO: use mempool
             UiEventData *event = malloc(sizeof(UiEventData));
             event->obj = list->object;
             event->userdata = list->userdata;
             event->callback = list->callback;
             event->value = i - 1;
+            event->customdata = elm;
 
             g_signal_connect(
                 widget,
@@ -430,7 +312,7 @@
                 event);
         }
         
-        str = ui_list_next(list->list);
+        elm = ui_list_next(list->list);
         i++;
     }
     
@@ -442,7 +324,7 @@
     evt.obj = event->obj;
     evt.window = event->obj->window;
     evt.document = event->obj->ctx->document;
-    evt.eventdata = NULL;
+    evt.eventdata = event->customdata;
     evt.intval = event->value;
     event->callback(&evt, event->userdata);    
 }
@@ -473,34 +355,28 @@
  * widget menu functions
  */
 
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+    GtkWidget *menu_widget = gtk_menu_new();
+    ui_add_menu_items(menu_widget, 0, builder->menus_begin, obj);
+    return GTK_MENU(menu_widget);
+}
+
 static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
     if(event->type == GDK_BUTTON_PRESS) {
         GdkEventButton *e = (GdkEventButton*)event;
         if(e->button == 3) {
             gtk_widget_show_all(GTK_WIDGET(menu));
-            ui_contextmenu_popup(menu);
-            return TRUE;
+            ui_contextmenu_popup(menu, widget, 0, 0);
         }
     }
     return FALSE;
 }
 
-UIMENU ui_contextmenu(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    return ui_contextmenu_w(obj, ct->current);
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkMenu *menu) {
+    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
 }
 
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    GtkMenu *menu = GTK_MENU(gtk_menu_new());
-    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
-    
-    ct->menu = menu;
-    return menu;
-}
-
-void ui_contextmenu_popup(UIMENU menu) {
+void ui_contextmenu_popup(UIMENU menu, GtkWidget *widget, int x, int y) {
 #if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
     gtk_menu_popup_at_pointer(menu, NULL);
 #else
@@ -508,102 +384,255 @@
 #endif
 }
 
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
-    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+
+
+#if GTK_MAJOR_VERSION >= 4
+
+
+
+static ui_gmenu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ ui_gmenu_add_menu,
+    /* UI_MENU_ITEM            */ ui_gmenu_add_menuitem,
+    /* UI_MENU_CHECK_ITEM      */ ui_gmenu_add_checkitem,
+    /* UI_MENU_RADIO_ITEM      */ ui_gmenu_add_radioitem,
+    /* UI_MENU_ITEM_LIST       */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_CHECKITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_RADIOITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_SEPARATOR       */ ui_gmenu_add_menuseparator
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    int index_section = 0;
+    GMenu *section = NULL;
+    while(it) {
+        if(it->type == UI_MENU_SEPARATOR) {
+            section = g_menu_new();
+            g_menu_append_section(parent, NULL, G_MENU_MODEL(section));
+            index++;
+            index_section = 0;
+        } else {
+            if(section) {
+                createMenuItem[it->type](section, index_section++, it, obj);
+            } else {
+                createMenuItem[it->type](parent, index++, it, obj);
+            }
+        }
+        it = it->next;
+    }
 }
 
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *mi = (UiMenu*)item;
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, mi, obj);
+    g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu));
+}
+
+static void action_enable(GSimpleAction *action, int enabled) {
+    g_simple_action_set_enabled(action, enabled);
+}
+
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *i = (UiMenuItem*)item;
+     
+    GSimpleAction *action = g_simple_action_new(item->id, NULL);
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(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);
+        cxListDestroy(groups);
     }
     
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label);
-    gtk_widget_show(widget);
-    
-    if(f) {
+    if(i->callback != NULL) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
         event->value = 0;
+        event->customdata = NULL;
 
         g_signal_connect(
-                widget,
+                action,
                 "activate",
-                G_CALLBACK(ui_menu_event_wrapper),
+                G_CALLBACK(ui_activate_event_wrapper),
                 event);
         g_signal_connect(
-                widget,
+                obj->widget,
                 "destroy",
                 G_CALLBACK(ui_destroy_userdata),
                 event);
     }
     
-    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
+    char action_name[32];
+    snprintf(action_name, 32, "win.%s", item->id); 
+    g_menu_append(parent, i->label, action_name);
+}
+
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
     
-    if(groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
-    }
+}
+
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item;
+    
+    // TODO
+}
+
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    
 }
 
-void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
-    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item; 
+    
+    const CxAllocator *a = obj->ctx->allocator;
+    
+    UiActiveGMenuItemList *ls = cxMalloc(
+            a,
+            sizeof(UiActiveGMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = p;
+    ls->index = index;
+    ls->oldcount = 0;
+    ls->getvalue = il->getvalue;
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    ls->var = var;
+    UiList *list = var->value;
+    
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    UiObserver *observer = ui_observer_new((ui_callback)ui_update_gmenu_item_list, ls);
+    list->observers = ui_obsvlist_add(list->observers, observer);
+    uic_list_register_observer_destructor(obj->ctx, list, observer);
+    
+    GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    snprintf(ls->action, 32, "win.%s", item->id);
+    
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = il->userdata;
+    event->callback = il->callback;
+    event->customdata = var;
+    event->value = 0;
+    
+    g_signal_connect(
+            action,
+            "activate",
+            G_CALLBACK(ui_menu_list_item_activate_event_wrapper),
+            event);
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    ui_update_gmenu_item_list(NULL, ls);
 }
 
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int intval = event->value;
+    if(parameter && g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
+        intval = g_variant_get_int32(parameter);
     }
     
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = event->customdata;
+    evt.intval = intval;
+    event->callback(&evt, event->userdata);    
+}
+
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int index = g_variant_get_int32(parameter);
+    UiVar *var = event->customdata;
+    UiList *list = var->value;
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = ui_list_get(list, index);
+    evt.intval = index;
+    event->callback(&evt, event->userdata);    
+    
+}
+
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) {
+    // remove old items
+    for(int i=0;i<list->oldcount;i++) {
+        g_menu_remove(list->menu, list->index);
     }
-    va_end(ap);
-    
-    // create menuitem
-    GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group);
-    gtk_widget_show(widget);
+    UiList *ls = list->var->value;
     
-    if(f) {
-        UiEventData *event = malloc(sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
-        event->value = 0;
+    // add list items
+    ui_getvaluefunc getvalue = list->getvalue;
+    int i = 0;
+    void* elm = ui_list_first(ls);
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        
+        GMenuItem *item = g_menu_item_new(label, NULL);
+        GVariant *v = g_variant_new("i", i);
+        g_menu_item_set_action_and_target_value(item, list->action, v);
+        g_menu_insert_item(list->menu, list->index+i, item);
+        
+        elm = ui_list_next(ls);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
 
-        g_signal_connect(
-                widget,
-                "activate",
-                G_CALLBACK(ui_menu_event_wrapper),
-                event);
-        g_signal_connect(
+
+/* --------------------- context menu / menubuilder --------------------- */
+
+static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) {
+    gtk_widget_unparent(GTK_WIDGET(menu));
+}
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) {
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj);
+    GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
+    gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE);
+    gtk_widget_set_halign(contextmenu, GTK_ALIGN_START);
+    gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget);
+    g_signal_connect(
                 widget,
                 "destroy",
-                G_CALLBACK(ui_destroy_userdata),
-                event);
-    }
-    
-    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
-    
-    if(groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
-    }
+                G_CALLBACK(remove_popover),
+                contextmenu);
+    return GTK_POPOVER_MENU(contextmenu);
+}
+
+static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(user_data));
 }
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) {
+    GtkGesture *gesture = gtk_gesture_click_new();
+    gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+    gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture));
+    g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(menu));
+}
+
+#endif
--- a/ui/gtk/menu.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/menu.h	Sat Jan 04 16:38:48 2025 +0100
@@ -30,89 +30,45 @@
 #define	MENU_H
 
 #include "../ui/menu.h"
-#include <ucx/list.h>
+#include "../common/menu.h"
+#include <cx/list.h>
 #include "toolkit.h"
 
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
     
-typedef struct UiMenuItemI      UiMenuItemI;
-typedef struct UiMenu           UiMenu;
-typedef struct UiMenuItem       UiMenuItem;
-typedef struct UiStMenuItem     UiStMenuItem;
-typedef struct UiCheckItem      UiCheckItem;
-typedef struct UiCheckItemNV    UiCheckItemNV;
-typedef struct UiMenuItemList   UiMenuItemList;
+UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj);
+void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu);
+    
+#if GTK_MAJOR_VERSION <= 3
 
 typedef struct UiActiveMenuItemList UiActiveMenuItemList;
 
-typedef GtkWidget*(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
-    
-struct UiMenuItemI {
-    ui_menu_add_f  add_to;
-};
-
-struct UiMenu {
-    UiMenuItemI    item;
-    char           *label;
-    UcxList        *items;
-    UiMenu         *parent;
-};
-
-struct UiMenuItem {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    char           *label;
-    void           *userdata;
-    UcxList        *groups;
-};
-
-struct UiStMenuItem {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    char           *stockid;
-    void           *userdata;
-    UcxList        *groups;
-};
-
-struct UiCheckItem {
-    UiMenuItemI    item;
-    char           *label;
-    ui_callback    callback;
-    void           *userdata;
-};
-
-struct UiCheckItemNV {
-    UiMenuItemI    item;
-    char           *label;
-    char           *varname;
-};
-
-struct UiMenuItemList {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    void           *userdata;
-    UiList         *list;
-};
+typedef void(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
 
 struct UiActiveMenuItemList {
-    UiObject     *object;
-    GtkMenuShell *menu;
-    int          index;
-    int          oldcount;
-    UiList       *list;
-    ui_callback  callback;
-    void         *userdata;
+    UiObject         *object;
+    GtkMenuShell     *menu;
+    int              index;
+    int              oldcount;
+    UiList           *list;
+    ui_getvaluefunc getvalue;
+    ui_callback     callback;
+    void            *userdata;
 };
 
 GtkWidget *ui_create_menubar(UiObject *obj);
 
+void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj);
+
 void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
 void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
 void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj);
 void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 
@@ -122,6 +78,41 @@
 int64_t ui_checkitem_get(UiInteger *i);
 void ui_checkitem_set(UiInteger *i, int64_t value);
 
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+#if GTK_MAJOR_VERSION >= 4
+
+typedef void(*ui_gmenu_add_f)(GMenu *, int, UiMenuItemI*, UiObject*);
+
+typedef struct UiActiveGMenuItemList UiActiveGMenuItemList;
+struct UiActiveGMenuItemList {
+    UiObject         *object;
+    GMenu            *menu;
+    char             action[32];
+    int              index;
+    int              oldcount;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj);
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list);
+
+#endif
+
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/model.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,539 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-
-#include "model.h"
-#include "image.h"
-#include "toolkit.h"
-
-#define IS_UI_LIST_MODEL(obj) \
-        (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type))
-#define UI_LIST_MODEL(obj) \
-        (G_TYPE_CHECK_INSTANCE_CAST((obj), list_model_type, UiListModel))
-
-static void list_model_class_init(GObjectClass *cl, gpointer data);
-static void list_model_interface_init(GtkTreeModelIface *i, gpointer data);
-static void list_model_init(UiListModel *instance, GObjectClass *cl);
-
-static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data);
-static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data);
-
-static GObjectClass list_model_class;
-static const GTypeInfo list_model_info = {
-    sizeof(GObjectClass),
-    NULL,
-    NULL,
-    (GClassInitFunc)list_model_class_init,
-    NULL,
-    NULL,
-    sizeof(UiListModel),
-    0,
-    (GInstanceInitFunc)list_model_init
-};
-static const GInterfaceInfo list_model_interface_info = {
-    (GInterfaceInitFunc)list_model_interface_init,
-    NULL,
-    NULL
-};
-static GType list_model_type;
-
-static const GInterfaceInfo list_model_dnd_dest_interface_info = {
-    (GInterfaceInitFunc)list_model_dnd_dest_interface_init,
-    NULL,
-    NULL
-};
-static const GInterfaceInfo list_model_dnd_src_interface_info = {
-    (GInterfaceInitFunc)list_model_dnd_src_interface_init,
-    NULL,
-    NULL
-};
-
-void ui_list_init() {
-    list_model_type = g_type_register_static(
-            G_TYPE_OBJECT,
-            "UiListModel",
-            &list_model_info,
-            (GTypeFlags)0);
-    g_type_add_interface_static(
-            list_model_type,
-            GTK_TYPE_TREE_MODEL,
-            &list_model_interface_info);
-    g_type_add_interface_static(
-            list_model_type,
-            GTK_TYPE_TREE_DRAG_DEST,
-            &list_model_dnd_dest_interface_info);
-    g_type_add_interface_static(
-            list_model_type,
-            GTK_TYPE_TREE_DRAG_SOURCE,
-            &list_model_dnd_src_interface_info);
-}
-
-static void list_model_class_init(GObjectClass *cl, gpointer data) {
-    cl->dispose = ui_list_model_dispose;
-    cl->finalize = ui_list_model_finalize;
-    
-}
-
-static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) {
-    i->get_flags       = ui_list_model_get_flags;
-    i->get_n_columns   = ui_list_model_get_n_columns;
-    i->get_column_type = ui_list_model_get_column_type;
-    i->get_iter        = ui_list_model_get_iter;
-    i->get_path        = ui_list_model_get_path;
-    i->get_value       = ui_list_model_get_value;
-    i->iter_next       = ui_list_model_iter_next;
-    i->iter_children   = ui_list_model_iter_children;
-    i->iter_has_child  = ui_list_model_iter_has_child;
-    i->iter_n_children = ui_list_model_iter_n_children;
-    i->iter_nth_child  = ui_list_model_iter_nth_child;
-    i->iter_parent     = ui_list_model_iter_parent;
-}
-
-static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) {
-    i->drag_data_received = ui_list_model_drag_data_received;
-    i->row_drop_possible = ui_list_model_row_drop_possible;
-}
-
-static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) {
-    i->drag_data_delete = ui_list_model_drag_data_delete;
-    i->drag_data_get = ui_list_model_drag_data_get;
-    i->row_draggable = ui_list_model_row_draggable;
-}
-
-static void list_model_init(UiListModel *instance, GObjectClass *cl) {
-    instance->columntypes = NULL;
-    instance->var = NULL;
-    instance->numcolumns = 0;
-    instance->stamp = g_random_int();
-}
-
-static GType ui_gtk_type(UiModelType type) {
-    switch(type) {
-        default: break;
-        case UI_STRING: return G_TYPE_STRING;
-        case UI_INTEGER: return G_TYPE_INT;
-    }
-    return G_TYPE_INVALID;
-}
-
-static void ui_model_set_value(GType type, void *data, GValue *value) {
-    switch(type) {
-        default: break;
-        case G_TYPE_OBJECT: {
-            value->g_type = G_TYPE_OBJECT;
-            g_value_set_object(value, data);
-            return;
-        }
-        case G_TYPE_STRING: {
-            value->g_type = G_TYPE_STRING;
-            g_value_set_string(value, data);
-            return;
-        }
-        case G_TYPE_INT: {
-            value->g_type = G_TYPE_INT;
-            int *i = data;
-            g_value_set_int(value, *i);
-            return;
-        }
-    }
-    value->g_type = G_TYPE_INVALID; 
-}
-
-UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) {
-    UiListModel *model = g_object_new(list_model_type, NULL);
-    model->obj = obj;
-    model->model = info;
-    model->var = var;
-    model->columntypes = calloc(sizeof(GType), 2 * info->columns);
-    int ncol = 0;
-    for(int i=0;i<info->columns;i++) {
-        UiModelType type = info->types[i];
-        if(type == UI_ICON_TEXT) {
-            model->columntypes[ncol] = G_TYPE_OBJECT;
-            ncol++;
-            model->columntypes[ncol] = G_TYPE_STRING;
-        } else {
-            model->columntypes[ncol] = ui_gtk_type(info->types[i]);
-        }
-        ncol++;
-    }
-    model->numcolumns = ncol;
-    return model;
-}
-
-void ui_list_model_dispose(GObject *obj) {
-    
-}
-
-void ui_list_model_finalize(GObject *obj) {
-    
-}
-
-
-GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) {
-    return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
-}
-
-gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) {
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0);
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    return model->numcolumns;
-}
-
-GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) {
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID);
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID);
-    return model->columntypes[index];
-}
-
-gboolean ui_list_model_get_iter(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreePath *path)
-{
-    g_assert(IS_UI_LIST_MODEL(tree_model));
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    UiList *list = model->var->value;
-    
-    // check the depth of the path
-    // a list must have a depth of 1
-    gint depth = gtk_tree_path_get_depth(path);
-    g_assert(depth == 1);
-    
-    // get row
-    gint *indices = gtk_tree_path_get_indices(path);
-    gint row = indices[0];
-    
-    // check row
-    if(row == 0) {
-        // we don't need to count if the first element is requested
-        if(list->first(list) == NULL) {
-            return FALSE;
-        }
-    } else if(row >= list->count(list)) {
-        return FALSE;
-    }
-    
-    // the UiList has an integrated iterator
-    // we only get a value to adjust it
-    void *val = NULL;
-    if(row == 0) {
-        val = list->first(list);
-    } else {
-        val = list->get(list, row);
-    }
-    
-    iter->stamp = model->stamp;
-    iter->user_data = list->iter;
-    iter->user_data2 = (gpointer)(intptr_t)row; // list->index
-    iter->user_data3 = val;
-    
-    return val ? TRUE : FALSE;
-}
-
-GtkTreePath* ui_list_model_get_path(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter)
-{
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL);
-    g_return_val_if_fail(iter != NULL, NULL);
-    g_return_val_if_fail(iter->user_data != NULL, NULL);
-    
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    
-    GtkTreePath *path = gtk_tree_path_new();
-    gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index
-    
-    return path;
-}
-
-void ui_list_model_get_value(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        gint column,
-        GValue *value)
-{
-    g_return_if_fail(IS_UI_LIST_MODEL(tree_model));
-    g_return_if_fail(iter != NULL);
-    g_return_if_fail(iter->user_data != NULL);
-    
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    UiList *list = model->var->value;
-    
-    g_return_if_fail(column < model->numcolumns);
-    
-    // TODO: return correct value from column
-    
-    //value->g_type = G_TYPE_STRING;
-    list->iter = iter->user_data;
-    //list->index = (int)(intptr_t)iter->user_data2;
-    //list->current = iter->user_data3;
-    if(model->model->getvalue) {
-        void *data = model->model->getvalue(iter->user_data3, column);
-        if(model->columntypes[column] == G_TYPE_OBJECT) {
-            UiImage *img = data;
-            ui_model_set_value(model->columntypes[column], img->pixbuf, value);
-        } else {
-            ui_model_set_value(model->columntypes[column], data, value);
-        }
-    } else {
-        value->g_type = G_TYPE_INVALID;
-    }
-}
-
-gboolean ui_list_model_iter_next(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter)
-{
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
-    g_return_val_if_fail(iter != NULL, FALSE);
-    //g_return_val_if_fail(iter->user_data != NULL, FALSE);
-    
-    if(!iter->user_data) {
-        return FALSE;
-    }
-    
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    UiList *list = model->var->value;
-    list->iter = iter->user_data;
-    //list->index = (int)(intptr_t)iter->user_data2;
-    void *val = list->next(list);
-    iter->user_data = list->iter;
-    intptr_t index = (intptr_t)iter->user_data2;
-    index++;
-    //iter->user_data2 = (gpointer)(intptr_t)list->index;
-    iter->user_data2 = (gpointer)index;
-    iter->user_data3 = val;
-    return val ? TRUE : FALSE;
-}
-
-gboolean ui_list_model_iter_children(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *parent)
-{
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
-    
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    UiList *list = model->var->value;
-    
-    if(parent) {
-        return FALSE;
-    }
-    
-    /*
-     * a list element has no children
-     * we set the iter to the first element
-     */
-    void *val = list->first(list);
-    iter->stamp = model->stamp;
-    iter->user_data = list->iter;
-    iter->user_data2 = (gpointer)0;
-    iter->user_data3 = val;
-    
-    return val ? TRUE : FALSE;
-}
-
-gboolean ui_list_model_iter_has_child(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter)
-{
-    return FALSE;
-}
-
-gint ui_list_model_iter_n_children(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter)
-{
-    g_assert(IS_UI_LIST_MODEL(tree_model));
-    
-    if(!iter) {
-        // return number of rows
-        UiListModel *model = UI_LIST_MODEL(tree_model);
-        UiList *list = model->var->value;
-        return list->count(list);
-    }
-    
-    return 0;
-}
-
-gboolean ui_list_model_iter_nth_child(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *parent,
-        gint n)
-{
-    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
-    
-    if(parent) {
-        return FALSE;
-    }
-    
-    UiListModel *model = UI_LIST_MODEL(tree_model);
-    UiList *list = model->var->value;
-    
-    // check n
-    if(n == 0) {
-        // we don't need to count if the first element is requested
-        if(list->first(list) == NULL) {
-            return FALSE;
-        }
-    } else if(n >= list->count(list)) {
-        return FALSE;
-    }
-    
-    void *val = list->get(list, n);
-    iter->stamp = model->stamp;
-    iter->user_data = list->iter;
-    iter->user_data2 = (gpointer)(intptr_t)n; // list->index
-    iter->user_data3 = val;
-    
-    return val ? TRUE : FALSE;
-}
-
-gboolean ui_list_model_iter_parent(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *child)
-{
-    return FALSE;
-}
-
-// ****** dnd ******
-
-gboolean ui_list_model_drag_data_received(
-        GtkTreeDragDest   *drag_dest,
-        GtkTreePath       *dest_path,
-        GtkSelectionData  *selection_data)
-{
-    //printf("drag received\n");
-    UiListModel *model = UI_LIST_MODEL(drag_dest);
-    if(model->model->drop) {
-        gint *indices = gtk_tree_path_get_indices(dest_path);
-        gint row = indices[0];
-        UiEvent e;
-        e.obj = model->obj;
-        e.window = e.obj->window;
-        e.document = e.obj->ctx->document;
-        e.eventdata = NULL;
-        e.intval = 0;
-        UiSelection s;
-        s.data = selection_data;
-        model->model->drop(&e, &s, model->var->value, row);
-    }
-    return TRUE;
-}
-
-gboolean ui_list_model_row_drop_possible(
-        GtkTreeDragDest   *drag_dest,
-        GtkTreePath       *dest_path,
-	GtkSelectionData  *selection_data)
-{
-    //printf("row_drop_possible\n");
-    UiListModel *model = UI_LIST_MODEL(drag_dest);
-    if(model->model->candrop) {
-        gint *indices = gtk_tree_path_get_indices(dest_path);
-        gint row = indices[0];
-        UiEvent e;
-        e.obj = model->obj;
-        e.window = e.obj->window;
-        e.document = e.obj->ctx->document;
-        e.eventdata = NULL;
-        e.intval = 0;
-        UiSelection s;
-        s.data = selection_data;
-        return model->model->candrop(&e, &s, model->var->value, row);
-    }
-    return TRUE;
-}
-
-gboolean ui_list_model_row_draggable(
-        GtkTreeDragSource   *drag_source,
-        GtkTreePath         *path)
-{
-    //printf("row_draggable\n");
-    UiListModel *model = UI_LIST_MODEL(drag_source);
-    if(model->model->candrag) {
-        gint *indices = gtk_tree_path_get_indices(path);
-        gint row = indices[0];
-        UiEvent e;
-        e.obj = model->obj;
-        e.window = e.obj->window;
-        e.document = e.obj->ctx->document;
-        e.eventdata = NULL;
-        e.intval = 0;
-        return model->model->candrag(&e, model->var->value, row);
-    }
-    return TRUE;
-}
-
-gboolean ui_list_model_drag_data_get(
-        GtkTreeDragSource   *drag_source,
-        GtkTreePath         *path,
-        GtkSelectionData    *selection_data)
-{
-    //printf("drag_data_get\n");
-    UiListModel *model = UI_LIST_MODEL(drag_source);
-    if(model->model->data_get) {
-        gint *indices = gtk_tree_path_get_indices(path);
-        gint row = indices[0];
-        UiEvent e;
-        e.obj = model->obj;
-        e.window = e.obj->window;
-        e.document = e.obj->ctx->document;
-        e.eventdata = NULL;
-        e.intval = 0;
-        UiSelection s;
-        s.data = selection_data;
-        model->model->data_get(&e, &s, model->var->value, row);
-    }
-    return TRUE;
-}
-
-gboolean ui_list_model_drag_data_delete(
-        GtkTreeDragSource *drag_source,
-        GtkTreePath       *path)
-{
-    //printf("drag_data_delete\n");
-    UiListModel *model = UI_LIST_MODEL(drag_source);
-    if(model->model->data_get) {
-        gint *indices = gtk_tree_path_get_indices(path);
-        gint row = indices[0];
-        UiEvent e;
-        e.obj = model->obj;
-        e.window = e.obj->window;
-        e.document = e.obj->ctx->document;
-        e.eventdata = NULL;
-        e.intval = 0;
-        model->model->data_delete(&e, model->var->value, row);
-    }
-    return TRUE;
-}
--- a/ui/gtk/model.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * 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 MODEL_H
-#define	MODEL_H
-
-#include "../ui/toolkit.h"
-#include "../common/context.h"
-#include "../ui/tree.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif    
-
-typedef struct UiListModel        UiListModel;
-
-/*
- * UiList to GtkTreeModel wrapper
- */
-struct UiListModel {
-    GObject  object;
-    UiObject *obj;
-    UiModel  *model;
-    UiVar   *var;
-    GType    *columntypes;
-    int      numcolumns;
-    int      stamp;
-};
-
-/*
- * initialize the class and register the type
- */
-void ui_list_init();
-
-/*
- * Creates a UiListModel for a given UiList
- */
-UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info);
-
-void ui_list_model_dispose(GObject *obj);
-void ui_list_model_finalize(GObject *obj);
-
-
-// interface functions
-
-GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model);
-
-gint ui_list_model_get_n_columns(GtkTreeModel *tree_model);
-
-GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index);
-
-gboolean ui_list_model_get_iter(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreePath *path);
-
-GtkTreePath* ui_list_model_get_path(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter);
-
-void ui_list_model_get_value(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        gint column,
-        GValue *value);
-
-gboolean ui_list_model_iter_next(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter);
-
-gboolean ui_list_model_iter_children(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *parent);
-
-gboolean ui_list_model_iter_has_child(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter);
-
-gint ui_list_model_iter_n_children(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter);
-
-gboolean ui_list_model_iter_nth_child(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *parent,
-        gint n);
-
-gboolean ui_list_model_iter_parent(
-        GtkTreeModel *tree_model,
-        GtkTreeIter *iter,
-        GtkTreeIter *child);
-
-/* dnd */
-
-gboolean ui_list_model_drag_data_received(
-        GtkTreeDragDest   *drag_dest,
-        GtkTreePath       *dest,
-        GtkSelectionData  *selection_data);
-
-gboolean ui_list_model_row_drop_possible(
-        GtkTreeDragDest   *drag_dest,
-        GtkTreePath       *dest_path,
-	GtkSelectionData  *selection_data);
-
-gboolean ui_list_model_row_draggable(
-        GtkTreeDragSource   *drag_source,
-        GtkTreePath         *path);
-
-gboolean ui_list_model_drag_data_get(
-        GtkTreeDragSource   *drag_source,
-        GtkTreePath         *path,
-        GtkSelectionData    *selection_data);
-
-gboolean ui_list_model_drag_data_delete(
-        GtkTreeDragSource *drag_source,
-        GtkTreePath       *path);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* MODEL_H */
--- a/ui/gtk/objs.mk	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/objs.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -38,13 +38,14 @@
 GTKOBJ += button.o
 GTKOBJ += display.o
 GTKOBJ += text.o
-GTKOBJ += model.o
-GTKOBJ += tree.o
+GTKOBJ += list.o
 GTKOBJ += image.o
+GTKOBJ += icon.o
 GTKOBJ += graphics.o
 GTKOBJ += range.o
 GTKOBJ += entry.o
 GTKOBJ += dnd.o
+GTKOBJ += headerbar.o
 
 TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
 TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- a/ui/gtk/range.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/range.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,13 +31,12 @@
 
 #include "range.h"
 #include "container.h"
-#include <ucx/mempool.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
 
 static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL);
 #else
     GtkWidget *scrollbar;
@@ -62,6 +61,7 @@
         event->userdata = userdata;
         event->callback = f;
         event->value = 0;
+        event->customdata = NULL;
         
         g_signal_connect(
                 G_OBJECT(scrollbar),
@@ -124,7 +124,7 @@
 #else
     gtk_adjustment_set_page_size(a, extent);
 #endif
-#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18)
+#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318
     gtk_adjustment_changed(a);
 #endif
     range->extent = extent;
--- a/ui/gtk/text.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/text.c	Sat Jan 04 16:38:48 2025 +0100
@@ -33,6 +33,12 @@
 #include "text.h"
 #include "container.h"
 
+#include <cx/printf.h>
+
+#include <gdk/gdkkeysyms.h>
+
+
+#include "../common/types.h"
 
 static void selection_handler(
         GtkTextBuffer *buf,
@@ -56,8 +62,14 @@
     }
 }
 
-UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);
+    
     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);
+    
     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
     g_signal_connect(
             text_area,
@@ -66,9 +78,12 @@
             NULL);
     
     UiTextArea *uitext = malloc(sizeof(UiTextArea));
+    uitext->obj = obj;
     uitext->ctx = obj->ctx;
     uitext->var = var;
     uitext->last_selection_state = 0;
+    uitext->onchange = args.onchange;
+    uitext->onchangedata = args.onchangedata;
     
     g_signal_connect(
                 text_area,
@@ -76,29 +91,29 @@
                 G_CALLBACK(ui_textarea_destroy),
                 uitext);
     
-    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     gtk_scrolled_window_set_policy(
             GTK_SCROLLED_WINDOW(scroll_area),
             GTK_POLICY_AUTOMATIC,
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
     
     // font and padding
-    PangoFontDescription *font;
-    font = pango_font_description_from_string("Monospace");
-    gtk_widget_modify_font(text_area, font);
-    pango_font_description_free(font);
+    //PangoFontDescription *font;
+    //font = pango_font_description_from_string("Monospace");
+    //gtk_widget_modify_font(text_area, font); // TODO
+    //pango_font_description_free(font);
     
     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
     gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
     
     // add
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, scroll_area, TRUE);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, TRUE);
     
     // bind value
-    UiText *value = var->value;
-    if(value) {
+    if(var) {
+        UiText *value = var->value;
         GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
         
         if(value->value.ptr) {
@@ -150,31 +165,14 @@
 }
 
 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
-    ui_destroy_boundvar(textarea->ctx, textarea->var);
+    if(textarea->var) {
+        ui_destroy_boundvar(textarea->ctx, textarea->var);
+    }
     free(textarea);
 }
 
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = value;
-    var->type = UI_VAR_SPECIAL;
-    var->from = NULL;
-    var->from_ctx = NULL;
-    return ui_textarea_var(obj, var);
-}
-
-UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
-    if(var) {
-        return ui_textarea_var(obj, var);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
-    return gtk_bin_get_child(GTK_BIN(textarea));
+    return SCROLLEDWINDOW_GET_CHILD(textarea);
 }
 
 char* ui_textarea_get(UiText *text) {
@@ -191,7 +189,7 @@
     return str;
 }
 
-void ui_textarea_set(UiText *text, char *str) {
+void ui_textarea_set(UiText *text, const char *str) {
     gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
@@ -273,13 +271,19 @@
 
 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
     UiText *value = textarea->var->value;
+    
+    UiEvent e;
+    e.obj = textarea->obj;
+    e.window = e.obj->window;
+    e.document = textarea->ctx->document;
+    e.eventdata = value;
+    e.intval = 0;
+    
+    if(textarea->onchange) {
+        textarea->onchange(&e, textarea->onchangedata);
+    }
+    
     if(value->observers) {
-        UiEvent e;
-        e.obj = textarea->ctx->obj;
-        e.window = e.obj->window;
-        e.document = textarea->ctx->document;
-        e.eventdata = value;
-        e.intval = 0;
         ui_notify_evt(value->observers, &e);
     }
 }
@@ -304,19 +308,19 @@
     }
     
     if(mgr->cur) {
-        UcxList *elm = mgr->cur->next;
+        UiTextBufOp *elm = mgr->cur->next;
         if(elm) {
             mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
             while(elm) {
                 elm->prev = NULL;   
-                UcxList *next = elm->next;
-                ui_free_textbuf_op(elm->data);
-                free(elm);
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
                 elm = next;
             }
         }
         
-        UiTextBufOp *last_op = mgr->cur->data;
+        UiTextBufOp *last_op = mgr->cur;
         if(
             last_op->type == UI_TEXTBUF_INSERT &&
             ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
@@ -341,15 +345,22 @@
     dpstr[length] = 0;
     
     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
     op->type = UI_TEXTBUF_INSERT;
     op->start = gtk_text_iter_get_offset(location);
     op->end = op->start+length;
     op->len = length;
     op->text = dpstr;
     
-    UcxList *elm = ucx_list_append(NULL, op);
-    mgr->cur = elm;
-    mgr->begin = ucx_list_concat(mgr->begin, elm);
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
 }
 
 void ui_textbuf_delete(
@@ -369,14 +380,14 @@
     }
     
     if(mgr->cur) {
-        UcxList *elm = mgr->cur->next;
+        UiTextBufOp *elm = mgr->cur->next;
         if(elm) {
             mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
             while(elm) {
                 elm->prev = NULL;   
-                UcxList *next = elm->next;
-                ui_free_textbuf_op(elm->data);
-                free(elm);
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
                 elm = next;
             }
         }
@@ -385,6 +396,8 @@
     char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
     
     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
     op->type = UI_TEXTBUF_DELETE;
     op->start = gtk_text_iter_get_offset(start);
     op->end = gtk_text_iter_get_offset(end);
@@ -395,20 +408,39 @@
     dpstr[op->len] = 0;
     op->text = dpstr;
     
-    UcxList *elm = ucx_list_append(NULL, op);
-    mgr->cur = elm;
-    mgr->begin = ucx_list_concat(mgr->begin, elm);
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
 }
 
 UiUndoMgr* ui_create_undomgr() {
     UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
     mgr->begin = NULL;
+    mgr->end = NULL;
     mgr->cur = NULL;
     mgr->length = 0;
     mgr->event = 1;
     return mgr;
 }
 
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+    UiTextBufOp *op = mgr->begin;
+    while(op) {
+        UiTextBufOp *nextOp = op->next;
+        if(op->text) {
+            free(op->text);
+        }
+        free(op);
+        op = nextOp;
+    }
+    free(mgr);
+}
+
 void ui_free_textbuf_op(UiTextBufOp *op) {
     if(op->text) {
         free(op->text);
@@ -440,7 +472,7 @@
     UiUndoMgr *mgr = value->undomgr;
     
     if(mgr->cur) {
-        UiTextBufOp *op = mgr->cur->data;
+        UiTextBufOp *op = mgr->cur;
         mgr->event = 0;
         switch(op->type) {
             case UI_TEXTBUF_INSERT: {
@@ -468,7 +500,7 @@
 void ui_text_redo(UiText *value) {
     UiUndoMgr *mgr = value->undomgr;
     
-    UcxList *elm = NULL;
+    UiTextBufOp *elm = NULL;
     if(mgr->cur) {
         if(mgr->cur->next) {
             elm = mgr->cur->next;
@@ -478,7 +510,7 @@
     }
     
     if(elm) {
-        UiTextBufOp *op = elm->data;
+        UiTextBufOp *op = elm;
         mgr->event = 0;
         switch(op->type) {
             case UI_TEXTBUF_INSERT: {
@@ -504,12 +536,23 @@
 }
 
 
-static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
+
+
+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);
+    
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
     
     UiTextField *uitext = malloc(sizeof(UiTextField));
-    uitext->ctx = obj->ctx;
+    uitext->obj = obj;
     uitext->var = var;
+    uitext->onchange = args.onchange;
+    uitext->onchangedata = args.onchangedata;
+    uitext->onactivate = args.onactivate;
+    uitext->onactivatedata = args.onactivatedata;
     
     g_signal_connect(
                 textfield,
@@ -517,8 +560,11 @@
                 G_CALLBACK(ui_textfield_destroy),
                 uitext);
     
-    if(width > 0) {
-        gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
+    if(args.width > 0) {
+        // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
+#endif
     }
     if(frameless) {
         // TODO: gtk2legacy workaroud
@@ -528,13 +574,13 @@
         gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, textfield, FALSE);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, textfield, FALSE);
     
     if(var) {
         UiString *value = var->value;
         if(value->value.ptr) {
-            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            ENTRY_SET_TEXT(textfield, value->value.ptr);
             value->value.free(value->value.ptr);
             value->value.ptr = NULL;
             value->value.free = NULL;
@@ -545,7 +591,9 @@
         value->value.ptr = NULL;
         value->value.free = NULL;
         value->obj = GTK_ENTRY(textfield);
-        
+    }
+    
+    if(args.onchange || var) {
         g_signal_connect(
                 textfield,
                 "changed",
@@ -553,105 +601,544 @@
                 uitext);
     }
     
+    if(args.onactivate) {
+        g_signal_connect(
+                textfield,
+                "activate",
+                G_CALLBACK(ui_textfield_activate),
+                uitext);
+    }
+    
     return textfield;
 }
 
-static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
-    if(var) {
-        return create_textfield_var(obj, width, frameless, password, var);
-    } else {
-        // TODO: error
-    }
-    return NULL;
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, FALSE, args);
 }
 
-static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
-    UiVar *var = NULL;
-    if(value) {
-        var = malloc(sizeof(UiVar));
-        var->value = value;
-        var->type = UI_VAR_SPECIAL;
-        var->from = NULL;
-        var->from_ctx = NULL;
-    }
-    return create_textfield_var(obj, width, frameless, password, var);
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, TRUE, FALSE, args);
 }
 
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, TRUE, args);
+}
+
+
 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
-    if(textfield->var) {
-        ui_destroy_boundvar(textfield->ctx, textfield->var);
-    }
     free(textfield);
 }
 
 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
     UiString *value = textfield->var->value;
-    if(value->observers) {
-        UiEvent e;
-        e.obj = textfield->ctx->obj;
-        e.window = e.obj->window;
-        e.document = textfield->ctx->document;
-        e.eventdata = value;
-        e.intval = 0;
+    
+    UiEvent e;
+    e.obj = textfield->obj;
+    e.window = e.obj->window;
+    e.document = textfield->obj->ctx->document;
+    e.eventdata = value;
+    e.intval = 0;
+    
+    if(textfield->onchange) {
+        textfield->onchange(&e, textfield->onchangedata);
+    }
+    
+    if(textfield->var) {
         ui_notify_evt(value->observers, &e);
     }
 }
 
-UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, TRUE, FALSE, value);
-}
-
-UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
-}
-
-UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
-}
-
-UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) {
+    if(textfield->onactivate) {
+        UiEvent e;
+        e.obj = textfield->obj;
+        e.window = e.obj->window;
+        e.document = textfield->obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        textfield->onactivate(&e, textfield->onactivatedata);
+    }
 }
 
 char* ui_textfield_get(UiString *str) {
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
     }
-    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj));
     str->value.free = (ui_freefunc)g_free;
     return str->value.ptr;
 }
 
-void ui_textfield_set(UiString *str, char *value) {
-    gtk_entry_set_text(str->obj, value);
+void ui_textfield_set(UiString *str, const char *value) {
+    ENTRY_SET_TEXT(str->obj, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
         str->value.ptr = NULL;
         str->value.free = NULL;
     }
 }
+
+// ----------------------- path textfield -----------------------
+
+// TODO: move to common
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
+    cxstring *pathelms;
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+
+    if (nelm == 0) {
+        *ret_nelm = 0;
+        return NULL;
+    }
+
+    UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));
+    size_t n = nelm;
+    int j = 0;
+    for (int i = 0; i < nelm; i++) {
+        cxstring c = pathelms[i];
+        if (c.length == 0) {
+            if (i == 0) {
+                c.length = 1;
+            }
+            else {
+                n--;
+                continue;
+            }
+        }
+
+        cxmutstr m = cx_strdup(c);
+        elms[j].name = m.ptr;
+        elms[j].name_len = m.length;
+        
+        size_t elm_path_len = c.ptr + c.length - full_path;
+        cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
+        elms[j].path = elm_path.ptr;
+        elms[j].path_len = elm_path.length;
+
+        j++;
+    }
+    *ret_nelm = n;
+
+    return elms;
+}
+
+static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
+    for(int i=0;i<nelm;i++) {
+        free(elms[i].name);
+        free(elms[i].path);
+    }
+    free(elms);
+}
+
+static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
+    g_object_unref(pathtf->entry);
+    free(pathtf);
+}
+
+void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) {
+    UiPathTextField *pathtf = event->customdata1;
+    for(int i=0;i<event->value1;i++) {
+        if(i <= event->value0) {
+            WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+        } else {
+            WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+        }
+    }
+    
+    UiPathElm *elm = event->customdata0;
+    cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = evt.obj->window;
+    evt.document = evt.obj->ctx->document;
+    evt.eventdata = elm->path;
+    evt.intval = event->value0;
+    event->callback(&evt, event->userdata);
+    free(path.ptr);
+}
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) {
+    size_t full_path_len = strlen(full_path);
+    if(full_path_len == 0) {
+        return 1;
+    }
+    
+    size_t nelm = 0;
+    UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
+    if (!path_elm) {
+        return 1;
+    }
+    
+    free(pathtf->current_path);
+    pathtf->current_path = strdup(full_path);
+    
+    ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
+    free(pathtf->current_path_buttons);
+    pathtf->current_path_buttons = calloc(nelm, sizeof(GtkWidget*));
+    pathtf->current_pathelms = path_elm;
+    pathtf->current_nelm = nelm;
+    
+    return ui_pathtextfield_update_widget(pathtf);
+}
+
+static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) {
+    cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
+    GtkWidget *button = gtk_button_new_with_label(name.ptr);
+    pathtf->current_path_buttons[i] = button;
+    free(name.ptr);
+
+    if(pathtf->onactivate) {
+        UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt));
+        memset(eventdata, 0, sizeof(UiEventDataExt));
+        eventdata->callback = pathtf->onactivate;
+        eventdata->userdata = pathtf->onactivatedata;
+        eventdata->obj = pathtf->obj;
+        eventdata->customdata0 = elm;
+        eventdata->customdata1 = pathtf;
+        eventdata->value0 = i;
+        eventdata->value1 = pathtf->current_nelm;        
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_path_button_clicked),
+                eventdata);
+
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                eventdata);
+    }
+    
+    return button;
+}
+
+static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
+    const gchar *text = ENTRY_GET_TEXT(pathtf->entry);
+    if(strlen(text) == 0) {
+        return;
+    }
+    
+    UiObject *obj = pathtf->obj;
+    
+    if(ui_pathtextfield_update(pathtf, text)) {
+        return;
+    }
+    
+    if(pathtf->onactivate) {
+        UiEvent evt;
+        evt.obj = obj;
+        evt.window = obj->window;
+        evt.document = obj->ctx->document;
+        evt.eventdata = (char*)text;
+        evt.intval = -1;
+        pathtf->onactivate(&evt, pathtf->onactivatedata);
+    }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
+    if(pathtf->current_path) {
+        gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+        ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
+    }
+}
+
+static gboolean ui_path_textfield_key_controller(
+        GtkEventControllerKey* self,
+        guint keyval,
+        guint keycode,
+        GdkModifierType state,
+        UiPathTextField *pathtf)
+{
+    if(keyval == GDK_KEY_Escape) {
+        pathbar_show_hbox(NULL, pathtf);
+    }
+    return FALSE;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    pathtf->stack = gtk_stack_new();
+    gtk_widget_set_name(pathtf->stack, "path-textfield-box");
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, pathtf->stack, FALSE);
+    
+    pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    pathtf->entry = gtk_entry_new();
+    gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry);
+    gtk_widget_set_hexpand(pathtf->entry, TRUE);
+    
+    GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic");
+    gtk_widget_add_css_class(cancel_button, "flat");
+    gtk_widget_add_css_class(cancel_button, "pathbar-extra-button");
+    gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button);
+    g_signal_connect(
+                cancel_button,
+                "clicked",
+                G_CALLBACK(pathbar_show_hbox),
+                pathtf);
+    
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    g_object_ref(pathtf->entry); // for compatibility with older pathbar version
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    
+    GtkEventController *entry_cancel = gtk_event_controller_key_new();
+    gtk_widget_add_controller(pathtf->entry, entry_cancel);
+    g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf);
+    
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return pathtf->stack;    
+}
+
+static void pathbar_pressed(
+        GtkGestureClick* self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        UiPathTextField *pathtf)
+{
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    gtk_widget_grab_focus(pathtf->entry);
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    // recreate button hbox
+    if(pathtf->hbox) {
+        gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox);
+    }
+    pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE);
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    gtk_widget_set_name(pathtf->hbox, "pathbar");
+    
+    // add buttons for path elements
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_widget_add_css_class(button, "flat");
+        
+        gtk_box_append(GTK_BOX(pathtf->hbox), button);
+        
+        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+            GtkWidget *path_separator = gtk_label_new("/");
+            gtk_widget_add_css_class(path_separator, "pathbar-button-inactive");
+            gtk_box_append(GTK_BOX(pathtf->hbox), path_separator);
+        }
+    }
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    
+    // create a widget for receiving button press events
+    GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    GtkGesture *handler = gtk_gesture_click_new();
+    gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler));
+    g_signal_connect(
+                handler,
+                "pressed",
+                G_CALLBACK(pathbar_pressed),
+                pathtf);
+    gtk_widget_set_hexpand(event_area, TRUE);
+    gtk_widget_set_vexpand(event_area, TRUE);
+    gtk_box_append(GTK_BOX(pathtf->hbox), event_area);
+    
+    return 0;
+}
+
+#else
+
+static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0);
+    gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+    pathtf->buttonbox = NULL;
+    
+    gtk_widget_show(pathtf->entry);
+    gtk_widget_grab_focus(pathtf->entry);    
+    
+    return TRUE;
+}
+
+static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
+    if (event->keyval == GDK_KEY_Escape) {
+        // reset GtkEntry value
+        gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path);
+        const gchar *text = gtk_entry_get_text(GTK_ENTRY(self));
+        ui_pathtextfield_update(pathtf, text);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static GtkWidget* create_path_button_box() {
+    GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style
+    gtk_box_set_homogeneous(GTK_BOX(bb), FALSE);
+    gtk_box_set_spacing(GTK_BOX(bb), 0);
+    return bb;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    // top level container for the path textfield is a GtkEventBox
+    // the event box is needed to handle background button presses
+    GtkWidget *eventbox = gtk_event_box_new();
+    g_signal_connect(
+            eventbox,
+            "button-press-event",
+            G_CALLBACK(path_textfield_btn_pressed),
+            pathtf);
+    g_signal_connect(
+            eventbox,
+            "destroy",
+            G_CALLBACK(ui_path_textfield_destroy),
+            pathtf);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, eventbox, FALSE);
+    
+    // hbox as parent for the GtkEntry and GtkButtonBox
+    GtkWidget *hbox = ui_gtk_hbox_new(0);
+    pathtf->hbox = hbox;
+    gtk_container_add(GTK_CONTAINER(eventbox), hbox);
+    gtk_widget_set_name(hbox, "path-textfield-box");
+    
+    // create GtkEntry, that is also visible by default (with input yet)
+    pathtf->entry = gtk_entry_new();
+    g_object_ref(G_OBJECT(pathtf->entry));
+    gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0);
+    
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    g_signal_connect(
+            pathtf->entry,
+            "key-press-event",
+            G_CALLBACK(ui_path_textfield_key_press),
+            pathtf);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return hbox;
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    GtkWidget *buttonbox = create_path_button_box();
+    
+    // switch from entry to buttonbox or remove current buttonbox
+    if(pathtf->buttonbox) {
+        gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+    } else {
+        gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry);
+    }
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
+    pathtf->buttonbox = buttonbox;
+    
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
+    }
+    
+    gtk_widget_show_all(buttonbox);
+    
+    return 0;
+}
+
+#endif
+
+char* ui_path_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    UiPathTextField *tf = str->obj;
+    str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_path_textfield_set(UiString *str, const char *value) {
+    UiPathTextField *tf = str->obj;
+    ENTRY_SET_TEXT(tf->entry, value);
+    ui_pathtextfield_update(tf, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
--- a/ui/gtk/text.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/text.h	Sat Jan 04 16:38:48 2025 +0100
@@ -31,7 +31,7 @@
 
 #include "../ui/text.h"
 #include "toolkit.h"
-#include <ucx/list.h>
+#include <cx/linked_list.h>
 #include "../common/context.h"
 
 #ifdef	__cplusplus
@@ -40,38 +40,79 @@
 
 #define UI_TEXTBUF_INSERT 0
 #define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp {
+    
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+    UiTextBufOp *prev;
+    UiTextBufOp *next;
     int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
     int  start;
     int  end;
     int  len;
     char *text;
-} UiTextBufOp;
+};
 
 typedef struct UiUndoMgr {
-    UcxList *begin;
-    UcxList *cur;
-    int     length;
-    int     event;
+    UiTextBufOp  *begin;
+    UiTextBufOp  *end;
+    UiTextBufOp  *cur;
+    int          length;
+    int          event;
 } UiUndoMgr;
 
 typedef struct UiTextArea {
-    UiContext *ctx;
-    UiVar    *var;
-    int       last_selection_state;
+    UiObject    *obj;
+    UiContext   *ctx;
+    UiVar       *var;
+    int         last_selection_state;
+    ui_callback onchange;
+    void        *onchangedata;
 } UiTextArea;
 
 typedef struct UiTextField {
-    UiContext *ctx;
-    UiVar    *var;
-    // TODO: validatefunc
+    UiObject    *obj;
+    UiVar       *var;
+    ui_callback onchange;
+    void        *onchangedata;
+    ui_callback onactivate;
+    void        *onactivatedata;
 } UiTextField;
 
+typedef struct UiPathTextField {
+    UiObject *obj;
+    
+    GtkWidget *stack;
+    GtkWidget *hbox;
+    GtkWidget *entry_box;
+    GtkWidget *entry;
+#if GTK_MAJOR_VERSION == 3
+    GtkWidget *buttonbox;
+#endif
+    
+    char *current_path;
+    UiPathElm *current_pathelms;
+    GtkWidget **current_path_buttons;
+    size_t current_nelm;
+    
+    ui_pathelm_func getpathelm;
+    void* getpathelmdata;
+
+    ui_callback onactivate;
+    void* onactivatedata;
+    
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropdata;
+} UiPathTextField;
+
 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
 
 char* ui_textarea_get(UiText *text);
-void ui_textarea_set(UiText *text, char *str);
+void ui_textarea_set(UiText *text, const char *str);
 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);
@@ -95,14 +136,21 @@
         GtkTextIter *end,
         void *data);
 UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
 void ui_free_textbuf_op(UiTextBufOp *op);
 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
 
 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield);
 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield);
+void ui_textfield_activate(GtkEntry* self, UiTextField *textfield);
 
 char* ui_textfield_get(UiString *str);
-void ui_textfield_set(UiString *str, char *value);
+void ui_textfield_set(UiString *str, const char *value);
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path);
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf);
+char* ui_path_textfield_get(UiString *str);
+void ui_path_textfield_set(UiString *str, const char *value);
 
 #ifdef	__cplusplus
 }
--- a/ui/gtk/toolbar.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/toolbar.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,199 +31,20 @@
 #include <string.h>
 
 #include "toolbar.h"
+#include "menu.h"
 #include "button.h"
-#include "image.h"
-#include "tree.h"
-#include <ucx/mempool.h>
+#include "icon.h"
+#include "list.h"
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
 #include "../common/context.h"
 
-static UcxMap *toolbar_items;
-static UcxList *defaults;
 
-void ui_toolbar_init() {
-    toolbar_items = ucx_map_new(16);
-}
-
-void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
-    ui_toolitem_img(name, label, NULL, f, udata);
-}
-
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgr(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgri(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    va_list ap;
-    va_start(ap, userdata);
-    ui_toolitem_vstgr(name, stockid, 0, f, userdata, ap);
-    va_end(ap);
-}
-
-void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    va_list ap;
-    va_start(ap, userdata);
-    ui_toolitem_vstgr(name, stockid, 1, f, userdata, ap);
-    va_end(ap);
-}
-
-void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
-    item->label = label;
-    item->image = img;
-    item->callback = f;
-    item->userdata = udata;
-    item->isimportant = 0;
-    item->groups = NULL;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_vstgr(
-        char *name,
-        char *stockid,
-        int isimportant,
-        ui_callback f,
-        void *userdata,
-        va_list ap)
-{
-    UiStToolItem *item = malloc(sizeof(UiStToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
-    item->stockid = stockid;
-    item->callback = f;
-    item->userdata = userdata;
-    item->groups = NULL;
-    item->isimportant = isimportant;
-    
-    // add groups
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i) {
-    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = label;
-    item->image = img;
-    item->stockid = NULL;
-    item->groups = NULL;
-    item->isimportant = 0;
-    item->value = i;
-    item->var = NULL;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i) {
-    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = NULL;
-    item->image = NULL;
-    item->stockid = stockid;
-    item->groups = NULL;
-    item->isimportant = 0;
-    item->value = i;
-    item->var = NULL;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar) {
-    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = label;
-    item->image = img;
-    item->stockid = NULL;
-    item->groups = NULL;
-    item->isimportant = 0;
-    item->value = NULL;
-    item->var = intvar;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar) {
-    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = NULL;
-    item->image = NULL;
-    item->stockid = stockid;
-    item->groups = NULL;
-    item->isimportant = 0;
-    item->value = NULL;
-    item->var = intvar;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-
-void ui_toolbar_combobox(
-        char *name,
-        UiList *list,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    var->from = NULL;
-    var->from_ctx = NULL;
-    cb->var = var;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    ucx_map_cstr_put(toolbar_items, name, cb);
-}
-
-void ui_toolbar_combobox_str(
-        char *name,
-        UiList *list,
-        ui_callback f,
-        void *udata)
-{
-    ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
-}
-
-void ui_toolbar_combobox_nv(
-        char *name,
-        char *listname,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;  
-    cb->listname = listname;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    ucx_map_cstr_put(toolbar_items, name, cb);
-}
-
-
-void ui_toolbar_add_default(char *name) {
-    char *s = strdup(name);
-    defaults = ucx_list_append(defaults, s);
-}
+#if UI_GTK2 || UI_GTK3
 
 GtkWidget* ui_create_toolbar(UiObject *obj) {
-    if(!defaults) {
-        return NULL;
-    }
-    
     GtkWidget *toolbar = gtk_toolbar_new();
 #ifdef UI_GTK3
     gtk_style_context_add_class(
@@ -231,39 +52,110 @@
             GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
 #endif
     
+    CxMap *items = uic_get_toolbar_items();
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_toolbar_add_items(obj, toolbar, items, left_defaults);
+    ui_toolbar_add_items(obj, toolbar, items, center_defaults);
+    ui_toolbar_add_items(obj, toolbar, items, right_defaults);
+    
+    /*
     GtkToolbar *tb = GTK_TOOLBAR(toolbar);
-    UCX_FOREACH(elm, defaults) {
-        UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data);
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char *, def, i) {
+        UiToolItemI *item = cxMapGet(toolbar_items, def);
         if(item) {
             item->add_to(tb, item, obj);
-        } else if(!strcmp(elm->data, "@separator")) {
+        } else if(!strcmp(def, "@separator")) {
             gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1);
         } else {
-            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
         }
     }
+    */
     
     return toolbar;
 }
 
-void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj) {
-    GtkToolItem *button = gtk_tool_button_new(NULL, item->label);
-    gtk_tool_item_set_homogeneous(button, FALSE);
-    if(item->image) {
-        GdkPixbuf *pixbuf = ui_get_image(item->image);
-        GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
-        gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+static void create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+    GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            add_toolitem_widget(tb, (UiToolbarItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            add_toolitem_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            add_toolitem_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults) {
+    // add pre-configured items
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        create_item(obj, toolbar, item);
+    }
+}
+
+static void set_toolbutton_icon(GtkToolItem *item, const char *icon_name) {
+#if GTK_MAJOR_VERSION >= 3
+        gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), icon_name);
+#else
+        UiIcon *icon = ui_icon(icon_name, 24);
+        if(icon) {
+            GdkPixbuf *pixbuf = ui_icon_pixbuf(icon);
+            if(pixbuf) {
+                GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+                gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), image);
+            }
+        }
+#endif
+}
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+        // TODO: gtk3 stock
+        button = gtk_tool_button_new(NULL, item->args.label);
+#endif
     } else {
-        gtk_tool_item_set_is_important(button, TRUE);
+        button = gtk_tool_button_new(NULL, item->args.label);
     }
     
-    if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->args.icon) {
+        set_toolbutton_icon(button, item->args.icon);
+    }
+    gtk_tool_item_set_is_important(button, TRUE);
+    
+    ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
+    
+    if(item->args.onclick) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
                 sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
+        event->callback = item->args.onclick;
+        event->userdata = item->args.onclickdata;
+        event->customdata = NULL;
+        event->value = 0;
         
         g_signal_connect(
                 button,
@@ -274,103 +166,72 @@
     
     gtk_toolbar_insert(tb, button, -1);
     
+    /*
     if(item->groups) {
         uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
     }
+    */
 }
 
-void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) {
-    GtkToolItem *button = gtk_tool_button_new_from_stock(item->stockid);
-    gtk_tool_item_set_homogeneous(button, FALSE);
-    if(item->isimportant) {
-        gtk_tool_item_set_is_important(button, TRUE);
-    }
-    
-    if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        
-        g_signal_connect(
-                button,
-                "clicked",
-                G_CALLBACK(ui_button_clicked),
-                event);
-    }
-    
-    gtk_toolbar_insert(tb, button, -1);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
-    }
-}
-
-void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj) {
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj) {
     GtkToolItem *button;
-    if(item->stockid) {
-        button = gtk_toggle_tool_button_new_from_stock(item->stockid);
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_toggle_tool_button_new_from_stock(item->args.stockid);
+#else
+        button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); // TODO: gtk3 stock
+#endif
     } else {
         button = gtk_toggle_tool_button_new();
         gtk_tool_item_set_homogeneous(button, FALSE);
-        if(item->label) {
-            gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->label);
+        if(item->args.label) {
+            gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.label);
         }
-        if(item->image) {
-            GdkPixbuf *pixbuf = ui_get_image(item->image);
-            GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
-            gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+        if(item->args.icon) {
+            set_toolbutton_icon(button, item->args.icon);
         }    
     }
+    ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
     
-    UiVar *var;
-    if(item->value) {
-        var = malloc(sizeof(UiVar));
-        var->value = item->value;
-        var->type = UI_VAR_SPECIAL;
-        var->from = NULL;
-        var->from_ctx = NULL;
-    } else {
-        var = uic_create_var(obj->ctx, item->var, UI_VAR_INTEGER);
-    }
-    
-    if(var->value) {
-        UiInteger *i = var->value;
-        i->get = ui_tool_toggle_button_get;
-        i->set = ui_tool_toggle_button_set;
-        i->obj = button;
-        
-        if(i->value != 0) {
-            gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE);
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *i = (UiInteger*)var->value;
+	if(i) {
+            i->get = ui_tool_toggle_button_get;
+            i->set = ui_tool_toggle_button_set;
+            i->obj = button;
+
+            if(i->value != 0) {
+                gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE);
+            }
         }
     }
     
-    // register event
-    // the event func will call the UiInteger observer callbacks
-    UiEventData *event = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiEventData));
+    UiVarEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiVarEventData));
     event->obj = obj;
-    event->userdata = var;
-    event->callback = NULL;
+    event->callback = item->args.onchange;
+    event->userdata = item->args.onchangedata;
+    event->var = var;
 
     g_signal_connect(
-            button,
-            "toggled",
-            G_CALLBACK(ui_tool_button_toggled),
-            event);
+        button,
+        "toggled",
+        G_CALLBACK(ui_tool_button_toggled),
+        event);
     
     // add item to toolbar
     gtk_toolbar_insert(tb, button, -1);
     
+    /*
     if(item->groups) {
         uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
     }
+    */
 }
 
-void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event) {
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event) {
     UiEvent e;
     e.obj = event->obj;
     e.window = event->obj->window;
@@ -378,10 +239,16 @@
     e.eventdata = NULL;
     e.intval = gtk_toggle_tool_button_get_active(widget);
     
-    UiVar *var = event->userdata;
-    UiInteger *i = var->value;
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
     
-    ui_notify_evt(i->observers, &e);
+    UiVar *var = event->var;
+    UiInteger *i = var ? var->value : NULL;
+    
+    if(i) {
+        ui_notify_evt(i->observers, &e);
+    }
 }
 
 int64_t ui_tool_toggle_button_get(UiInteger *integer) {
@@ -395,6 +262,70 @@
     integer->value = s;
 }
 
+
+
+typedef struct UiToolbarMenuWidget {
+    GtkWidget *button;
+    GtkMenu *menu;
+} UiToolbarMenuWidget;
+
+static void ui_toolbar_menubutton_clicked(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+    int x;
+    gtk_menu_popup_at_widget(menu->menu, menu->button, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+}
+
+static void ui_toolbar_menubutton_destroy(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+    free(menu);
+}
+
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+        // TODO: gtk3 stock
+        button = gtk_tool_button_new(NULL, item->args.label);
+#endif
+    } else {
+        button = gtk_tool_button_new(NULL, item->args.label);
+    }
+    
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->args.icon) {
+        set_toolbutton_icon(button, item->args.icon);
+    }
+    gtk_tool_item_set_is_important(button, TRUE);
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    // menu
+    GtkWidget *menu_widget = gtk_menu_new();
+    ui_add_menu_items(menu_widget, 0, &item->menu, obj); 
+    gtk_widget_show_all(menu_widget);
+    
+    UiToolbarMenuWidget *tbmenu = malloc(sizeof(UiToolbarMenuWidget));
+    tbmenu->button = GTK_WIDGET(button);
+    tbmenu->menu = GTK_MENU(menu_widget);
+    
+    g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_toolbar_menubutton_clicked),
+                tbmenu);
+    
+    g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_toolbar_menubutton_destroy),
+                tbmenu);
+}
+
+
+
+
+// deprecated / unsupported
+/*
 void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) {
     UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
     modelinfo->getvalue = cb->getvalue;
@@ -419,4 +350,79 @@
         gtk_toolbar_insert(tb, item, -1);
     }
 }
+*/
 
+
+
+#ifdef UI_GTK3
+
+GtkWidget* ui_create_headerbar(UiObject *obj) {
+    GtkWidget *headerbar = gtk_header_bar_new();
+    
+    CxMap *items = uic_get_toolbar_items();
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, left_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, center_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, right_defaults);
+    
+    return headerbar;
+}
+
+static void hb_create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+    GtkHeaderBar *tb = GTK_HEADER_BAR(toolbar);
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            add_headerbar_item_widget(tb, (UiToolbarItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            add_headerbar_item_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            add_headerbar_item_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
+    // add pre-configured items
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        hb_create_item(obj, headerbar, item);
+    }
+}
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj) {
+    GtkWidget *button = gtk_button_new_with_label(item->args.label);
+    if(item->args.icon) {
+        ui_button_set_icon_name(button, item->args.icon);
+    }
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    
+    gtk_header_bar_pack_start(hb, button);
+    
+}
+
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj) {
+    
+}
+
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj) {
+    
+}
+
+#endif /* UI_GTK3 */
+
+#endif /* UI_GTK2 || UI_GTK3 */
--- a/ui/gtk/toolbar.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/toolbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -30,16 +30,18 @@
 #define	TOOLBAR_H
 
 #include "../ui/toolbar.h"
-#include <ucx/map.h>
-#include <ucx/list.h>
+#include "../common/toolbar.h"
+#include <cx/map.h>
+#include <cx/list.h>
 
-#include "model.h"
-#include "tree.h"
+#include "list.h"
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
+#if UI_GTK2 || UI_GTK3
+    
 typedef struct UiToolItemI      UiToolItemI;
 typedef struct UiToolItem       UiToolItem;
 typedef struct UiStToolItem     UiStToolItem;
@@ -61,7 +63,7 @@
     ui_callback    callback;
     void           *userdata;
     const char     *varname;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
@@ -71,7 +73,7 @@
     ui_callback    callback;
     void           *userdata;
     const char     *varname;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
@@ -82,7 +84,7 @@
     const char     *stockid;
     UiInteger      *value;
     const char     *var;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
@@ -97,12 +99,11 @@
 struct UiToolbarComboBoxNV {
     UiToolItemI         item;
     char                *listname;
-    ui_getvaluefunc getvalue;
+    ui_getvaluefunc     getvalue;
     ui_callback         callback;
     void                *userdata;
 };
 
-void ui_toolbar_init();
 
 void ui_toolitem_vstgr(
         char *name,
@@ -114,18 +115,33 @@
 
 GtkWidget* ui_create_toolbar(UiObject *obj);
 
-void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj);
-void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj);
-void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj);
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults);
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj);
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj);
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event);
+int64_t ui_tool_toggle_button_get(UiInteger *integer);
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
 
+GtkWidget* ui_create_headerbar(UiObject *obj);
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj);
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj);
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj);
+
+
+/*
 void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj);
 void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj);
 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
 void ui_combobox_update(UiEvent *event, void *combobox);
+*/
 
-void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event);
-int64_t ui_tool_toggle_button_get(UiInteger *integer);
-void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
+#endif
 
 #ifdef	__cplusplus
 }
--- a/ui/gtk/toolkit.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/toolkit.c	Sat Jan 04 16:38:48 2025 +0100
@@ -33,20 +33,24 @@
 
 #include "toolkit.h"
 #include "toolbar.h"
-#include "model.h"
-#include "image.h"
+#include "icon.h"
 #include "../common/document.h"
 #include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+#include "../common/threadpool.h"
 
-#include <ucx/utils.h>
+#include <cx/utils.h>
+#include <cx/string.h>
+#include <cx/printf.h>
 
 #include <pthread.h>
 
-#ifndef UI_GTK2
-static GtkApplication *app;
+#ifdef UI_APPLICATION
+UI_APPLICATION app;
 #endif
 
-static char *application_name;
+static const char *application_name;
 
 static ui_callback   startup_func;
 static void          *startup_data;
@@ -62,29 +66,32 @@
 
 static int scale_factor = 1;
 
-void ui_init(char *appname, int argc, char **argv) {
+UIEXPORT void ui_init(const char *appname, int argc, char **argv) {
+    application_name = appname;
     uic_init_global_context();
     
+#if GTK_MAJOR_VERSION >= 4
+    gtk_init();
+#else
     gtk_init(&argc, &argv);
-    application_name = appname;
-    
-    uic_docmgr_init();
-    ui_toolbar_init();
+#endif
     
-    // init custom types
-    ui_list_init();
-    
+    ui_css_init();
+    uic_docmgr_init();
+    uic_menu_init();
+    uic_toolbar_init();
     ui_image_init();
-    
     uic_load_app_properties();
     
-#ifdef UI_SUPPORTS_SCALE
+#if GTK_MAJOR_VERSION >= 4
+    scale_factor = 1; // TODO
+#elif defined(UI_SUPPORTS_SCALE)
     scale_factor = gdk_monitor_get_scale_factor(
             gdk_display_get_primary_monitor(gdk_display_get_default()));
 #endif
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
@@ -117,14 +124,11 @@
 #endif
 
 void ui_main() {
-#ifndef UI_GTK2
-    sstr_t appid = ucx_sprintf(
+#ifdef UI_APPLICATION
+    cxmutstr appid = cx_asprintf(
             "ui.%s",
             application_name ? application_name : "application1");
-    
-    app = gtk_application_new(
-            appid.ptr,
-            G_APPLICATION_FLAGS_NONE);
+    app = UI_APPLICATION_NEW(appid.ptr);
     g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
     g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
     g_application_run(G_APPLICATION (app), 0, NULL);
@@ -149,17 +153,32 @@
 }
 
 GtkApplication* ui_get_application() {
-    return app;
+    return GTK_APPLICATION(app);
 }
 #endif
 
 void ui_show(UiObject *obj) {
+    gboolean visible = gtk_widget_is_visible(obj->widget);
+    
     uic_check_group_widgets(obj->ctx);
+#if GTK_MAJOR_VERSION >= 4
+    gtk_window_present(GTK_WINDOW(obj->widget));
+#elif GTK_MAJOR_VERSION <= 3
     gtk_widget_show_all(obj->widget);
+#endif
+    
+    if(!visible) {
+        obj->ref++;
+    }
 }
 
 void ui_close(UiObject *obj) {
+    uic_context_prepare_close(obj->ctx);
+#if GTK_CHECK_VERSION(4, 0, 0)
+    gtk_window_close(GTK_WINDOW(obj->widget));
+#else
     gtk_widget_destroy(obj->widget);
+#endif
 }
 
 
@@ -181,12 +200,31 @@
 static void* ui_jobthread(void *data) {
     UiJob *job = data;
     int result = job->job_func(job->job_data);
-    if(!result) {
+    if(!result && job->finish_callback) {
         g_idle_add(ui_job_finished, job);
+    } else {
+        free(job);
     }
     return NULL;
 }
 
+static gboolean ui_idle_func(void *data) {
+    UiJob *job = data;
+    job->job_func(job->job_data);
+    free(job);
+    return FALSE;
+}
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+    UiJob *job = malloc(sizeof(UiJob));
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = NULL;
+    job->finish_data = NULL;
+    job->obj = NULL;
+    g_idle_add(ui_idle_func, job);
+}
+
 void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
     UiJob *job = malloc(sizeof(UiJob));
     job->obj = obj;
@@ -203,24 +241,38 @@
 }
 
 void ui_set_show_all(UIWIDGET widget, int value) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
     gtk_widget_set_no_show_all(widget, !value);
+#endif
 }
 
 void ui_set_visible(UIWIDGET widget, int visible) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
     if(visible) {
         gtk_widget_set_no_show_all(widget, FALSE);
         gtk_widget_show_all(widget);
     } else {
         gtk_widget_hide(widget);
     }
+#endif
 }
 
 void ui_clipboard_set(char *str) {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+#else
     GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
     gtk_clipboard_set_text(cb, str, strlen(str));
+#endif
 }
 
 char* ui_clipboard_get() {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+    return NULL;
+#else
     GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
     char *str = gtk_clipboard_wait_for_text(cb);
     if(str) {
@@ -230,6 +282,7 @@
     } else {
         return NULL;
     }
+#endif
 }
 
 int ui_get_scalefactor() {
@@ -241,15 +294,25 @@
 }
 
 void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) {
-    ui_destroy_boundvar(data->obj->ctx, data->var);
+    if(data->var) {
+        ui_destroy_boundvar(data->obj->ctx, data->var);
+    }
     free(data);
 }
 
+void ui_destroy_widget_var(GtkWidget *object, UiVar *var) {
+    ui_destroy_boundvar(NULL, var);
+}
+
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+    uic_unbind_var(var);
+    
     if(var->type == UI_VAR_SPECIAL) {
-        free(var);
+        ui_free(var->from_ctx, var);
     } else {
-        uic_remove_bound_var(ctx, var);
+        ui_free(var->from_ctx, var);
+        // TODO: free or unbound
+        //uic_remove_bound_var(ctx, var);
     }
 }
 
@@ -262,3 +325,160 @@
 }
 
 
+#if GTK_MAJOR_VERSION >= 3
+
+static GtkCssProvider* ui_gtk_css_provider;
+
+#if GTK_MAJOR_VERSION == 4
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: alpha(currentColor, 0.1);"
+"  border-radius: 6px;"
+"  padding: 0px;"
+"}\n"
+".pathbar-extra-button {\n"
+"  border-top-right-radius: 6px;"
+"  border-bottom-right-radius: 6px;"
+"  border-top-left-radius: 0px;"
+"  border-bottom-left-radius: 0px;"
+"}\n"
+"#pathbar button {\n"
+"  margin: 3px;"
+"  border-radius: 4px;"
+"  padding-top: 0px;"
+"  padding-bottom: 0px;"
+"  padding-left: 8px;"
+"  padding-right: 8px;"
+"}\n"
+"#path-textfield-box entry {\n"
+"  background-color: #00000000;"
+"  border-top-left-radius: 6px;"
+"  border-bottom-left-radius: 6px;"
+"  border-top-right-radius: 0px;"
+"  border-bottom-right-radius: 0px;"
+"}\n"
+".pathbar-button-inactive {\n"
+"  color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+"  background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+"  font-weight: bold;\n"
+"}\n"
+".ui-listbox-header {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 12px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+".ui-listbox-header-first {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 4px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+;
+
+#elif GTK_MAJOR_VERSION == 3
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: @theme_base_color;\n"
+"  border-radius: 5px;\n"
+"  padding: 0px;\n"
+"}\n"
+".pathbar-button-inactive {\n"
+"  color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+"  background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+"  font-weight: bold;\n"
+"}\n"
+"placessidebar row {\n"
+"  padding-left: 10px;\n"
+"}\n"
+".ui-listbox-header {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 12px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+".ui-listbox-header-first {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 4px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+;
+#endif
+
+void ui_css_init(void) {
+    ui_gtk_css_provider = gtk_css_provider_new();
+    
+#ifdef UI_GTK3
+    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL);
+    
+    GdkScreen *screen = gdk_screen_get_default();
+    gtk_style_context_add_provider_for_screen(
+            screen,
+            GTK_STYLE_PROVIDER(ui_gtk_css_provider),
+            GTK_STYLE_PROVIDER_PRIORITY_USER);
+#endif /* UI_GTK3 */
+    
+#ifdef UI_GTK4
+    
+    
+#if GTK_MINOR_VERSION < 12
+    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1);
+#else
+    gtk_css_provider_load_from_string(ui_gtk_css_provider, ui_gtk_css);
+#endif /* GTK_MINOR_VERSION < 12 */
+    
+    GdkDisplay *display = gdk_display_get_default();
+    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    
+#endif /* UI_GTK4 */
+}
+
+
+
+#endif
+
+void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style_classes) {
+    if(name) {
+        gtk_widget_set_name(widget, name);
+    }
+    if(style_classes) {
+        cxstring *cls = NULL;
+        size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls);
+        for(int i=0;i<numClasses;i++) {
+            cxmutstr m = cx_strdup(cls[i]);
+#if GTK_MAJOR_VERSION >= 4
+            gtk_widget_add_css_class(widget, m.ptr);
+#elif GTK_MAJOR_VERSION >= 3
+            GtkStyleContext *ctx = gtk_widget_get_style_context(widget);
+            gtk_style_context_add_class(ctx, m.ptr);
+#endif
+            free(m.ptr);
+        }
+        free(cls);
+        
+    }
+}
+
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) {
+    if(!groups) {
+        return;
+    }
+    size_t ngroups = uic_group_array_size(groups);
+    ui_set_widget_ngroups(ctx, widget, groups, ngroups);
+}
+
+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);
+        ui_set_enabled(widget, FALSE);
+    }
+}
--- a/ui/gtk/toolkit.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/toolkit.h	Sat Jan 04 16:38:48 2025 +0100
@@ -39,49 +39,150 @@
     
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
+  
+#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 2074
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS
+#else
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE
+#endif
+    
+#ifdef UI_LIBADWAITA
+#define UI_APPLICATION AdwApplication*
+#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS)
+#elif GTK_MAJOR_VERSION >= 3  
+#define UI_APPLICATION GtkApplication*
+#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window))
+#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window))
+#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child)
+#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child)
+#define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child)
+#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new()
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_frame_set_child(GTK_FRAME(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass)
+#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon)
+#define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row)
+#define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child)
+#else
+#define WINDOW_SHOW(window) gtk_widget_show_all(window)
+#define WINDOW_DESTROY(window) gtk_widget_destroy(window)
+#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child)
+#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0)  
+#define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child)
+#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_container_add(GTK_CONTAINER(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass)
+#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON)
+#define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row)
+#define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child)
+#endif
+    
+#ifdef UI_GTK2
+#undef SCROLLEDWINDOW_SET_CHILD
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st)
+#define UI_GTK_V4(st) st
+#elif GTK_MAJOR_VERSION >= 3
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) st 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st) st
+#define UI_GTK_V4(st)
+#else
+#define UI_GTK_SINCE_V4(st) 
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st) st
+#define UI_GTK_V3(st) 
+#define UI_GTK_V4(st)
+#endif
+
+
 typedef struct UiEventData {
     UiObject    *obj;
     ui_callback callback;
     void        *userdata;
     int         value;
+    void        *customdata;
 } UiEventData;
 
+typedef struct UiEventDataExt {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    ui_callback callback2;
+    void        *userdata2;
+    int         value0;
+    int         value1;
+    int         value2;
+    int         value3;
+    void        *customdata0;
+    void        *customdata1;
+    void        *customdata2;
+    void        *customdata3;
+} UiEventDataExt;
+
 typedef struct UiVarEventData {
-    UiObject   *obj;
-    UiVar     *var;
-    UiObserver **observers;
+    UiObject    *obj;
+    UiVar       *var;
+    UiObserver  **observers;
+    ui_callback callback;
+    void        *userdata;
 } UiVarEventData;
 
-
-typedef struct UiJob {
-    UiObject      *obj;
-    ui_threadfunc job_func;
-    void          *job_data;
-    ui_callback   finish_callback;
-    void          *finish_data;
-} UiJob;
-
+#ifndef UI_GTK4
 struct UiSelection {
     GtkSelectionData *data;
 };
+#endif
 
-typedef enum UiOrientation UiOrientation;
-enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
-
-#ifndef UI_GTK2
+#ifdef UI_APPLICATION
 void ui_app_quit();
 GtkApplication* ui_get_application();
 #endif
 
 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_destroy_userdata(GtkWidget *object, void *userdata);
 void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_widget_var(GtkWidget *object, UiVar *var);
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
 
 void ui_set_active_window(UiObject *obj);
 UiObject *ui_get_active_window();
 
+#if GTK_MAJOR_VERSION >= 3
+void ui_css_init(void);
+#endif
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/tree.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,562 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-
-#include "../common/context.h"
-#include "../common/object.h"
-#include "container.h"
-
-#include "tree.h"
-
-
-void* ui_strmodel_getvalue(void *elm, int column) {
-    return column == 0 ? elm : NULL;
-}
-
-
-UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    // create treeview
-    GtkWidget *view = gtk_tree_view_new();
-    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);
-    
-    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
-#ifdef UI_GTK3
-#if GTK_MINOR_VERSION >= 8
-    gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
-#else
-    // TODO: implement for older gtk3
-#endif
-#else
-    // TODO: implement for gtk2
-#endif
-    
-    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    model->getvalue = getvalue;
-    UiList *list = var->value;
-    UiListModel *listmodel = ui_list_model_new(obj, var, model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
-    
-    UiListView *listview = malloc(sizeof(UiListView));
-    listview->obj = obj;
-    listview->widget = view;
-    listview->var = var;
-    listview->model = model;
-    g_signal_connect(
-                view,
-                "destroy",
-                G_CALLBACK(ui_listview_destroy),
-                listview);
-    
-    // bind var
-    list->update = ui_listview_update;
-    list->obj = listview;
-    
-    // add callback
-    if(f) {
-        UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
-        event->obj = obj;
-        event->userdata = udata;
-        event->activate = f;
-        event->selection = NULL;
-
-        g_signal_connect(
-                view,
-                "row-activated",
-                G_CALLBACK(ui_listview_activate_event),
-                event);
-    }
-    
-    // add widget to the current container
-    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
-    gtk_scrolled_window_set_policy(
-            GTK_SCROLLED_WINDOW(scroll_area),
-            GTK_POLICY_AUTOMATIC,
-            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), view);
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, scroll_area, TRUE);
-    
-    // ct->current should point to view, not scroll_area, to make it possible
-    // to add a context menu
-    ct->current = view;
-    
-    return scroll_area;
-}
-
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_listview_var(obj, var, getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        return ui_listview_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
-    printf("drag begin\n");
-    
-}
-
-static void drag_end(
-        GtkWidget *widget,
-        GdkDragContext *context,
-        guint time,
-        gpointer udata)
-{
-    printf("drag end\n");
-    
-}
-
-static GtkTargetEntry targetentries[] =
-    {
-      { "STRING",        0, 0 },
-      { "text/plain",    0, 1 },
-      { "text/uri-list", 0, 2 },
-    };
-
-UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
-    // create treeview
-    GtkWidget *view = gtk_tree_view_new();
-    
-    int addi = 0;
-    for(int i=0;i<model->columns;i++) {
-        GtkTreeViewColumn *column = NULL;
-        if(model->types[i] == UI_ICON_TEXT) {
-            column = gtk_tree_view_column_new();
-            gtk_tree_view_column_set_title(column, model->titles[i]);
-            
-            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
-            GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
-            
-            gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
-            gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
-            
-            
-            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
-            gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
-            
-            addi++;
-        } else {
-            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
-            column = gtk_tree_view_column_new_with_attributes(
-                model->titles[i],
-                renderer,
-                "text",
-                i + addi,
-                NULL);
-        }
-        gtk_tree_view_column_set_resizable(column, TRUE);
-        gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
-    }
-    
-    //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
-#ifdef UI_GTK3
-    //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
-#else
-    
-#endif
-    
-    UiList *list = var->value;
-    UiListModel *listmodel = ui_list_model_new(obj, var, model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
-    
-    //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
-    //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
-       
-    // add TreeView as observer to the UiList to update the TreeView if the
-    // data changes
-    UiListView *tableview = malloc(sizeof(UiListView));
-    tableview->obj = obj;
-    tableview->widget = view;
-    tableview->var = var;
-    tableview->model = model;
-    g_signal_connect(
-                view,
-                "destroy",
-                G_CALLBACK(ui_listview_destroy),
-                tableview);
-    
-    // bind var
-    list->update = ui_listview_update;
-    list->obj = tableview;
-    
-    // add callback
-    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
-    event->obj = obj;
-    event->activate = cb.activate;
-    event->selection = cb.selection;
-    event->userdata = cb.userdata;
-    if(cb.activate) {
-        g_signal_connect(
-                view,
-                "row-activated",
-                G_CALLBACK(ui_listview_activate_event),
-                event);
-    }
-    if(cb.selection) {
-        GtkTreeSelection *selection = gtk_tree_view_get_selection(
-                GTK_TREE_VIEW(view));
-        g_signal_connect(
-                selection,
-                "changed",
-                G_CALLBACK(ui_listview_selection_event),
-                event);
-    }
-    // TODO: destroy callback
-
-    
-    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
-    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
-    
-    // add widget to the current container
-    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
-    gtk_scrolled_window_set_policy(
-            GTK_SCROLLED_WINDOW(scroll_area),
-            GTK_POLICY_AUTOMATIC,
-            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), view);
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, scroll_area, TRUE);
-    
-    // ct->current should point to view, not scroll_area, to make it possible
-    // to add a context menu
-    ct->current = view;
-    
-    return scroll_area;
-}
-
-UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_table_var(obj, var, model, cb);
-}
-
-UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        return ui_table_var(obj, var, model, cb);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
-    GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
-    if(c) {
-        return c->data;
-    }
-    return NULL;
-}
-
-static char** targets2array(char *target0, va_list ap, int *nelm) {
-    int al = 16;
-    char **targets = calloc(16, sizeof(char*));
-    targets[0] = target0;
-    
-    int i = 1;
-    char *target;
-    while((target = va_arg(ap, char*)) != NULL) {
-        if(i >= al) {
-            al *= 2;
-            targets = realloc(targets, al*sizeof(char*));
-        }
-        targets[i] = target;
-        i++;
-    }
-    
-    *nelm = i;
-    return targets;
-}
-
-static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
-    GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
-    for(int i=0;i<nelm;i++) {
-        targets[i].target = str[i];
-    }
-    return targets;
-}
-
-void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
-    va_list ap;
-    va_start(ap, target0);
-    int nelm;
-    char **targets = targets2array(target0, ap, &nelm);
-    va_end(ap);
-    ui_table_dragsource_a(tablewidget, actions, targets, nelm);
-    free(targets);
-}
-
-void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
-    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
-    gtk_tree_view_enable_model_drag_source(
-            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
-            GDK_BUTTON1_MASK,
-            t,
-            nelm,
-            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
-    free(t);
-}
-
-void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
-    va_list ap;
-    va_start(ap, target0);
-    int nelm;
-    char **targets = targets2array(target0, ap, &nelm);
-    va_end(ap);
-    ui_table_dragdest_a(tablewidget, actions, targets, nelm);
-    free(targets);
-}
-
-void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
-    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
-    gtk_tree_view_enable_model_drag_dest(
-            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
-            t,
-            nelm,
-            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
-    free(t);
-}
-
-void ui_listview_update(UiList *list, int i) {
-    UiListView *view = list->obj;
-    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model));
-    g_object_unref(G_OBJECT(model));
-    // TODO: free old model
-}
-
-void ui_listview_destroy(GtkWidget *w, UiListView *v) {
-    gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
-    ui_destroy_boundvar(v->obj->ctx, v->var);
-    // TODO: destroy model?
-    free(v);
-}
-
-void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
-    gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL);
-    ui_destroy_boundvar(v->obj->ctx, v->var);
-    // TODO: destroy model?
-    free(v);
-}
-
-
-void ui_listview_activate_event(
-        GtkTreeView *treeview,
-        GtkTreePath *path,
-        GtkTreeViewColumn *column,
-        UiTreeEventData *event)
-{
-    UiListSelection *selection = ui_listview_selection(
-            gtk_tree_view_get_selection(treeview),
-            event);
-    
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    event->activate(&e, event->userdata);
-    
-    if(selection->count > 0) {
-        free(selection->rows);
-    }
-    free(selection);
-}
-
-void ui_listview_selection_event(
-        GtkTreeSelection *treeselection,
-        UiTreeEventData *event)
-{
-    UiListSelection *selection = ui_listview_selection(treeselection, event);
-    
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    event->selection(&e, event->userdata);
-    
-    if(selection->count > 0) {
-        free(selection->rows);
-    }
-    free(selection);
-}
-
-UiListSelection* ui_listview_selection(
-        GtkTreeSelection *selection,
-        UiTreeEventData *event)
-{
-    GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
-    
-    UiListSelection *ls = malloc(sizeof(UiListSelection));
-    ls->count = g_list_length(rows);
-    ls->rows = calloc(ls->count, sizeof(int));
-    GList *r = rows;
-    int i = 0;
-    while(r) {
-        GtkTreePath *path = r->data;
-        ls->rows[i] = ui_tree_path_list_index(path);
-        r = r->next;
-        i++;
-    }
-    return ls;
-}
-
-int ui_tree_path_list_index(GtkTreePath *path) {
-    int depth = gtk_tree_path_get_depth(path);
-    if(depth == 0) {
-        fprintf(stderr, "UiError: treeview selection: depth == 0\n");
-        return -1;
-    }
-    int *indices = gtk_tree_path_get_indices(path);
-    return indices[depth - 1];
-}
-
-
-/* --------------------------- ComboBox ---------------------------  */
-
-UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_combobox_var(obj, var, getvalue, f, udata);
-}
-
-UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        return ui_combobox_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    model->getvalue = getvalue;
-    UiListModel *listmodel = ui_list_model_new(obj, var, model);
-    
-    GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata);
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(ct, combobox, FALSE);
-    return combobox;
-}
-
-GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) {
-    GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
-    
-    UiListView *uicbox = malloc(sizeof(UiListView));
-    uicbox->obj = obj;
-    uicbox->widget = combobox;
-    uicbox->var = model->var;
-    uicbox->model = model->model;
-    
-    g_signal_connect(
-                combobox,
-                "destroy",
-                G_CALLBACK(ui_combobox_destroy),
-                uicbox);
-    
-    // bind var
-    UiList *list = model->var->value;
-    list->update = ui_combobox_modelupdate;
-    list->obj = uicbox;
-    
-    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
-    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
-    gtk_cell_layout_set_attributes(
-            GTK_CELL_LAYOUT(combobox),
-            renderer,
-            "text",
-            0,
-            NULL);
-    gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
-    
-    // add callback
-    if(f) {
-        UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = udata;
-        event->callback = f;
-        event->value = 0;
-
-        g_signal_connect(
-                combobox,
-                "changed",
-                G_CALLBACK(ui_combobox_change_event),
-                event);
-    }
-    
-    return combobox;
-}
-
-void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
-    UiEvent event;
-    event.obj = e->obj;
-    event.window = event.obj->window;
-    event.document = event.obj->ctx->document;
-    event.eventdata = NULL;
-    event.intval = gtk_combo_box_get_active(widget);
-    e->callback(&event, e->userdata);
-}
-
-void ui_combobox_modelupdate(UiList *list, int i) {
-    UiListView *view = list->obj;
-    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
-    gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model));
-}
-
--- a/ui/gtk/tree.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * 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 TREE_H
-#define	TREE_H
-
-#include "../ui/tree.h"
-#include "toolkit.h"
-#include "model.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-typedef struct UiListView {
-    UiObject    *obj;
-    GtkWidget   *widget;
-    UiVar       *var;
-    UiModel     *model;
-} UiListView;
-
-typedef struct UiTreeEventData {
-    UiObject    *obj;
-    ui_callback activate;
-    ui_callback selection;
-    void        *userdata;
-} UiTreeEventData;
-    
-void* ui_strmodel_getvalue(void *elm, int column);
-
-UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
-
-GtkWidget* ui_get_tree_widget(UIWIDGET widget);
-
-void ui_listview_update(UiList *list, int i);
-void ui_combobox_destroy(GtkWidget *w, UiListView *v);
-void ui_listview_destroy(GtkWidget *w, UiListView *v);
-
-void ui_listview_activate_event(
-        GtkTreeView *tree_view,
-        GtkTreePath *path,
-        GtkTreeViewColumn *column,
-        UiTreeEventData *event);
-void ui_listview_selection_event(
-        GtkTreeSelection *treeselection,
-        UiTreeEventData *event);
-UiListSelection* ui_listview_selection(
-        GtkTreeSelection *selection,
-        UiTreeEventData *event);
-int ui_tree_path_list_index(GtkTreePath *path);
-
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata);
-void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
-void ui_combobox_modelupdate(UiList *list, int i);
-        
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* TREE_H */
-
--- a/ui/gtk/window.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/gtk/window.c	Sat Jan 04 16:38:48 2025 +0100
@@ -33,29 +33,25 @@
 #include "../ui/window.h"
 #include "../ui/properties.h"
 #include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+
+#include <cx/mempool.h>
 
 #include "menu.h"
 #include "toolbar.h"
 #include "container.h"
+#include "headerbar.h"
+#include "button.h"
 
 static int nwindows = 0;
 
 static int window_default_width = 650;
 static int window_default_height = 550;
 
-void ui_exit_event(GtkWidget *widget, gpointer data) {
+static gboolean ui_window_destroy(void *data)  {
     UiObject *obj = data;
-    UiEvent ev;
-    ev.window = obj->window;
-    ev.document = obj->ctx->document;
-    ev.obj = obj;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    if(obj->ctx->close_callback) {
-        obj->ctx->close_callback(&ev, obj->ctx->close_data);
-    }
-    // TODO: free UiObject
+    uic_object_destroy(obj);
     
     nwindows--;
 #ifdef UI_GTK2
@@ -63,13 +59,56 @@
         gtk_main_quit();
     }
 #endif
+    
+    return FALSE;
+}
+
+void ui_window_widget_destroy(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 4
+    gtk_window_destroy(GTK_WINDOW(obj->widget));
+#else
+    gtk_widget_destroy(obj->widget);
+#endif
+}
+
+void ui_exit_event(GtkWidget *widget, gpointer data) {
+    // delay exit handler  
+    g_idle_add(ui_window_destroy, data);
 }
 
-static UiObject* create_window(char *title, void *window_data, UiBool simple) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject)); 
-    
-#ifndef UI_GTK2
+static gboolean ui_window_close_request(UiObject *obj) {
+    uic_context_prepare_close(obj->ctx);
+    obj->ref--;
+    if(obj->ref > 0) {
+#if GTK_CHECK_VERSION(2, 18, 0)
+        gtk_widget_set_visible(obj->widget, FALSE);
+#else
+        gtk_widget_hide(obj->widget);
+#endif
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+static gboolean close_request(GtkWindow* self, UiObject *obj) {
+    return ui_window_close_request(obj);
+}
+#else
+static gboolean close_request(GtkWidget* self, GdkEvent* event, UiObject *obj) {
+    return ui_window_close_request(obj);
+}
+#endif
+
+static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+    obj->ref = 0;
+   
+#ifdef UI_LIBADWAITA
+    obj->widget = adw_application_window_new(ui_get_application());
+#elif !defined(UI_GTK2)
     obj->widget = gtk_application_window_new(ui_get_application());
 #else
     obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
@@ -79,12 +118,16 @@
     obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     
+#if GTK_CHECK_VERSION(4, 0, 0)
+    obj->ctx->action_map = G_ACTION_MAP(obj->widget);
+#endif
+    
     if(title != NULL) {
         gtk_window_set_title(GTK_WINDOW(obj->widget), title);
     }
     
-    char *width = ui_get_property("ui.window.width");
-    char *height = ui_get_property("ui.window.height");
+    const char *width = ui_get_property("ui.window.width");
+    const char *height = ui_get_property("ui.window.height");
     if(width && height) {
         gtk_window_set_default_size(
                 GTK_WINDOW(obj->widget),
@@ -93,35 +136,117 @@
     } else {
         gtk_window_set_default_size(
                 GTK_WINDOW(obj->widget),
-                window_default_width,
+                window_default_width + sidebar*250,
                 window_default_height);
     }
     
+    obj->destroy = ui_window_widget_destroy;
     g_signal_connect(
             obj->widget,
             "destroy",
             G_CALLBACK(ui_exit_event),
             obj);
+#if GTK_MAJOR_VERSION >= 4
+    g_signal_connect(
+            obj->widget,
+            "close-request",
+            G_CALLBACK(close_request),
+            obj);
+#else
+    g_signal_connect(
+            obj->widget,
+            "delete-event",
+            G_CALLBACK(close_request),
+            obj);
+#endif
     
     GtkWidget *vbox = ui_gtk_vbox_new(0);
-    gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
+#ifdef UI_LIBADWAITA
+    GtkWidget *toolbar_view = adw_toolbar_view_new();
+    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
+    
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    
+    if(sidebar) {
+        GtkWidget *splitview = adw_overlay_split_view_new();
+        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview);
+        
+        GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new();
+        adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view);
+        GtkWidget *sidebar_headerbar = adw_header_bar_new();
+        adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar);
+        
+        adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view);
+        
+        g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view);
+    } else {
+        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+    }
+    
+
+    GtkWidget *headerbar = adw_header_bar_new();
+    adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
+    g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar);
     
     if(!simple) {
+        ui_fill_headerbar(obj, headerbar);
+    }
+#elif GTK_MAJOR_VERSION >= 4
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    WINDOW_SET_CONTENT(obj->widget, vbox);
+    if(sidebar) {
+        GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0);
+        gtk_paned_set_start_child(GTK_PANED(paned), sidebar_vbox);
+        gtk_paned_set_end_child(GTK_PANED(paned), content_box);
+        BOX_ADD_EXPAND(GTK_BOX(vbox), paned);
+        g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox);
+    } else {
+        BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    }
+#else
+    if(!simple) {
         // menu
-        GtkWidget *mb = ui_create_menubar(obj);
-        if(mb) {
-            gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+        if(uic_get_menu_list()) {
+            GtkWidget *mb = ui_create_menubar(obj);
+            if(mb) {
+                gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+            }
         }
 
         // toolbar
-        GtkWidget *tb = ui_create_toolbar(obj);
-        if(tb) {
-            gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+        if(uic_toolbar_isenabled()) {
+            GtkWidget *tb = ui_create_toolbar(obj);
+            if(tb) {
+                gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+            }
         }
+        
+        //GtkWidget *hb = ui_create_headerbar(obj);
+        //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb);
     }
     
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    WINDOW_SET_CONTENT(obj->widget, vbox);
+    if(sidebar) {
+        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);
+        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);
+    }
+    
+#endif
+    
     // window content
     // the content has a (TODO: not yet) configurable frame
+    // TODO: really? why
+    /*
     GtkWidget *frame = gtk_frame_new(NULL);
     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
     gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
@@ -130,58 +255,637 @@
     GtkWidget *content_box = ui_gtk_vbox_new(0);
     gtk_container_add(GTK_CONTAINER(frame), content_box);
     obj->container = ui_box_container(obj, content_box);
+    */
+    obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
     
     nwindows++;
     return obj;
 }
 
 
-UiObject* ui_window(char *title, void *window_data) {
-    return create_window(title, window_data, FALSE);
+UiObject* ui_window(const char *title, void *window_data) {
+    return create_window(title, window_data, FALSE, FALSE);
+}
+
+UiObject *ui_sidebar_window(const char *title, void *window_data) {
+    return create_window(title, window_data, TRUE, FALSE);
+}
+
+UiObject* ui_simple_window(const char *title, void *window_data) {
+    return create_window(title, window_data, FALSE, TRUE);
+}
+
+void ui_window_size(UiObject *obj, int width, int height) {
+    gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                width,
+                height);
+}
+
+#ifdef UI_LIBADWAITA
+
+static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) {
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.eventdata = NULL;
+    evt.intval = 0;
+    
+    if(!strcmp(response, "btn1")) {
+        evt.intval = 1;
+    } else if(!strcmp(response, "btn2")) {
+        evt.intval = 2;
+    }
+    
+    if(data->customdata) {
+        GtkWidget *entry = data->customdata;
+        evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+    }
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+}
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+    AdwDialog *dialog = adw_alert_dialog_new(args.title, args.content);
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->callback = args.result;
+    event->userdata = args.resultdata;
+    event->customdata = NULL;
+    event->value = 0;
+    event->obj = parent;
+    
+    if(args.button1_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn1", args.button1_label);
+    }
+    if(args.button2_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn2", args.button2_label);
+    }
+    if(args.closebutton_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "close", args.closebutton_label);
+        adw_alert_dialog_set_close_response(ADW_ALERT_DIALOG(dialog), "close");
+    }
+    
+    GtkWidget *entry = NULL;
+    if(args.input || args.password) {
+        entry = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(entry, args.input_value);
+        }
+        adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry);
+        event->customdata = entry;
+    }
+    
+    g_signal_connect(
+                dialog,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    
+    g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), event);
+    adw_dialog_present(dialog, parent->widget);
+    
+    if(entry) {
+        gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
+    }
+}
+#else
+
+static void ui_dialog_response (GtkDialog* self, gint response_id, gpointer user_data) {
+    UiEventData *data = user_data;
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.eventdata = NULL;
+    evt.intval = 0;
+    
+    if(data->customdata) {
+        GtkWidget *entry = data->customdata;
+        evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+        
+    }
+    
+    if(response_id == 1 || response_id == 2) {
+        evt.intval = response_id;
+    }
+    
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+    
+    WINDOW_DESTROY(GTK_WIDGET(self));
 }
 
-UiObject* ui_simplewindow(char *title, void *window_data) {
-    return create_window(title, window_data, TRUE);
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+    GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new());
+    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    
+    GtkWidget *dialog_w = GTK_WIDGET(dialog);
+    if(args.title) {
+        gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+    }
+    if(args.button1_label) {
+        gtk_dialog_add_button(dialog, args.button1_label, 1);
+    }
+    if(args.button2_label) {
+        gtk_dialog_add_button(dialog, args.button2_label, 2);
+    }
+    if(args.closebutton_label) {
+        gtk_dialog_add_button(dialog, args.closebutton_label, 0);
+    }
+    
+    GtkWidget *content_area = gtk_dialog_get_content_area(dialog);
+    if(args.content) {
+        GtkWidget *label = gtk_label_new(args.content);
+        BOX_ADD(content_area, label);
+    }
+    
+    GtkWidget *textfield = NULL;
+    if(args.input || args.password) {
+        textfield = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(textfield, args.input_value);
+        }
+        BOX_ADD(content_area, textfield);
+    }
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = parent;
+    event->callback = args.result;
+    event->userdata = args.resultdata;
+    event->value = 0;
+    event->customdata = textfield;
+    
+    g_signal_connect(dialog_w,
+                           "response",
+                           G_CALLBACK(ui_dialog_response),
+                           event);
+    
+    WINDOW_SHOW(GTK_WIDGET(dialog_w));
+}
+#endif
+
+
+#if GTK_MAJOR_VERSION >= 3
+UiFileList listmodel2filelist(GListModel *selection) {
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    flist.nfiles = g_list_model_get_n_items(selection);
+    flist.files = calloc(flist.nfiles, sizeof(char*));
+    for(int i=0;i<flist.nfiles;i++) {
+        GFile *file = g_list_model_get_item(selection, i);
+        char *path = g_file_get_path(file);
+        flist.files[i] = path ? strdup(path) : NULL;
+        g_object_unref(file);
+    }
+    return flist;
+}
+#endif
+
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+#define UI_GTK_FILEDIALOG_OPEN 16
+#define UI_GTK_FILEDIALOG_SAVE 32
+
+static void filechooser_opened(GObject *source, GAsyncResult *result, void *data) {
+    UiEventData *event = data;
+    
+    GFile *file = NULL;
+    GListModel *selection = NULL;
+    GError *error = NULL;
+    
+    int mode = event->value;
+    int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        if(multi) {
+            selection = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error);
+        } else {
+            file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), result, &error);
+        }
+    } else if((mode & UI_GTK_FILEDIALOG_OPEN) == UI_GTK_FILEDIALOG_OPEN) {
+        if(multi) {
+            selection = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error);
+        } else {
+            file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), result, &error);
+        }
+    } else {
+        file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error);
+    }
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    if(selection) {
+        flist = listmodel2filelist(selection);
+        g_object_unref(selection);
+    } else if(file) {
+        char *path = g_file_get_path(file);
+        if(path) {
+            flist.nfiles = 1;
+            flist.files = calloc(flist.nfiles, sizeof(char*));
+            flist.files[0] = strdup(path);
+        }
+        g_object_unref(file); 
+    }
+    
+    if(event->callback) {
+        event->callback(&evt, event->userdata);
+    }
+    
+    for(int i=0;i<flist.nfiles;i++) {
+        free(flist.files[i]);
+    }
 }
 
-static char* ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action) {
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        mode |= UI_GTK_FILEDIALOG_OPEN;
+    } else {
+        mode |= UI_GTK_FILEDIALOG_SAVE;
+    }
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->callback = file_selected_callback;
+    event->userdata = cbdata;
+    event->customdata = NULL;
+    event->value = mode;
+    event->obj = obj;
+    
+    GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(obj->widget));
+    GtkFileDialog *dialog = gtk_file_dialog_new();
+    if(name) {
+        gtk_file_dialog_set_initial_name(dialog, name);
+    }
+    
+    int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        if(multi) {
+            gtk_file_dialog_select_multiple_folders(dialog, parent, NULL, filechooser_opened, event);
+        } else {
+            gtk_file_dialog_select_folder(dialog, parent, NULL, filechooser_opened, event);
+        }
+    } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        if(multi) {
+            gtk_file_dialog_open_multiple(dialog, parent, NULL, filechooser_opened, event);
+        } else {
+            gtk_file_dialog_open(dialog, parent, NULL, filechooser_opened, event);
+        }
+    } else {
+        gtk_file_dialog_save(dialog, parent, NULL, filechooser_opened, event);
+    }
+    
+    g_object_unref(dialog);
+}
+#else
+
+
+
+static void filechooser_response(GtkDialog* self, gint response_id, UiEventData *data) {
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    if(response_id == GTK_RESPONSE_ACCEPT) {
+#if GTK_CHECK_VERSION(4, 0, 0)
+        GListModel *selection = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(self));
+        flist = flist = listmodel2filelist(selection);
+        g_object_unref(selection);
+#else
+        GSList *selection = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(self));
+        flist.nfiles = g_slist_length(selection);
+        flist.files = calloc(flist.nfiles, sizeof(char*));
+        int i = 0;
+        while(selection) {
+            char *file = selection->data;
+            flist.files[i] = strdup(file);
+            g_free(file);
+            selection = selection->next;
+            i++;
+        }
+        g_slist_free(selection);
+#endif
+    }
+    
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+    
+    for(int i=0;i<flist.nfiles;i++) {
+        free(flist.files[i]);
+    }
+    
+    WINDOW_DESTROY(GTK_WIDGET(self));
+}
+
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
     char *button;
     char *title;
     
-    if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
-        button = GTK_STOCK_OPEN;
-        title = "Datei öffnen...";
+    GtkWidget *dialog;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        dialog = gtk_file_chooser_dialog_new (
+                "Open Folder",
+                GTK_WINDOW(obj->widget),
+                GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Select Folder",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
+    } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        dialog = gtk_file_chooser_dialog_new (
+                "Select Folder",
+                GTK_WINDOW(obj->widget),
+                action,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Open File",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
     } else {
-        button = GTK_STOCK_SAVE;
-        title = "Datei speichern...";
+        dialog = gtk_file_chooser_dialog_new (
+                "Save File",
+                GTK_WINDOW(obj->widget),
+                action,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Save File",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
+    }
+    
+    if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+        gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
     }
     
-    GtkWidget *dialog = gtk_file_chooser_dialog_new(
-                                title,
-                                GTK_WINDOW(obj->widget),
-                                action,
-                                GTK_STOCK_CANCEL,
-                                GTK_RESPONSE_CANCEL,
-                                button,
-                                GTK_RESPONSE_ACCEPT,
-                                NULL);
-    if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
-        char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-        gtk_widget_destroy(dialog);
-        char *copy = strdup(file);
-        g_free(file);
-        return copy;
-    } else {
-        gtk_widget_destroy(dialog);
-        return NULL;
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = cbdata;
+    event->callback = file_selected_callback;
+    event->value = 0;
+    event->customdata = NULL;
+    
+    g_signal_connect(
+                dialog,
+                "response",
+                G_CALLBACK(filechooser_response),
+                event);
+    g_signal_connect(
+                dialog,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    
+    
+    UiEvent evt;
+    evt.obj = obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    gtk_widget_show(dialog);
+}
+#endif
+
+void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+    ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN, mode, NULL, file_selected_callback, cbdata);
+}
+
+void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata);
+}
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+#define DIALOG_NEW() gtk_window_new()
+#else
+#define DIALOG_NEW() gtk_dialog_new()
+
+static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) {
+    UiEventData *event = user_data;
+    // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT?
+    if(event->callback) {
+        UiEvent e;
+        e.obj = event->obj;
+        e.window = event->obj->window;
+        e.document = event->obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = event->value;
+        event->callback(&e, event->userdata);
     }
 }
 
-char* ui_openfiledialog(UiObject *obj) {
-    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN);
-}
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button)
+#else
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button)
+#endif
+
+
+
+UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
+    GtkWidget *dialog = DIALOG_NEW();
+    if(args.width > 0 || args.height > 0) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(dialog),
+                args.width,
+                args.height);
+    }
+    
+    
+    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+    if(args.modal != UI_OFF) {
+        gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    }
+    
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
+    obj->ctx = uic_context(obj, mp);
+    obj->widget = dialog;
+    obj->ref = 0;
+    obj->destroy = ui_window_widget_destroy;
+    nwindows++;
+    
+    if(args.title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+    }
+    
+#if ! GTK_CHECK_VERSION(4, 10, 0)
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = args.onclickdata;
+    event->callback = args.onclick;
+    event->value = 0;
+    event->customdata = NULL;
 
-char* ui_savefiledialog(UiObject *obj) {
-    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE);
+    g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event);
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+#endif
+    
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+#if GTK_MAJOR_VERSION >= 4
+    g_signal_connect(
+            obj->widget,
+            "close-request",
+            G_CALLBACK(close_request),
+            obj);
+#else
+    g_signal_connect(
+            obj->widget,
+            "delete-event",
+            G_CALLBACK(close_request),
+            obj);
+#endif
+    
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    gtk_container_remove(GTK_CONTAINER(dialog), c);
+#endif
+    
+    GtkWidget *content_vbox = ui_gtk_vbox_new(0);
+    obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX);
+    if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) {
+#if GTK_CHECK_VERSION(3, 10, 0)
+        if(args.titlebar_buttons != UI_OFF) {
+            GtkWidget *headerbar = gtk_header_bar_new();
+            gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar);
+            if(args.show_closebutton == UI_OFF) {
+                HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE);
+            }
+            
+            if(args.lbutton1) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 1) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            if(args.lbutton2) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 2) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            
+            if(args.rbutton4) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 4) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            if(args.rbutton3) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 3) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            WINDOW_SET_CONTENT(obj->widget, content_vbox);
+            return obj;
+        }
+#endif
+        GtkWidget *vbox = ui_gtk_vbox_new(0);
+        WINDOW_SET_CONTENT(obj->widget, vbox);
+        
+        GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        
+        GtkWidget *grid = ui_create_grid_widget(10, 10);
+        GtkWidget *widget = ui_box_set_margin(grid, 16);
+        gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); 
+        
+        if(args.lbutton1) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+            gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
+            if(args.default_button == 1) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        if(args.lbutton2) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+            gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
+            if(args.default_button == 2) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        GtkWidget *space = gtk_label_new(NULL);
+        gtk_widget_set_hexpand(space, TRUE);
+        gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
+        if(args.rbutton3) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+            gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
+            if(args.default_button == 3) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        if(args.rbutton4) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+            gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
+            if(args.default_button == 4) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        
+        BOX_ADD_EXPAND(vbox, content_vbox);   
+        BOX_ADD_NO_EXPAND(vbox, separator);
+        BOX_ADD_NO_EXPAND(vbox, widget);
+    } else {
+        WINDOW_SET_CONTENT(obj->widget, content_vbox);
+    }
+    
+    return obj;
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/Grid.c	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,636 @@
+/*
+ * 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.
+ */
+
+/*
+ * 
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "Grid.h"
+
+#include <X11/Xlib.h>
+
+
+
+static XtActionsRec actionslist[] = {
+  {"getfocus",grid_getfocus},
+  {"loosefocus",grid_loosefocus},
+  {"NULL",NULL}
+};
+
+//static char defaultTranslations[] = "<BtnDown>: mousedown()\n";
+static char defaultTranslations[] = "\
+<EnterWindow>:		getfocus()\n\
+<LeaveWindow>:          loosefocus()\n";
+
+static XtResource resources[] =
+{
+    {
+        gridColumnSpacing,
+        gridColumnSpacing,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridRec,
+                   mywidget.columnspacing),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridRowSpacing,
+        gridRowSpacing,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridRec,
+                   mywidget.rowspacing),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMargin,
+        gridMargin,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridRec,
+                   mywidget.margin),
+        XmRImmediate,
+        (XtPointer) 0
+    }
+};
+
+///*
+static XtResource constraints[] =
+{
+    {
+        gridColumn,
+        gridColumn,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.x),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridRow,
+        gridRow,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.y),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridColspan,
+        gridColspan,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.colspan),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridRowspan,
+        gridRowspan,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.rowspan),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginLeft,
+        gridMarginLeft,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_left),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginRight,
+        gridMarginRight,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_right),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginTop,
+        gridMarginTop,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_top),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginBottom,
+        gridMarginBottom,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_bottom),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridHExpand,
+        gridHExpand,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.hexpand),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridVExpand,
+        gridVExpand,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.vexpand),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridHFill,
+        gridHFill,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.hfill),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridVFill,
+        gridVFill,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.vfill),
+        XmRImmediate,
+        (XtPointer) 0
+    }
+    
+};
+//*/
+
+GridClassRec gridClassRec = {
+    // Core Class
+    {
+        //(WidgetClass)&constraintClassRec,   // superclass  
+        (WidgetClass)&xmManagerClassRec,
+        "Grid",                       // class_name
+        sizeof(GridRec),              // widget_size
+        grid_class_initialize,        // class_initialize
+        NULL,                         // class_part_initialize
+        FALSE,                        // class_inited
+        (XtInitProc)grid_initialize,  // initialize
+        NULL,                         // initialize_hook
+        grid_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
+        (XtWidgetProc)grid_destroy,             // destroy
+        (XtWidgetProc)grid_resize,              // resize
+        (XtExposeProc)grid_expose,              // expose
+        grid_set_values,          // set_values
+        NULL,                         // set_values_hook
+        XtInheritSetValuesAlmost,     // set_values_almost
+        NULL,                         // get_values_hook
+        (XtAcceptFocusProc)grid_acceptfocus,       // accept_focus
+        XtVersion,                    // version
+        NULL,                         // callback_offsets
+        //NULL,                         // tm_table
+                defaultTranslations,
+        XtInheritQueryGeometry,       // query_geometry
+        NULL,                         // display_accelerator
+        NULL,                         // extension
+    },
+    // Composite Class
+    {
+        GridGeometryManager, /* geometry_manager */
+        GridChangeManaged,  /* change_managed */   
+        XtInheritInsertChild,  /* insert_child */ 
+        XtInheritDeleteChild,  /* delete_child */  
+        NULL,                 /* extension */    
+    },
+    // Constraint Class
+    {
+        constraints,    /* resources */  
+        XtNumber(constraints),  /* num_resources */    
+        sizeof(GridConstraintRec),  /* constraint_size */  
+        grid_constraint_init,  /* initialize */  
+        NULL,  /* destroy */
+        ConstraintSetValues,  /* set_values */   
+        NULL,  /* extension */    
+    },
+    // XmManager Class
+    ///*
+    {
+        XtInheritTranslations,
+        NULL,
+        0,
+        NULL,
+        0,
+        XmInheritParentProcess,
+        NULL
+    },
+    //*/
+    // MyWidget Class
+    {
+        0
+    }
+};
+
+WidgetClass gridClass = (WidgetClass)&gridClassRec;
+
+
+void grid_class_initialize(Widget request, Widget new, ArgList args, Cardinal *num_args) {
+    
+}
+void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) {
+    MyWidget mn = (MyWidget)new;
+    
+    mn->mywidget.max_col = 0;
+    mn->mywidget.max_row = 0;
+    
+}
+void grid_realize(MyWidget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) {
+    XtMakeResizeRequest((Widget)w, 400, 400, NULL, NULL);
+    (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); 
+    grid_place_children(w);
+}
+
+
+void grid_destroy(MyWidget widget) {
+    
+}
+void grid_resize(MyWidget widget) {
+    grid_place_children(widget);
+}
+
+void grid_expose(MyWidget widget, XEvent *event, Region region) {
+    
+}
+
+
+Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    return False;
+}
+
+Boolean grid_acceptfocus(Widget w, Time *t) {
+    
+}
+
+void grid_getfocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+    
+}
+
+void grid_loosefocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+    
+}
+
+
+
+XtGeometryResult GridGeometryManager(Widget	widget, XtWidgetGeometry *request, XtWidgetGeometry *reply) {
+    GridRec *grid = (GridRec*)XtParent(widget);
+    GridConstraintRec *constraints = widget->core.constraints;
+    //XtVaSetValues(widget, XmNwidth, request->width, XmNheight, request->height, NULL);
+    if((request->request_mode & CWWidth) == CWWidth) {
+        widget->core.width = request->width;
+        constraints->grid.pref_width = request->width;
+    }
+    if((request->request_mode & CWHeight) == CWHeight) {
+        widget->core.height = request->height;
+        constraints->grid.pref_height = request->height;
+    }
+    grid_place_children((MyWidget)XtParent(widget));
+    return XtGeometryYes;
+}
+
+void GridChangeManaged(Widget widget) {
+    
+}
+
+Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    GridConstraintRec *constraints = neww->core.constraints;
+    MyWidget grid = (MyWidget)XtParent(neww);
+    if(constraints->grid.x > grid->mywidget.max_col) {
+        grid->mywidget.max_col = constraints->grid.x;
+    }
+    if(constraints->grid.y > grid->mywidget.max_row) {
+        grid->mywidget.max_row = constraints->grid.y;
+    }
+}
+
+
+void grid_constraint_init(
+    Widget	request,
+    Widget	neww,
+    ArgList	args,
+    Cardinal*	num_args
+)
+{
+    GridConstraintRec *constraints = neww->core.constraints;
+    
+    MyWidget grid = (MyWidget)XtParent(neww);
+    if(constraints->grid.x > grid->mywidget.max_col) {
+        grid->mywidget.max_col = constraints->grid.x;
+    }
+    if(constraints->grid.y > grid->mywidget.max_row) {
+        grid->mywidget.max_row = constraints->grid.y;
+    }
+    constraints->grid.pref_width = neww->core.width;
+    constraints->grid.pref_height = neww->core.height;
+}
+
+void grid_place_children(MyWidget w) {
+    int ncols = w->mywidget.max_col+1;
+    int nrows = w->mywidget.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;
+    
+    //printf("container width: %d\n", (int)w->core.width);
+    
+    // calculate the minimum size requirements for all columns and rows
+    // we need to run this 2 times: for widgets without colspan/rowspan first
+    // and then again for colspan/rowspan > 1
+    int span_max = 1;
+    for(int r=0;r<2;r++) {
+        for(int i=0;i<w->composite.num_children;i++) {
+            Widget child = w->composite.children[i];
+            GridConstraintRec *constraints = child->core.constraints;
+            if(constraints->grid.pref_width == 0) {
+                constraints->grid.pref_width = child->core.width;
+            }
+            if(constraints->grid.pref_height == 0) {
+                constraints->grid.pref_height = child->core.height;
+            }
+            
+            if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) {
+                continue;
+            }
+            
+            int x = constraints->grid.x;
+            int y = constraints->grid.y;
+            // make sure ncols/nrows is correct
+            // errors shouldn't happen, unless someone messes up the grid internals
+            if(x >= ncols) {
+                fprintf(stderr, "Error: widget x out of bounds\n");
+                continue;
+            }
+            if(y >= nrows) {
+                fprintf(stderr, "Error: widget y out of bounds\n");
+                continue;
+            }
+            GridDef *col = &cols[x];
+            GridDef *row = &rows[y];
+            
+            if(constraints->grid.hexpand) {
+                if(constraints->grid.colspan > 1) {
+                    // check if any column in the span is expanding
+                    // if not, make the last column expanding
+                    GridDef *last_col = col;
+                    for(int c=x;c<ncols;c++) {
+                        last_col = &cols[c];
+                        if(last_col->expand) {
+                            break;
+                        }
+                    }
+                    last_col->expand = TRUE;
+                } else {
+                    col->expand = TRUE;
+                }
+            }
+            if(constraints->grid.vexpand) {
+                if(constraints->grid.rowspan > 1) {
+                    GridDef *last_row = row;
+                    for(int c=x;c<nrows;c++) {
+                        last_row = &rows[c];
+                        if(last_row->expand) {
+                            break;
+                        }
+                    }
+                    last_row->expand = TRUE;
+                } else {
+                    row->expand = TRUE;
+                }
+            } 
+            
+            // column size
+            if(constraints->grid.colspan > 1) {
+                // check size of all columns in span
+                Dimension span_width = col->size;
+                GridDef *last_col = col;
+                for(int s=x+1;s<ncols;s++) {
+                    last_col = &cols[s];
+                    span_width = last_col->size;
+                    
+                }
+                int diff = constraints->grid.pref_width - span_width;
+                if(diff > 0) {
+                    last_col->size += diff; 
+                }
+            } else if(constraints->grid.pref_width > col->size) {
+                col->size = constraints->grid.pref_width;
+            }
+            // row size
+            if(constraints->grid.rowspan > 1) {
+                Dimension span_height = row->size;
+                GridDef *last_row = row;
+                for(int s=x+1;s<nrows;s++) {
+                    last_row = &rows[s];
+                    span_height = last_row->size;
+                    
+                }
+                int diff = constraints->grid.pref_height - span_height;
+                if(diff > 0) {
+                    last_row->size += diff; 
+                }
+            } else if(constraints->grid.pref_height > row->size) {
+                row->size = constraints->grid.pref_height;
+            }
+        }
+        span_max = 50000; // not sure if this is unreasonable low or high
+    }
+    
+    // calc required size
+    for(int i=0;i<ncols;i++) {
+        if(cols[i].expand) {
+            num_cols_expanding++;
+        }
+        req_width += cols[i].size;
+    }
+    for(int i=0;i<nrows;i++) {
+        if(rows[i].expand) {
+            num_rows_expanding++;
+        }
+        req_height += rows[i].size;
+    }
+    
+    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;
+        
+        Widget parent = w->core.parent;
+        Dimension rwidth = req_width;
+        Dimension rheight = req_height;
+        if(rwidth < w->core.width) {
+            //rwidth = w->core.width;
+        }
+        if(rheight < w->core.height) {
+            //rheight = w->core.height;
+        }
+        
+        if(!w->mywidget.sizerequest) {
+            Dimension actual_width, actual_height;
+            w->mywidget.sizerequest = TRUE;
+            
+            //XtWidgetGeometry request;
+            //request.width = req_width;
+            //request.request_mode = CWWidth;
+            //XtWidgetGeometry reply;
+            //XtGeometryResult result = XtMakeGeometryRequest((Widget)w, &request, &reply);
+            
+            XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height);
+            w->mywidget.sizerequest = FALSE;
+            //printf("size request: %d %d\n", (int)actual_width, (int)actual_height);
+        }
+        
+       
+        
+    }
+    
+    // how much space can we add to each expanding col/row
+    int hexpand = 0;
+    int width_diff = (int)w->core.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;
+    for(int i=0;i<ncols;i++) {
+        cols[i].pos = x;
+        if(cols[i].expand) {
+            cols[i].size += hexpand + hexpand2;
+        }
+        x += cols[i].size + w->mywidget.columnspacing;
+        
+        hexpand2 = 0;
+    }
+    
+    int vexpand = 0;
+    int height_diff = (int)w->core.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;
+    for(int i=0;i<nrows;i++) {
+        rows[i].pos = y;
+        if(rows[i].expand) {
+            rows[i].size += vexpand + vexpand2;
+        }
+        y += rows[i].size += w->mywidget.rowspacing;
+        
+        vexpand2 = 0;
+    }
+    
+    for(int i=0;i<w->composite.num_children;i++) {
+        Widget child = w->composite.children[i];
+        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 width = constraints->grid.pref_width;
+        int height = constraints->grid.pref_height;
+        if(constraints->grid.hfill) {
+            if(constraints->grid.colspan > 1) {
+                Dimension cwidth = 0;
+                for(int j=0;j<constraints->grid.colspan;j++) {
+                    if(constraints->grid.x+j < ncols) {
+                        cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->mywidget.columnspacing : 0);
+                    }
+                }
+                width = cwidth;
+            } else {
+                width = c.size - w->mywidget.columnspacing;
+            }
+        }
+        if(constraints->grid.vfill) {
+            if(constraints->grid.rowspan > 1) {
+                Dimension cheight = 0;
+                for(int j=0;j<constraints->grid.rowspan;j++) {
+                    if(constraints->grid.y+j < nrows) {
+                        cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->mywidget.rowspacing : 0);
+                    }
+                }
+                height = cheight;
+            } else {
+                height = r.size - w->mywidget.rowspacing;
+            }
+        }
+        
+        if(width > 0 && height > 0) {
+            XtConfigureWidget(child, x, y, width, height, child->core.border_width);
+        }
+        //printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height);
+    }
+    
+    free(cols);
+    free(rows);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/Grid.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#ifndef GRID_H
+#define GRID_H
+
+#include <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+#include <Xm/XmAll.h>
+#include <Xm/Primitive.h>
+#include <Xm/PrimitiveP.h>
+#include <Xm/ManagerP.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// resources
+#define gridColumnSpacing "gridColumnSpacing"
+#define gridRowSpacing "gridRowSpacing"
+#define gridMargin "gridMargin"
+
+// constraints    
+#define gridColumn "gridColumn"
+#define gridRow "gridRow"
+#define gridColspan "gridColspan"
+#define gridRowspan "gridRowspan"
+#define gridHExpand "gridHExpand"
+#define gridVExpand "gridVExpand"
+#define gridHFill "gridHFill"
+#define gridVFill "gridVFill"
+#define gridMarginLeft "gridMarginLeft"
+#define gridMarginRight "gridMarginRight"
+#define gridMarginTop "gridMarginTop"
+#define gridMarginBottom "gridMarginBottom"
+    
+
+typedef struct GridDef {
+    Dimension size;
+    Dimension pos;
+    Boolean expand;
+} GridDef;
+    
+typedef struct GridClassPart {
+    int test;
+} GridClassPart;
+    
+typedef struct GridClassRec {
+    CoreClassPart        core_class;
+    CompositeClassPart   composite_class;
+    ConstraintClassPart  constraint_class;
+    XmManagerClassPart  manager_class;
+    GridClassPart    mywidgetclass;
+} GridClassRec;
+
+
+typedef struct GridPart {
+    int margin_left;
+    int margin_right;
+    int margin_top;
+    int margin_bottom;
+    int max_col;
+    int max_row;
+    Dimension columnspacing;
+    Dimension rowspacing;
+    Dimension margin;
+    
+    Boolean sizerequest;
+} GridPart;
+
+typedef struct GridRec {
+    CorePart	    core;
+    CompositePart   composite;
+    ConstraintPart  constraint;
+    XmManagerPart   manager;
+    GridPart    mywidget;
+} GridRec;
+
+typedef struct GridContraintPart {
+    Dimension x;
+    Dimension y;
+    Dimension margin_left;
+    Dimension margin_right;
+    Dimension margin_top;
+    Dimension margin_bottom;
+    Boolean hexpand;
+    Boolean vexpand;
+    Boolean hfill;
+    Boolean vfill;
+    Dimension colspan;
+    Dimension rowspan;
+    Dimension pref_width;
+    Dimension pref_height;
+} GridContraintPart;
+
+typedef struct GridConstraintRec {
+    XmManagerConstraintPart manager;
+    GridContraintPart grid;
+} GridConstraintRec;
+
+typedef GridRec* MyWidget;
+
+extern WidgetClass gridClass;
+
+void grid_class_initialize();
+void grid_initialize();
+void grid_realize();
+void grid_destroy();
+void grid_resize();
+void grid_expose();
+Boolean grid_set_values();
+Boolean grid_acceptfocus(Widget , Time*);
+
+void grid_place_children(MyWidget w);
+
+void grid_getfocus();
+void grid_loosefocus();
+
+void grid_constraint_init(
+    Widget	request,
+    Widget	neww,
+    ArgList	args,
+    Cardinal*	num_args
+);
+
+XtGeometryResult GridGeometryManager(Widget	widget, XtWidgetGeometry *request, XtWidgetGeometry *reply);
+void GridChangeManaged(Widget widget);
+Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRID_H */
+
--- a/ui/motif/button.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/button.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -32,71 +32,60 @@
 #include "button.h"
 #include "container.h"
 #include "../common/context.h"
-#include <ucx/mempool.h>
+#include <cx/mempool.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include <cx/compare.h>
+
+#include <Xm/XmAll.h>
 
 
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
-    
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+    Arg xargs[16];
     int n = 0;
-    Arg args[16];
     
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
     
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget button = XmCreatePushButton(parent, "button", args, n);
-    ct->add(ct, button);
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
     
-    if(f) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = data;
-        event->callback = f;
-        event->value = 0;
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreatePushButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    if(args.onclick) {
+        UiEventData *eventdata = malloc(sizeof(UiEventData));
+        eventdata->callback = args.onclick;
+        eventdata->userdata = args.onclickdata;
+        eventdata->obj = obj;
+        eventdata->value = 0;
         XtAddCallback(
                 button,
                 XmNactivateCallback,
                 (XtCallbackProc)ui_push_button_callback,
-                event);
+                eventdata);
+       XtAddCallback(
+                button,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_destroy_eventdata,
+                eventdata);
     }
     
-    XtManageChild(button);
     
+    XmStringFree(label);
     return button;
 }
 
-// wrapper
-int64_t ui_toggle_button_get(UiInteger *i) {
-    int state = 0;
-    XtVaGetValues(i->obj, XmNset, &state, NULL);
-    i->value = state;
-    return state;
-}
-
-void ui_toggle_button_set(UiInteger *i, int64_t value) {
-    Arg arg;
-    XtSetArg(arg, XmNset, value);
-    XtSetValues(i->obj, &arg, 1);
-    i->value = value;
-}
-
-void ui_toggle_button_callback(
-        Widget widget,
-        UiEventData *event,
-        XmToggleButtonCallbackStruct *tb)
-{
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    // TODO: e.document
-    e.intval = tb->set;
-    event->callback(&e, event->userdata); 
-}
-
 void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) {
     UiEvent e;
     e.obj = event->obj;
@@ -106,102 +95,344 @@
     event->callback(&e, event->userdata);
 }
 
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    XtSetArg(xargs[n], XmNfillOnSelect, True); n++;
+    XtSetArg(xargs[n], XmNindicatorOn, False); n++;
+    
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "togglebutton";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group);
+    
+    XmStringFree(label);
+    return button;
+}
 
-static void radio_callback(
-        Widget widget,
-        RadioEventData *event,
-        XmToggleButtonCallbackStruct *tb)
-{
-    if(tb->set) {
-        RadioButtonGroup *group = event->group;
-        if(group->current) {
-            Arg arg;
-            XtSetArg(arg, XmNset, FALSE);
-            XtSetValues(group->current, &arg, 1);
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group);
+    
+    XmStringFree(label);
+    return button;
+}
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+    return ui_checkbox_create(obj, args);
+}
+
+static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {
+    if(event->value > 0) {
+        // button in configured to enable/disable states
+        if(tb->set) {
+            ui_set_group(event->obj->ctx, event->value);
+        } else {
+            ui_unset_group(event->obj->ctx, event->value);
         }
-        group->current = widget;
+    }
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = XmToggleButtonGetState(w);
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(event->var && event->var->value) {
+        UiInteger *v = event->var->value;
+        v->value = e.intval;
+        ui_notify_evt(v->observers, &e);
     }
 }
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
-    
-    int n = 0;
-    Arg args[16];
-    
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
-    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget button = XmCreateToggleButton(parent, "radiobutton", args, n);
-    ct->add(ct, button);
-    
-    if(rgroup) {
-        RadioButtonGroup *group;
-        if(rgroup->obj) {
-            group = rgroup->obj;
-            group->buttons = ucx_list_append(group->buttons, button);
-            group->ref++;
-        } else {
-            group = malloc(sizeof(RadioButtonGroup));
-            group->buttons = ucx_list_append(NULL, button);
-            group->current = button;
-            // this is the first button in the radiobutton group
-            // so we should enable it
-            Arg arg;
-            XtSetArg(arg, XmNset, TRUE);
-            XtSetValues(button, &arg, 1);
-            rgroup->obj = group;
-            
-            group->current = button;
+void ui_bind_togglebutton(
+        UiObject *obj,
+        Widget widget,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state)
+{
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER);
+    if(var) {
+        value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = ui_togglebutton_get;
+        value->set = ui_togglebutton_set;
+        
+        if(value->value) {
+            XmToggleButtonSetState(widget, True, False);
         }
-        
-        RadioEventData *event = malloc(sizeof(RadioEventData));
-        event->obj = obj;
-        event->callback = NULL;
-        event->userdata = NULL;
-        event->group = group;
-        XtAddCallback(
-            button,
-            XmNvalueChangedCallback,
-            (XtCallbackProc)radio_callback,
-            event);
-        
-        rgroup->get = ui_radiobutton_get;
-        rgroup->set = ui_radiobutton_set;
     }
     
-    XtManageChild(button); 
-    return button;
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = onchange;
+    event->userdata = onchangedata;
+    event->var = var;
+    event->observers = NULL;
+    event->value = enable_state;
+    XtAddCallback(
+            widget,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)togglebutton_changed,
+            event);
+    XtAddCallback(
+            widget,
+            XmNdestroyCallback,
+            (XtCallbackProc)ui_destroy_eventdata,
+            event);
+}
+
+int64_t ui_togglebutton_get(UiInteger *i) {
+    Widget togglebutton = i->obj;
+    Boolean state = XmToggleButtonGetState(togglebutton);
+    i->value = state;
+    return state;
+}
+
+void ui_togglebutton_set(UiInteger *i, int64_t value) {
+    Widget togglebutton = i->obj;
+    i->value = value;
+    XmToggleButtonSetState(togglebutton, (Boolean)value, False);
+}
+
+static void destroy_list(Widget w, CxList *list, XtPointer d) {
+    cxListDestroy(list);
 }
 
-int64_t ui_radiobutton_get(UiInteger *value) {
-    RadioButtonGroup *group = value->obj;
+static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {
+    if(event->value > 0) {
+        // button in configured to enable/disable states
+        if(tb->set) {
+            ui_set_group(event->obj->ctx, event->value);
+        } else {
+            ui_unset_group(event->obj->ctx, event->value);
+        }
+    }
+    
+    if(!tb->set) {
+        return; // only handle set-events
+    }
     
-    int i = ucx_list_find(group->buttons, group->current, NULL, NULL);
-    if (i >= 0) {
-        value->value = i;
-        return i;
-    } else {
-        return 0;
+    UiInteger *value = NULL;
+    int64_t v = 0;
+    if(event->var) {
+        value = event->var->value;
+        // find widget index and update all radiobuttons
+        // the UiInteger value must always be up-to-date
+        CxList *list = value->obj;
+        CxIterator i = cxListIterator(list);
+        cx_foreach(Widget, button, i) {
+            Boolean state = False;
+            if(button == w) {
+                value->value = i.index+1; // update value
+                state = True;
+            }
+            XmToggleButtonSetState(button, state, False);
+        }
+        v = value->value;
+    }
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = value;
+    e.intval = v;
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(value) {
+        ui_notify_evt(value->observers, &e);
     }
 }
 
-void ui_radiobutton_set(UiInteger *value, int64_t i) {
-    RadioButtonGroup *group = value->obj;
-    Arg arg;
+void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group) {
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        CxList *rb = value->obj;
+        if(!rb) {
+            // first button in the radiobutton group
+            // create a list for all buttons and use the list as value obj
+            rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+            value->obj = rb;
+            value->get = ui_radiobutton_get;
+            value->set = ui_radiobutton_set;
+            
+            // the first radio button is also responsible for cleanup
+            XtAddCallback(
+                    rbutton,
+                    XmNdestroyCallback,
+                    (XtCallbackProc)destroy_list,
+                    rb);
+        }
+        cxListAdd(rb, rbutton);
+        
+        // set the radiobutton state, if the value is already set
+        if(cxListSize(rb) == value->value) {
+            XmToggleButtonSetState(rbutton, True, False);
+        }
+    }
     
-    XtSetArg(arg, XmNset, FALSE);
-    XtSetValues(group->current, &arg, 1);
+    // the radio button needs to handle change events to update all
+    // other buttons in the radio button group
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = onchange;
+    event->userdata = onchangedata;
+    event->observers = NULL;
+    event->var = var;
+    event->value = enable_group;
+    XtAddCallback(
+            rbutton,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)radiobutton_changed,
+            event);
+    XtAddCallback(
+            rbutton,
+            XmNdestroyCallback,
+            (XtCallbackProc)ui_destroy_eventdata,
+            event);
+}
+
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    XtSetArg(xargs[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++;
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
     
-    UcxList *elm = ucx_list_get(group->buttons, i);
-    if(elm) {
-        Widget button = elm->data;
-        XtSetArg(arg, XmNset, TRUE);
-        XtSetValues(button, &arg, 1);
-        group->current = button;
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        CxList *rb = value->obj;
+        if(!rb) {
+            // first button in the radiobutton group
+            // create a list for all buttons and use the list as value obj
+            rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+            value->obj = rb;
+            value->get = ui_radiobutton_get;
+            value->set = ui_radiobutton_set;
+            
+            // the first radio button is also responsible for cleanup
+            XtAddCallback(
+                    button,
+                    XmNdestroyCallback,
+                    (XtCallbackProc)destroy_list,
+                    rb);
+        }
+        cxListAdd(rb, button);
+        
+        // set the radiobutton state, if the value is already set
+        if(cxListSize(rb) == value->value) {
+            XmToggleButtonSetState(button, True, False);
+        }
+    }
+    
+    // the radio button needs to handle change events to update all
+    // other buttons in the radio button group
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = args.onchange;
+    event->userdata = args.onchangedata;
+    event->observers = NULL;
+    event->var = var;
+    event->value = args.enable_group;
+    XtAddCallback(
+            button,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)radiobutton_changed,
+            event);
+    XtAddCallback(
+            button,
+            XmNdestroyCallback,
+            (XtCallbackProc)ui_destroy_eventdata,
+            event);
+    
+    XmStringFree(label);
+    return button;
+    
+    
+}
+
+int64_t ui_radiobutton_get(UiInteger *i) {
+    // the UiInteger should be updated automatically by change events
+    return i->value;
+}
+
+void ui_radiobutton_set(UiInteger *i, int64_t value) {
+    CxList *list = i->obj;
+    if(i->value > 0) {
+        Widget current = cxListAt(list, i->value-1);
+        if(current) {
+            XmToggleButtonSetState(current, False, False);
+        }
+    }
+    if(value > 0 && value <= cxListSize(list)) {
+        Widget button = cxListAt(list, value-1);
+        if(button) {
+            XmToggleButtonSetState(button, True, False);
+            i->value = value;
+        }
     }
 }
--- a/ui/motif/button.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/button.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,30 +36,24 @@
 extern "C" {
 #endif
 
-typedef struct {
-    UcxList *buttons;
-    Widget  current;
-    int     ref;
-} RadioButtonGroup;
-
-typedef struct {
-    UiObject         *obj;
-    ui_callback      callback;
-    void             *userdata;
-    RadioButtonGroup *group;
-} RadioEventData;
-
-// wrapper
-int64_t ui_toggle_button_get(UiInteger *i);
-void ui_toggle_button_set(UiInteger *i, int64_t value);
-void ui_toggle_button_callback(
-        Widget widget,
-        UiEventData *data,
-        XmToggleButtonCallbackStruct *e);
 void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d);
 
-int64_t ui_radiobutton_get(UiInteger *value);
-void ui_radiobutton_set(UiInteger *value, int64_t i);
+void ui_bind_togglebutton(
+        UiObject *obj,
+        Widget widget,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state);
+
+int64_t ui_togglebutton_get(UiInteger *i);
+void ui_togglebutton_set(UiInteger *i, int64_t value);
+
+void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group);
+
+int64_t ui_radiobutton_get(UiInteger *i);
+void ui_radiobutton_set(UiInteger *i, int64_t value);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/container.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/container.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -34,742 +34,190 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-#define UI_GRID_MAX_COLUMNS 512
-
-static UiBool ui_lb2bool(UiLayoutBool b) {
-    return b == UI_LAYOUT_TRUE ? TRUE : FALSE;
-}
-
-static UiLayoutBool ui_bool2lb(UiBool b) {
-    return b ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE;
-}
-
+#include "Grid.h"
 
-UiContainer* ui_frame_container(UiObject *obj, Widget frame) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiContainer));
-    ct->widget = frame;
-    ct->prepare = ui_frame_container_prepare;
-    ct->add = ui_frame_container_add;
-    return ct;
-}
-
-Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    return ct->widget;
-}
-
-void ui_frame_container_add(UiContainer *ct, Widget widget) {
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
+/* ---------------------------- Box Container ---------------------------- */
 
-UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation) {
-    UiBoxContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiBoxContainer));
-    ct->container.widget = box;
-    ct->container.prepare = ui_box_container_prepare;
-    ct->container.add = ui_box_container_add;
-    ct->orientation = orientation;
-    ct->margin = margin;
-    ct->spacing = spacing;
-    return (UiContainer*)ct;
-}
-
-Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    UiBoxContainer *bc = (UiBoxContainer*)ct;
-    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
-        fill = ui_lb2bool(ct->layout.fill);
-    }
+static UIWIDGET box_create(UiObject *obj, UiContainerArgs args, UiBoxOrientation orientation) { 
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    if(bc->has_fill && fill) {
-        fprintf(stderr, "UiError: container has 2 filled widgets");
-        fill = FALSE;
-    }
-    if(fill) {
-        bc->has_fill = TRUE;
-    }
+    Arg xargs[16];
+    int n = 0;
     
-    int a = *n;
-    // determine fixed and dynamic attachments
-    void *f1;
-    void *f2;
-    void *d1;
-    void *d2;
-    void *w1;
-    void *w2;
-    if(bc->orientation == UI_BOX_VERTICAL) {
-        f1 = XmNleftAttachment;
-        f2 = XmNrightAttachment;
-        d1 = XmNtopAttachment;
-        d2 = XmNbottomAttachment;
-        w1 = XmNtopWidget;
-        w2 = XmNbottomWidget;
-        
-        // margin/spacing
-        XtSetArg(args[a], XmNleftOffset, bc->margin); a++;
-        XtSetArg(args[a], XmNrightOffset, bc->margin); a++;
-        
-        XtSetArg(args[a], XmNtopOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+    if(orientation == UI_BOX_VERTICAL) {
+        //XtSetArg(xargs[n], gridRowSpacing, args.spacing); n++;
     } else {
-        f1 = XmNtopAttachment;
-        f2 = XmNbottomAttachment;
-        d1 = XmNleftAttachment;
-        d2 = XmNrightAttachment;
-        w1 = XmNleftWidget;
-        w2 = XmNrightWidget;
-        
-        // margin/spacing
-        XtSetArg(args[a], XmNtopOffset, bc->margin); a++;
-        XtSetArg(args[a], XmNbottomOffset, bc->margin); a++;
-        
-        XtSetArg(args[a], XmNleftOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
-    }
-    XtSetArg(args[a], f1, XmATTACH_FORM); a++;
-    XtSetArg(args[a], f2, XmATTACH_FORM); a++;
-
-    if(fill) {
-        XtSetArg(args[a], d2, XmATTACH_FORM); a++;
-    }
-    if(bc->prev_widget) {
-        XtSetArg(args[a], d1, XmATTACH_WIDGET); a++;
-        XtSetArg(args[a], w1, bc->prev_widget); a++;
-    } else {
-        XtSetArg(args[a], d1, XmATTACH_FORM); a++;
-    }
-    
-    *n = a;
-    return ct->widget;
-}
-
-void ui_box_container_add(UiContainer *ct, Widget widget) {
-    UiBoxContainer *bc = (UiBoxContainer*)ct;
-    // determine dynamic attachments
-    void *d1;
-    void *d2;
-    void *w1;
-    void *w2;
-    if(bc->orientation == UI_BOX_VERTICAL) {
-        d1 = XmNtopAttachment;
-        d2 = XmNbottomAttachment;
-        w1 = XmNtopWidget;
-        w2 = XmNbottomWidget;
-        
-    } else {
-        d1 = XmNleftAttachment;
-        d2 = XmNrightAttachment;
-        w1 = XmNleftWidget;
-        w2 = XmNrightWidget;
-    }
-    
-    if(bc->prev_widget) {
-        int v = 0;
-        XtVaGetValues(bc->prev_widget, d2, &v, NULL);
-        if(v == XmATTACH_FORM) {
-            XtVaSetValues(
-                    bc->prev_widget,
-                    d2,
-                    XmATTACH_WIDGET,
-                    w2,
-                    widget,
-                    NULL);
-            XtVaSetValues(
-                    widget,
-                    d1,
-                    XmATTACH_NONE,
-                    d2,
-                    XmATTACH_FORM,
-                    NULL);
-        }
-    }
-    bc->prev_widget = widget;
-    
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing) {
-    UiGridContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiGridContainer));
-    ct->container.widget = form;
-    ct->container.prepare = ui_grid_container_prepare;
-    ct->container.add = ui_grid_container_add;
-    ct->columnspacing = columnspacing;
-    ct->rowspacing = rowspacing;
-    return (UiContainer*)ct;
-}
-
-void ui_grid_newline(UiGridContainer *grid) {
-    if(grid->current) {
-        grid->current = NULL;
-    }
-    grid->container.layout.newline = FALSE;
-}
-
-Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    UiGridContainer *grid = (UiGridContainer*)ct;
-    if(ct->layout.newline) {
-        ui_grid_newline(grid);
-    }
-    return ct->widget;
-}
-
-void ui_grid_container_add(UiContainer *ct, Widget widget) {
-    UiGridContainer *grid = (UiGridContainer*)ct;
-    
-    if(grid->current) {
-        grid->current = ucx_list_append(grid->current, widget);
-    } else {
-        grid->current = ucx_list_append(grid->current, widget);
-        grid->lines = ucx_list_append(grid->lines, grid->current);
+        //XtSetArg(xargs[n], gridColumnSpacing, args.spacing); n++;
     }
     
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-static void ui_grid_resize(Widget widget, XtPointer udata, XtPointer cdata) {
-    UiGridContainer *grid = udata;
-    
-    UcxList *rowdim = NULL;
-    int coldim[UI_GRID_MAX_COLUMNS];
-    memset(coldim, 0, UI_GRID_MAX_COLUMNS*sizeof(int));
-    int numcol = 0;
-    
-    // get the minimum size of the columns and rows
-    int sumw = 0;
-    int sumh = 0;
-    UCX_FOREACH(row, grid->lines) {
-        int rheight = 0;
-        int i=0;
-        int sum_width = 0;
-        UCX_FOREACH(elm, row->data) {
-            Widget w = elm->data;
-            int widget_width = 0;
-            int widget_height = 0;
-            XtVaGetValues(
-                    w,
-                    XmNwidth,
-                    &widget_width,
-                    XmNheight,
-                    &widget_height, 
-                    NULL);
-            
-            // get the maximum height in this row
-            if(widget_height > rheight) {
-                rheight = widget_height;
-            }
-            
-            // get the maximum width in this column
-            if(widget_width > coldim[i]) {
-                coldim[i] = widget_width;
-            }
-            sum_width += widget_width;
-            if(sum_width > sumw) {
-                sumw = sum_width;
-            }
-            
-            i++;
-            if(i > numcol) {
-                numcol = i;
-            }
-        }
-        rowdim = ucx_list_append(rowdim, (void*)(intptr_t)rheight);
-        sumh += rheight;
-    }
-    
-    // check container size
-    int gwidth = 0;
-    int gheight = 0;
-    XtVaGetValues(widget, XmNwidth, &gwidth, XmNheight, &gheight, NULL);
-    if(gwidth < sumw || gheight < sumh) {
-        XtVaSetValues(widget, XmNwidth, sumw, XmNheight, sumh, NULL);
-        ucx_list_free(rowdim);
-        return;
-    }
-    
-    
-    // adjust the positions of all children
-    int y = 0;
-    UCX_FOREACH(row, grid->lines) {
-        int x = 0;       
-        int i=0;
-        UCX_FOREACH(elm, row->data) {
-            Widget w = elm->data;
-            XtVaSetValues(
-                    w,
-                    XmNx, x,
-                    XmNy, y,
-                    XmNwidth, coldim[i],
-                    XmNheight, rowdim->data,
-                    NULL);
-            
-            x += coldim[i];
-            i++;
-        }
-        y += (intptr_t)rowdim->data;
-        rowdim = rowdim->next;
-    }
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget grid = XtCreateManagedWidget(args.name ? args.name : "boxcontainer", gridClass, parent, xargs, n);
+    ctn->add(ctn, grid);
     
-    ucx_list_free(rowdim);
-}
-
-UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiContainer));
-    ct->widget = scrolledwindow;
-    ct->prepare = ui_scrolledwindow_container_prepare;
-    ct->add = ui_scrolledwindow_container_add;
-    return ct;
-}
-
-Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    return ct->widget;
-}
-
-void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget) {
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-
-UiContainer* ui_tabview_container(UiObject *obj, Widget frame) {
-    UiTabViewContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
-            1,
-            sizeof(UiTabViewContainer));
-    ct->context = obj->ctx;
-    ct->container.widget = frame;
-    ct->container.prepare = ui_tabview_container_prepare;
-    ct->container.add = ui_tabview_container_add;
-    return (UiContainer*)ct;
-}
-
-Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    int a = *n;
-    XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNtopAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
-    *n = a;
-    return ct->widget;
-}
-
-void ui_tabview_container_add(UiContainer *ct, Widget widget) {
-    UiTabViewContainer *tabview = (UiTabViewContainer*)ct; 
-    
-    if(tabview->current) {
-        XtUnmanageChild(tabview->current);
-    }
-
-    tabview->current = widget;
-    tabview->tabs = ucx_list_append(tabview->tabs, widget);
-    
-    ui_select_tab(ct->widget, 0);
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-UIWIDGET ui_box(UiObject *obj, int margin, int spacing, UiBoxOrientation orientation) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget form = XmCreateForm(parent, "vbox", args, n);
-    ct->add(ct, form);
-    XtManageChild(form);
-    
-    UiObject *newobj = uic_object_new(obj, form);
-    newobj->container = ui_box_container(obj, form, margin, spacing, orientation);
-    uic_obj_add(obj, newobj);
-    
-    return form;
-}
-
-UIWIDGET ui_vbox(UiObject *obj) {
-    return ui_box(obj, 0, 0, UI_BOX_VERTICAL);
-}
-
-UIWIDGET ui_hbox(UiObject *obj) {
-    return ui_box(obj, 0, 0, UI_BOX_HORIZONTAL);
-}
-
-UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
-    return ui_box(obj, margin, spacing, UI_BOX_VERTICAL);
-}
-
-UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
-    return ui_box(obj, margin, spacing, UI_BOX_HORIZONTAL);
-}
-
-UIWIDGET ui_grid(UiObject *obj) {
-    return ui_grid_sp(obj, 0, 0, 0);
-}
-
-UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget grid = XmCreateDrawingArea(parent, "grid", args, n);
-    ct->add(ct, grid);
-    XtManageChild(grid);
-    
-    UiObject *newobj = uic_object_new(obj, grid);
-    newobj->container = ui_grid_container(obj, grid, columnspacing, rowspacing);
-    uic_obj_add(obj, newobj);
-    
-    XtAddCallback (grid, XmNresizeCallback , ui_grid_resize, newobj->container);
+    UiContainerX *container = ui_box_container(obj, grid, orientation);
+    uic_object_push_container(obj, container);
     
     return grid;
 }
 
-UIWIDGET ui_scrolledwindow(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); // TODO: dosn't work, use XmAPPLICATION_DEFINED
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", args, n);
-    ct->add(ct, scrolledwindow);
-    XtManageChild(scrolledwindow);
-    
-    UiObject *newobj = uic_object_new(obj, scrolledwindow);
-    newobj->container = ui_scrolledwindow_container(obj, scrolledwindow);
-    uic_obj_add(obj, newobj);
-    
-    return scrolledwindow;
+// public
+UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return box_create(obj, args, UI_BOX_VERTICAL);
+}
+
+// public
+UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return box_create(obj, args, UI_BOX_HORIZONTAL);
 }
 
-UIWIDGET ui_sidebar(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    
-    XtSetArg(args[n], XmNorientation, XmHORIZONTAL);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget pane = XmCreatePanedWindow(parent, "pane", args, n);
-    ct->add(ct, pane);
-    XtManageChild(pane);
-    
-    // add sidebar widget
-    Widget sidebar = XmCreateForm(pane, "sidebar", args, 0);
-    XtManageChild(sidebar);
-    
-    UiObject *left = uic_object_new(obj, sidebar);
-    left->container = ui_box_container(left, sidebar, 0, 0, UI_BOX_VERTICAL);
-    
-    // add content widget
-    XtSetArg (args[0], XmNpaneMaximum, 8000);
-    Widget content = XmCreateForm(pane, "content_area", args, 1);
-    XtManageChild(content);
-    
-    UiObject *right = uic_object_new(obj, content);
-    right->container = ui_box_container(right, content, 0, 0, UI_BOX_VERTICAL);
-    
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
-    
-    return sidebar;
+UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation) {
+    UiBoxContainer *ctn = ui_malloc(obj->ctx, sizeof(UiBoxContainer));
+    memset(ctn, 0, sizeof(UiBoxContainer));
+    ctn->container.prepare = orientation == UI_BOX_VERTICAL ? ui_vbox_prepare : ui_hbox_prepare;
+    ctn->container.add = ui_box_container_add;
+    ctn->container.widget = grid;
+    ctn->n = 0;
+    return (UiContainerX*)ctn;
 }
 
-UIWIDGET ui_tabview(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    // create a simple frame as container widget
-    // when tabs are selected, the current child will be replaced by the
-    // the new tab widget
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    n++;
-    XtSetArg(args[n], XmNshadowThickness, 0);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget form = XmCreateForm(parent, "tabview", args, n);
-    ct->add(ct, form);
-    XtManageChild(form);
-    
-    UiObject *tabviewobj = uic_object_new(obj, form);
-    tabviewobj->container = ui_tabview_container(obj, form);
-    uic_obj_add(obj, tabviewobj);
-    
-    XtVaSetValues(form, XmNuserData, tabviewobj->container, NULL);
-    
-    return form;
+static Widget ui_box_container_prepare(UiBoxContainer *box, Arg *args, int *n) {
+    int a = *n;
+    box->n++;
+    return box->container.widget;
 }
 
-void ui_tab(UiObject *obj, char *title) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.label = title;
-    
-    ui_vbox(obj);
-}
-
-void ui_select_tab(UIWIDGET tabview, int tab) {
-    UiTabViewContainer *ct = NULL;
-    XtVaGetValues(tabview, XmNuserData, &ct, NULL);
-    if(ct) {
-        XtUnmanageChild(ct->current);
-        UcxList *elm = ucx_list_get(ct->tabs, tab);
-        if(elm) {
-            XtManageChild(elm->data);
-            ct->current = elm->data;
-        } else {
-            fprintf(stderr, "UiError: front tab index: %d\n", tab);
-        }
-    } else {
-        fprintf(stderr, "UiError: widget is not a tabview\n");
+Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiBoxContainer *box = (UiBoxContainer*)ctn;
+    int a = *n;
+    XtSetArg(args[a], gridRow, box->n); a++;
+    if(box->container.layout.fill == UI_ON) {
+        XtSetArg(args[a], gridVExpand, TRUE); a++;
+        XtSetArg(args[a], gridVFill, TRUE); a++;
     }
+    XtSetArg(args[a], gridHExpand, TRUE); a++;
+    XtSetArg(args[a], gridHFill, TRUE); a++;
+    *n = a;
+    return ui_box_container_prepare(box, args, n);
 }
 
-
-/* document tabview */
-
-static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
-    MotifTabbedPane *v = (MotifTabbedPane*)udata;
-    
-    int width = 0;
-    int height = 0;
-    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
-    int button_width = width / 4;
-    int x = 0;
-    UCX_FOREACH(elm, v->tabs) {
-        UiTab *tab = elm->data;
-        XtVaSetValues(
-                tab->tab_button,
-                XmNx, x,
-                XmNy, 0,
-                XmNwidth,
-                button_width,
-                
-                NULL);
-        x += button_width;
+Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiBoxContainer *box = (UiBoxContainer*)ctn;
+    int a = *n;
+    XtSetArg(args[a], gridColumn, box->n); a++;
+    if(box->container.layout.fill == UI_ON) {
+        XtSetArg(args[a], gridHExpand, TRUE); a++;
+        XtSetArg(args[a], gridHFill, TRUE); a++;
     }
-    
-    if(height <= v->height) {
-        XtVaSetValues(widget, XmNheight, v->height + 4, NULL);
-    }
+    XtSetArg(args[a], gridVExpand, TRUE); a++;
+    XtSetArg(args[a], gridVFill, TRUE); a++;
+    *n = a;
+    return ui_box_container_prepare(box, args, n);
 }
 
-static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
-    MotifTabbedPane *v = (MotifTabbedPane*)udata;
-    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
-    XEvent *event = cbs->event;
-    Display *dpy = XtDisplay(widget); 
-    
-    XGCValues gcvals;
-    GC gc;
-    Pixel fgpix;
-    
-    int tab_x;
-    int tab_width;
-    XtVaGetValues(v->current->tab_button, XmNx, &tab_x, XmNwidth, &tab_width, XmNhighlightColor, &fgpix, NULL);
-    
-    gcvals.foreground = v->bg1;
-    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
-      
-    int width = 0;
-    int height = 0;
-    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
-    XFillRectangle(dpy, XtWindow(widget), gc, 0, 0, width, height);
-    
-    gcvals.foreground = fgpix;
-    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
-    
-    XFillRectangle(dpy, XtWindow(widget), gc, tab_x, 0, tab_width, height);
+void ui_box_container_add(UiContainerPrivate *ctn, Widget widget) {
+    ui_reset_layout(ctn->layout);
     
 }
 
-UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+
+/* ---------------------------- Grid Container ---------------------------- */
+
+// public
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    Arg xargs[16];
     int n = 0;
-    Arg args[16];
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    
-    Widget tabview = XmCreateForm(parent, "tabview_form", args, n);
-    XtManageChild(tabview);
     
-    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
-    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
-    XtSetArg(args[2], XmNspacing, 1);
-    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[6], XmNmarginWidth, 0);
-    XtSetArg(args[7], XmNmarginHeight, 0);
-    Widget tabbar = XmCreateDrawingArea(tabview, "tabbar", args, 8);
-    XtManageChild(tabbar);
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    XtSetArg(args[0], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[1], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[2], XmNtopAttachment, XmATTACH_WIDGET);
-    XtSetArg(args[3], XmNtopWidget, tabbar);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNshadowThickness, 0);
-    Widget tabct = XmCreateForm(tabview, "tabview", args, 6);
-    XtManageChild(tabct);
+    Widget parent = ctn->prepare(ctn, 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);
+    ctn->add(ctn, grid);
     
-    MotifTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(MotifTabbedPane));
-    tabbedpane->view.ctx = uic_current_obj(obj)->ctx;
-    tabbedpane->view.widget = tabct;
-    tabbedpane->view.document = NULL;
-    tabbedpane->tabbar = tabbar;
-    tabbedpane->tabs = NULL;
-    tabbedpane->current = NULL;
-    tabbedpane->height = 0;
+    UiContainerX *container = ui_grid_container(obj, grid);
+    uic_object_push_container(obj, container);
     
-    XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabbedpane);
-    XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabbedpane);
-    
-    return &tabbedpane->view;
+    return grid;
 }
 
-UiObject* ui_document_tab(UiTabbedPane *view) {
-    MotifTabbedPane *v = (MotifTabbedPane*)view;
-    int n = 0;
-    Arg args[16];
-    
-    // hide the current tab content
-    if(v->current) {
-        XtUnmanageChild(v->current->content->widget);
+UiContainerX* ui_grid_container(UiObject *obj, Widget grid) {
+    UiGridContainer *ctn = ui_malloc(obj->ctx, sizeof(UiGridContainer));
+    memset(ctn, 0, sizeof(UiBoxContainer));
+    ctn->container.prepare = ui_grid_container_prepare;
+    ctn->container.add = ui_grid_container_add;
+    ctn->container.widget = grid;
+    ctn->x = 0;
+    ctn->y = 0;
+    return (UiContainerX*)ctn;
+}
+
+Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiGridContainer *grid = (UiGridContainer*)ctn;
+    if(ctn->layout.newline) {
+        grid->y++;
+        grid->x = 0;
     }
     
-    UiTab *tab = ui_malloc(view->ctx, sizeof(UiTab));
-    
-    // create the new tab content
-    XtSetArg(args[0], XmNshadowThickness, 0);
-    XtSetArg(args[1], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[2], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNuserData, tab);
-    Widget frame = XmCreateFrame(view->widget, "tab", args, 6);
-    XtManageChild(frame);
-    
-    UiObject *content = ui_malloc(view->ctx, sizeof(UiObject));
-    content->widget = NULL; // initialization for uic_context()
-    content->ctx = uic_context(content, view->ctx->mempool);
-    content->ctx->parent = view->ctx;
-    content->ctx->attach_document = uic_context_attach_document;
-    content->ctx->detach_document2 = uic_context_detach_document2;
-    content->widget = frame;
-    content->window = view->ctx->obj->window;
-    content->container = ui_frame_container(content, frame);
-    content->next = NULL;
-    
-    // add tab button
-    v->tabs = ucx_list_append_a(view->ctx->mempool->allocator, v->tabs, tab);
+    int a = *n;
+    XtSetArg(args[a], gridColumn, grid->x); a++;
+    XtSetArg(args[a], gridRow, grid->y); a++;
+    if(ctn->layout.colspan > 0) {
+        XtSetArg(args[a], gridColspan, ctn->layout.colspan); a++;
+    }
+    if(ctn->layout.rowspan > 0) {
+        XtSetArg(args[a], gridRowspan, ctn->layout.rowspan); a++;
+    }
     
-    XmString label = XmStringCreateLocalized("tab");
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 0);
-    XtSetArg(args[2], XmNhighlightThickness, 0);
-    
-    Widget button = XmCreatePushButton(v->tabbar, "tab_button", args, 3);
-    tab->tabbedpane = v;
-    tab->content = content;
-    tab->tab_button = button; 
-    XtManageChild(button);
-    XtAddCallback(
-        button,
-        XmNactivateCallback,
-        (XtCallbackProc)ui_tab_button_callback,
-        tab);
-    
-    if(v->height == 0) {
-        XtVaGetValues(
-                button,
-                XmNarmColor,
-                &v->bg1,
-                XmNbackground,
-                &v->bg2,
-                XmNheight,
-                &v->height,
-                NULL);
-        v->height += 2; // border
+    if(grid->container.layout.fill == UI_ON) {
+        grid->container.layout.hfill = TRUE;
+        grid->container.layout.vfill = TRUE;
+        grid->container.layout.hexpand = TRUE;
+        grid->container.layout.vexpand = TRUE;
     }
     
-    ui_change_tab(v, tab);
-    ui_tabbar_resize(v->tabbar, v, NULL);
+    if(grid->container.layout.hfill) {
+        XtSetArg(args[a], gridHFill, TRUE); a++;
+    }
+    if(grid->container.layout.vfill) {
+        XtSetArg(args[a], gridVFill, TRUE); a++;
+    }
+    if(grid->container.layout.hexpand) {
+        XtSetArg(args[a], gridHExpand, TRUE); a++;
+    }
+    if(grid->container.layout.vexpand) {
+        XtSetArg(args[a], gridVExpand, TRUE); a++;
+    }
     
-    return content;
-}
-
-void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) {  
-    MotifTabbedPane *t = tab->tabbedpane;
-    if(t->current) {
-        XtUnmanageChild(t->current->content->widget);
-        XtVaSetValues(t->current->tab_button, XmNset, 0, NULL);
-    }
-    XtManageChild(tab->content->widget);
-    
-    ui_change_tab(t, tab);
-    
+    *n = a;
+    return ctn->widget;
 }
 
-void ui_change_tab(MotifTabbedPane *pane, UiTab *tab) {
-    UiContext *ctx = tab->content->ctx;
-    ctx->parent->detach_document2(ctx->parent, pane->current->content->ctx->document);
-    ctx->parent->attach_document(ctx->parent, ctx->document);
-    
-    if(pane->current) {
-        XtVaSetValues(pane->current->tab_button, XmNshadowThickness, 0, XmNbackground, pane->bg1, NULL);
-    }
-    XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, pane->bg2, NULL);
-    
-    pane->current = tab;
-    pane->index = ucx_list_find(pane->tabs, tab, NULL, NULL);
-    printf("index: %d\n", pane->index);
-    
-    // redraw tabbar
-    Display *dpy = XtDisplay(pane->tabbar);
-    Window window = XtWindow(pane->tabbar);
-    if(dpy && window) {
-        XClearArea(dpy, XtWindow(pane->tabbar), 0, 0, 0, 0, TRUE);
-        XFlush(dpy);
-    }
+void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget) {
+    UiGridContainer *grid = (UiGridContainer*)ctn;
+    grid->x++;
+    ui_reset_layout(ctn->layout);
 }
 
-void ui_tab_set_document(UiContext *ctx, void *document) {
-    if(ctx->parent->document) {
-        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
-    }
-    uic_context_attach_document(ctx, document);
-    //uic_context_set_document(ctx->parent, document);
-    //ctx->parent->document = document;
-    
-    UiTab *tab = NULL;
-    XtVaGetValues(
-            ctx->obj->widget,
-            XmNuserData,
-            &tab,
-            NULL);
-    if(tab) {
-        if(tab->tabbedpane->current == tab) {
-            ctx->parent->attach_document(ctx->parent, ctx->document);
-        }
-    } else {
-        fprintf(stderr, "UiError: ui_bar_set_document: Cannot set document");
-    }
+
+/* -------------------- Container Helper Functions -------------------- */
+
+void ui_container_begin_close(UiObject *obj) {
+    UiContainerPrivate *ct = ui_obj_container(obj);
+    ct->container.close = 1;
 }
 
+int ui_container_finish(UiObject *obj) {
+    UiContainerPrivate *ct = ui_obj_container(obj);
+    if(ct->container.close) {
+        ui_end_new(obj);
+        return 0;
+    }
+    return 1;
+}
 
 
 /*
@@ -779,27 +227,7 @@
  *
  */
 
-void ui_layout_fill(UiObject *obj, UiBool fill) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.fill = ui_bool2lb(fill);
-}
-
-void ui_layout_hexpand(UiObject *obj, UiBool expand) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.hexpand = expand;
-}
-
-void ui_layout_vexpand(UiObject *obj, UiBool expand) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.vexpand = expand;
-}
-
-void ui_layout_gridwidth(UiObject *obj, int width) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.gridwidth = width;
-}
-
 void ui_newline(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
+    UiContainerPrivate *ct = ui_obj_container(obj);
     ct->layout.newline = TRUE;
 }
--- a/ui/motif/container.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/container.h	Sat Jan 04 16:38:48 2025 +0100
@@ -31,32 +31,43 @@
 
 #include "../ui/toolkit.h"
 #include "../ui/container.h"
-#include <ucx/list.h>
+#include <cx/list.h>
 #include <string.h>
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
-
-#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
     
-typedef struct MotifTabbedPane    MotifTabbedPane;
-typedef struct UiTab              UiTab;
-typedef struct UiBoxContainer     UiBoxContainer;
-typedef struct UiGridContainer    UiGridContainer;
-typedef struct UiTabViewContainer UiTabViewContainer;
-typedef struct UiLayout           UiLayout;
+#define UI_APPLY_LAYOUT(layout, args) \
+    layout.fill = args.fill; \
+    layout.hexpand = args.hexpand; \
+    layout.vexpand = args.vexpand; \
+    layout.hfill = args.hfill; \
+    layout.vfill = args.vfill; \
+    layout.colspan = args.colspan; \
+    layout.rowspan = args.rowspan
+    
+typedef enum UiBoxOrientation UiBoxOrientation;
+    
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
 
-typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool);
-
-typedef enum UiLayoutBool     UiLayoutBool;
-typedef enum UiBoxOrientation UiBoxOrientation;
+#define ui_obj_container(obj) (UiContainerPrivate*)obj->container_end
+    
+typedef struct UiLayout UiLayout;
 
-
-enum UiLayoutBool {
-    UI_LAYOUT_UNDEFINED = 0,
-    UI_LAYOUT_TRUE,
-    UI_LAYOUT_FALSE,
+struct UiLayout {
+    UiTri        fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    int          width;
+    int          colspan;
+    int          rowspan;
 };
 
 enum UiBoxOrientation {
@@ -64,93 +75,37 @@
     UI_BOX_HORIZONTAL
 };
 
-struct UiLayout {
-    UiLayoutBool fill;
-    UiBool       newline;
-    char         *label;
-    UiBool       hexpand;
-    UiBool       vexpand;
-    int          gridwidth;
-};
+typedef struct UiContainerPrivate UiContainerPrivate;
 
-struct UiContainer {
-    Widget   widget;
-    Widget   (*prepare)(UiContainer*, Arg *, int*, UiBool);
-    void     (*add)(UiContainer*, Widget);
-    UiLayout layout;
-    Widget   current;
-    Widget   menu;
-};
 
-struct UiBoxContainer {
-    UiContainer container;
-    Widget      prev_widget;
-    UiBool      has_fill;
-    UiBoxOrientation orientation;
-    int         margin;
-    int         spacing;
-};
-
-struct UiGridContainer {
-    UiContainer container;
-    UcxList     *lines;
-    UcxList     *current;
-    int         columnspacing;
-    int         rowspacing;
-};
-
-struct UiTabViewContainer {
-    UiContainer container;
-    UiContext   *context;
-    Widget      widget;
-    UcxList     *tabs;
-    Widget      current;
+struct UiContainerPrivate {
+    UiContainerX container;
+    Widget       (*prepare)(UiContainerPrivate*, Arg *, int*);
+    void         (*add)(UiContainerPrivate*, Widget);
+    Widget       widget;
+    UiLayout     layout;
 };
 
-struct MotifTabbedPane {
-    UiTabbedPane view;
-    Widget       tabbar;
-    UcxList      *tabs;
-    UiTab        *current;
-    int          index;
-    Pixel        bg1;
-    Pixel        bg2;
-    int          height;
-};
-
-struct UiTab {
-    MotifTabbedPane *tabbedpane;
-    UiObject        *content;
-    Widget          tab_button;
-};
-    
-
-UiContainer* ui_frame_container(UiObject *obj, Widget frame);
-Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_frame_container_add(UiContainer *ct, Widget widget);
+typedef struct UiBoxContainer {
+    UiContainerPrivate container;
+    Dimension n;
+} UiBoxContainer;
 
-UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation);
-Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_box_container_add(UiContainer *ct, Widget widget);
-
-UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing);
-Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_grid_container_add(UiContainer *ct, Widget widget);
+typedef struct UiGridContainer {
+    UiContainerPrivate container;
+    Dimension x;
+    Dimension y;
+} UiGridContainer;
 
-UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow);
-Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget);
+UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation);
+Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+void ui_box_container_add(UiContainerPrivate *ctn, Widget widget);
 
-UiContainer* ui_tabview_container(UiObject *obj, Widget rowcolumn);
-Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_tabview_container_add(UiContainer *ct, Widget widget);
 
-void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d);
-void ui_change_tab(MotifTabbedPane *pane, UiTab *tab);
-
-void ui_tab_set_document(UiContext *ctx, void *document);
-void ui_tab_detach_document(UiContext *ctx);
-
+UiContainerX* ui_grid_container(UiObject *obj, Widget grid);
+Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/dnd.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/dnd.c	Sat Jan 04 16:38:48 2025 +0100
@@ -28,18 +28,3 @@
 
 #include "dnd.h"
 
-void ui_selection_settext(UiSelection *sel, char *str, int len) {
-    
-}
-
-void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
-    
-}
-
-char* ui_selection_gettext(UiSelection *sel) {
-    
-}
-
-char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
-    
-}
--- a/ui/motif/graphics.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/graphics.c	Sat Jan 04 16:38:48 2025 +0100
@@ -34,250 +34,3 @@
 #include "graphics.h"
 
 #include "container.h"
-
-static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) {
-    UiDrawEvent *drawevent = u;
-    //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c;
-    //XEvent *event = cbs->event;
-    Display *dpy = XtDisplay(widget);
-    
-    UiEvent ev;
-    ev.obj = drawevent->obj;
-    ev.window = drawevent->obj->window;
-    ev.document = drawevent->obj->ctx->document;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    XtVaGetValues(
-            widget,
-            XmNwidth,
-            &drawevent->gr.g.width,
-            XmNheight,
-            &drawevent->gr.g.height,
-            NULL);
-    
-    XGCValues gcvals;
-    gcvals.foreground = BlackPixelOfScreen(XtScreen(widget));
-    drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals);
-    
-    drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata);
-}
-
-UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    int n = 0;
-    Arg args[16];
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n);
-    
-    if(f) {
-        UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
-        event->obj = obj;
-        event->callback = f;
-        event->userdata = userdata;
-        
-        event->gr.display = XtDisplay(drawingarea);
-        event->gr.widget = drawingarea;
-        
-        Colormap colormap;
-        XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL);    
-        event->gr.colormap = colormap;
-        
-        XtAddCallback(
-                drawingarea,
-                XmNexposeCallback,
-                ui_drawingarea_expose,
-                event);
-        
-        XtVaSetValues(drawingarea, XmNuserData, event, NULL);
-    }
-    
-    XtManageChild(drawingarea);
-    return drawingarea;
-}
-
-static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) {
-    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
-    XEvent *xevent = cbs->event;
-    UiMouseEventData *event = u;
-    
-    if (cbs->reason == XmCR_INPUT) {
-        if (xevent->xany.type == ButtonPress) {
-            UiMouseEvent me;
-            me.x = xevent->xbutton.x;
-            me.y = xevent->xbutton.y;
-            // TODO: configurable double click time
-            me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2;
-            
-            UiEvent e;
-            e.obj = event->obj;
-            e.window = event->obj->window;
-            e.document = event->obj->ctx->document;
-            e.eventdata = &me;
-            e.intval = 0;
-            event->callback(&e, event->userdata);
-            
-            
-            event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time;
-        }
-    }
-    
-}
-
-void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
-    if(f) {
-        UiMouseEventData *event = malloc(sizeof(UiMouseEventData));
-        event->obj = obj;
-        event->callback = f;
-        event->userdata = u;
-        event->last_event = 0;
-        
-        XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event);
-    }
-}
-
-void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
-    XtVaGetValues(
-            drawingarea,
-            XmNwidth,
-            width,
-            XmNheight,
-            height,
-            NULL);
-}
-
-void ui_drawingarea_redraw(UIWIDGET drawingarea) {
-    //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True);
-    UiDrawEvent *event;
-    XtVaGetValues(drawingarea, XmNuserData, &event, NULL);
-    ui_drawingarea_expose(drawingarea, event, NULL);
-}
-
-
-/* -------------------- text layout functions -------------------- */
-UiTextLayout* ui_text(UiGraphics *g) {
-    UiTextLayout *text = malloc(sizeof(UiTextLayout));
-    memset(text, 0, sizeof(UiTextLayout));
-    text->text = NULL;
-    text->length = 0;
-    text->widget = ((UiXlibGraphics*)g)->widget;
-    text->fontset = NULL;
-    return text;
-}
-
-static void create_default_fontset(UiTextLayout *layout) {
-    char **missing = NULL;
-    int num_missing = 0;
-    char *def = NULL;
-    Display *dpy = XtDisplay(layout->widget);
-    XFontSet fs = XCreateFontSet(
-        dpy,
-        "-dt-interface system-medium-r-normal-s*utf*:,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni,"
-                "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208",
-        &missing, &num_missing, &def);
-    layout->fontset = fs;
-}
-
-void ui_text_free(UiTextLayout *text) {
-    // TODO
-}
-
-void ui_text_setstring(UiTextLayout *layout, char *str) {
-    ui_text_setstringl(layout, str, strlen(str));
-}
-
-void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
-    layout->text = str;
-    layout->length = len;
-    layout->changed = 1;
-}
-
-void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
-    create_default_fontset(layout);//TODO
-    layout->changed = 1;
-}
-
-void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
-    if(layout->changed) {
-        XRectangle ext, lext;
-        XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext);
-        layout->width = ext.width;
-        layout->height = ext.height;
-        layout->changed = 0;
-    }
-    *width = layout->width;
-    *height = layout->height;
-}
-
-void ui_text_setwidth(UiTextLayout *layout, int width) {
-    layout->maxwidth = width;
-}
-
-
-/* -------------------- drawing functions -------------------- */
-
-void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    XColor color;
-    color.flags= DoRed | DoGreen | DoBlue; 
-    color.red = red * 257;
-    color.green = green * 257;
-    color.blue = blue * 257;
-    XAllocColor(gr->display, gr->colormap, &color);
-    XSetForeground(gr->display, gr->gc, color.pixel);
-}
-
-void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2);
-}
-
-void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    if(fill) {
-        XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
-    } else {
-        XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
-    }
-}
-
-void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    int width, height;
-    ui_text_getsize(text, &width, &height);
-    if(text->maxwidth > 0) {
-        XRectangle clip;
-        clip.x = x;
-        clip.y = y;
-        clip.width = text->maxwidth;
-        clip.height = height;
-        XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted);
-    }
-    
-    XmbDrawString(
-            gr->display,
-            XtWindow(gr->widget),
-            text->fontset,
-            gr->gc,
-            x,
-            y + height,
-            text->text,
-            text->length);
-    
-    XSetClipMask(gr->display, gr->gc, None);
-}
--- a/ui/motif/graphics.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/graphics.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,38 +36,6 @@
 extern "C" {
 #endif
 
-typedef struct UiXlibGraphics {
-    UiGraphics g;
-    Display    *display;
-    Widget     widget;
-    Colormap   colormap;
-    GC         gc;
-} UiXlibGraphics;
-
-typedef struct UiDrawEvent {
-    ui_drawfunc    callback;
-    UiObject       *obj;
-    void           *userdata;
-    UiXlibGraphics gr;
-} UiDrawEvent;
-
-typedef struct UiMouseEventData {
-    UiObject    *obj;
-    ui_callback callback;
-    void        *userdata;
-    Time        last_event;
-} UiMouseEventData;
-
-struct UiTextLayout {
-    char     *text;
-    size_t   length;
-    Widget   widget;
-    XFontSet fontset;
-    int      maxwidth;
-    int      width;
-    int      height;
-    int      changed;
-};
 
 
 #ifdef	__cplusplus
--- a/ui/motif/image.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/image.c	Sat Jan 04 16:38:48 2025 +0100
@@ -6,35 +6,3 @@
 
 #include "image.h"
 
-UiIcon* ui_icon(const char *name, int size) {
-    return NULL;
-}
-
-UiIcon* ui_icon_unscaled(const char *name, int size) {
-    return NULL;
-}
-
-void ui_free_icon(UiIcon *icon) {
-    
-}
-
-UiImage* ui_icon_image(UiIcon *icon) {
-    return NULL;
-}
-
-UiImage* ui_image(const char *filename) {
-    return NULL;
-}
-
-UiImage* ui_named_image(const char *filename, const char *name) {
-    return NULL;
-}
-
-UiImage* ui_load_image_from_path(const char *path, const char *name) {
-    return NULL;
-}
-
-void ui_free_image(UiImage *img) {
-    
-}
-
--- a/ui/motif/label.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/label.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -33,38 +33,154 @@
 #include "container.h"
 #include "../common/context.h"
 #include "../common/object.h"
-#include <ucx/mempool.h>
+
+#include "Grid.h"
 
-UIWIDGET ui_label(UiObject *obj, char *label) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
-    
+static UIWIDGET label_create(UiObject *obj, UiLabelArgs args, int align) {
+    Arg xargs[16];
     int n = 0;
-    Arg args[16]; 
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    
+    XtSetArg(xargs[n], XmNalignment, align); n++;
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "label";
+    Widget w = XmCreateLabel(parent, name, xargs, n);
+    XtManageChild(w);
+    ctn->add(ctn, w);
     
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget widget = XmCreateLabel(parent, "label", args, n);
-    ct->add(ct, widget);  
-    XtManageChild(widget);
+    XmStringFree(label);
+    return w;
+} 
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {
+    return label_create(obj, args, XmALIGNMENT_CENTER);
+}
+
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+    return label_create(obj, args, XmALIGNMENT_BEGINNING);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+    return label_create(obj, args, XmALIGNMENT_END);
+}
+
+
+/* ------------------------------ progressbar ------------------------------ */
+
+static void ui_destroy_progressbar(Widget w, UiProgressBar *pb, XtPointer d) {
+    // TODO: free other stuff
+    free(pb);
+}
+
+static void ui_progressbar_expose(Widget widget, UiProgressBar *pb, XtPointer c) {
+    Display *dp = XtDisplay(widget);
+    Window w = XtWindow(widget);
+    if(w == 0) {
+        return;
+    }
+    if(!pb->gc) {
+        XGCValues gcvals;
+        gcvals.foreground = pb->color;
+        pb->gc = XCreateGC(dp, w, (GCForeground), &gcvals);
+    }
     
-    return widget;
+    Dimension width = widget->core.width;
+    Dimension height = widget->core.height;
+    
+    double value = (pb->value - pb->min) / (pb->max - pb->min);
+    Dimension valueW = (double)width * value;
+    
+    XClearArea(dp, w, 0, 0, width, height, False);
+    XFillRectangle(dp, w, pb->gc, 0, 0, valueW, widget->core.height);
 }
 
-UIWIDGET ui_space(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized("");
-    
+UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) {
+    Arg xargs[16];
     int n = 0;
-    Arg args[16]; 
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    
+    char *name = args.name ? (char*)args.name : "progressbar";
+    Widget frame = XmCreateFrame(parent, name, xargs, n);
+    
+    // create a button and get some informations about the height, shadow, highlight, ....
+    // we want the frame to have the same dimensions as a normal button
+    Widget test = XmCreatePushButton(frame, "button", NULL, 0);
+    XtManageChild(test);
+    Dimension h, highlightThickness, shadowThickness;
+    Pixel highlightColor;
+    XtVaGetValues(test, XmNheight, &h, XmNhighlightThickness, &highlightThickness,
+            XmNshadowThickness, &shadowThickness, XmNhighlightColor, &highlightColor, NULL);
+    XtDestroyWidget(test);
+    
+    // adjust frame
+    XtVaSetValues(frame, XmNshadowThickness, shadowThickness, gridMarginLeft, highlightThickness,
+            gridMarginRight, highlightThickness, gridMarginTop, highlightThickness,
+            gridMarginBottom, highlightThickness, NULL);
+    
+    // create drawing area
+    Dimension da_height = h - 2*highlightThickness - 2*shadowThickness;
+    n = 0;
+    XtSetArg(xargs[n], XmNheight, da_height); n++;
+    Widget drawingArea = XmCreateDrawingArea(frame, "progressbar_drawingarea", xargs, n);
+    XtManageChild(drawingArea);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_DOUBLE);
     
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget widget = XmCreateLabel(parent, "space_label", args, n);
-    ct->add(ct, widget);  
-    XtManageChild(widget);
+    UiProgressBar *progressbarData = malloc(sizeof(UiProgressBar));
+    progressbarData->widget = drawingArea;
+    progressbarData->min = args.min;
+    progressbarData->max = args.max == 0 ? 100 : args.max;
+    progressbarData->value = 50;
+    progressbarData->var = var;
+    progressbarData->color = highlightColor;
+    progressbarData->gc = NULL; // initialize on first expose
+    
+    if(var) {
+        UiDouble *d = var->value;
+        progressbarData->value = d->value;
+        d->obj = progressbarData;
+        d->get = ui_progressbar_get;
+        d->set = ui_progressbar_set;
+    }
     
-    return widget;
+    XtAddCallback(
+            drawingArea,
+            XmNexposeCallback,
+            (XtCallbackProc)ui_progressbar_expose,
+            progressbarData);
+    XtAddCallback(
+            drawingArea,
+            XmNresizeCallback,
+            (XtCallbackProc)ui_progressbar_expose,
+            progressbarData);
+    
+    
+    XtManageChild(frame);
+    return frame;
 }
+
+double ui_progressbar_get(UiDouble *d) {
+    UiProgressBar *pb = d->obj;
+    d->value = pb->value;
+    return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+    UiProgressBar *pb = d->obj;
+    d->value = value;
+    pb->value = value;
+    ui_progressbar_expose(pb->widget, pb, NULL);
+}
--- a/ui/motif/label.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/label.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -29,12 +29,26 @@
 #ifndef LABEL_H
 #define	LABEL_H
 
+#include "../ui/display.h"
+#include "../common/context.h"
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
+typedef struct UiProgressBar {
+    Widget widget;
+    GC gc;
+    UiVar *var;
+    double min;
+    double max;
+    double value;
+    Pixel color;
+} UiProgressBar;
 
 
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/list.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/list.c	Sat Jan 04 16:38:48 2025 +0100
@@ -34,87 +34,118 @@
 #include "list.h"
 #include "../common/object.h"
 
-
-void* ui_strmodel_getvalue(void *elm, int column) {
-    return column == 0 ? elm : NULL;
-}
-
-
-UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    int count;
-    XmStringTable items = ui_create_stringlist(var->value, getvalue, &count);
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    Arg args[8];
-    int n = 0;
-    XtSetArg(args[n], XmNitemCount, count);
-    n++;
-    XtSetArg(args[n], XmNitems, count == 0 ? NULL : items);
-    n++;
+    if(args.multiselection) {
+        XtSetArg(xargs[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++;
+    } else {
+        XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++;
+    }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget widget = XmCreateScrolledList(parent, "listview", args, n);
-    ct->add(ct, XtParent(widget));
+    char *name = args.name ? (char*)args.name : "listview";
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget widget = XmCreateScrolledList(parent, name, xargs, n);
     XtManageChild(widget);
     
-    UiListView *listview = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListView));
-    listview->widget = widget;
-    listview->list = var;
-    listview->getvalue = getvalue;
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.list, args.varname, UI_VAR_LIST);
     
-    for (int i=0;i<count;i++) {
-        XmStringFree(items[i]);
-    }
-    XtFree((char *)items);
+    UiListView *listview = malloc(sizeof(UiListView));
+    memset(listview, 0, sizeof(UiListView));
+    listview->obj = obj;
+    listview->widget = widget;
+    listview->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    listview->var = var;
+    listview->onactivate = args.onactivate;
+    listview->onactivatedata = args.onactivatedata;
+    listview->onselection = args.onselection;
+    listview->onselectiondata = args.onselectiondata;
     
-    if(f) {
-        UiListViewEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiListViewEventData));
-        event->event.obj = obj;
-        event->event.userdata = udata;
-        event->event.callback = f;
-        event->event.value = 0;
-        event->var = var;
-        XtAddCallback(
+    if(var) {
+        UiList *list = var->value;
+        list->obj = listview;
+        list->update = ui_listview_update;
+        list->getselection = ui_listview_getselection;
+        list->setselection = ui_listview_setselection;
+        ui_listview_update(list, 0);
+    }
+    
+    XtAddCallback(
+                widget,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_listview_destroy,
+                listview);
+    
+    XtAddCallback(
                 widget,
                 XmNdefaultActionCallback,
-                (XtCallbackProc)ui_list_selection_callback,
-                event);
-    }
+                (XtCallbackProc)ui_listview_activate,
+                listview);
+    XtAddCallback(
+                widget,
+                XmNextendedSelectionCallback,
+                (XtCallbackProc)ui_listview_selection,
+                listview);
+    XtAddCallback(
+                widget,
+                XmNsingleSelectionCallback,
+                (XtCallbackProc)ui_listview_selection,
+                listview);
     
     return widget;
 }
 
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_listview_var(obj, var, getvalue, f, udata);
+void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) {
+    // TODO
+}
+
+static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) {
+    UiEvent event;
+    event.obj = obj;
+    event.window = obj->window;
+    event.document = obj->ctx->document;
+    event.eventdata = &sel;
+    event.intval = sel.count > 0 ? sel.rows[0] : -1;
+    callback(&event, userdata);
 }
 
-UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = var->value;
-        return ui_listview_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
+static void listview_save_selection(UiListView *listview, XmListCallbackStruct *cb) {
+    UiListSelection sel = { cb->selected_item_count, NULL };
+    if(sel.count > 0) {
+        sel.rows = calloc(sel.count, sizeof(int));
+        for(int i=0;i<sel.count;i++) {
+            sel.rows[i] = cb->selected_item_positions[i]-1;
+        }
     }
-    return NULL;
+    free(listview->current_selection.rows);
+    listview->current_selection = sel;
 }
 
+void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb) {
+    listview_save_selection(listview, cb);
+    if(listview->onactivate) {
+        list_callback(listview->obj, listview->current_selection, listview->onactivate, listview->onactivatedata);
+    }
+}
 
-XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) { 
+void ui_listview_selection(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);
+    }
+}
+
+static XmStringTable create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) { 
     int num = list->count(list);
     XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString));
     void *data = list->first(list);
     for(int i=0;i<num;i++) {
-        items[i] = XmStringCreateLocalized(getvalue(data, 0));
+        char *s = getvalue(data, 0);
+        items[i] = XmStringCreateLocalized(s ? s : "");
         data = list->next(list);
     }
     
@@ -122,16 +153,17 @@
     return items;
 }
 
-
-void ui_listview_update(UiEvent *event, UiListView *view) {
+void ui_listview_update(UiList *list, int i) {
+    UiListView *listview = list->obj;
+    
     int count;
-    XmStringTable items = ui_create_stringlist(
-            view->list->value,
-            view->getvalue,
+    XmStringTable items = create_stringlist(
+            list,
+            listview->getvalue,
             &count);
     
     XtVaSetValues(
-            view->widget,
+            listview->widget,
             XmNitems, count == 0 ? NULL : items,
             XmNitemCount,
             count,
@@ -143,65 +175,24 @@
     XtFree((char *)items);
 }
 
-void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data) {
-    XmListCallbackStruct *cbs = (XmListCallbackStruct *)data;
-    
-    UiEvent e;
-    e.obj = event->event.obj;
-    e.window = event->event.obj->window;
-    e.document = event->event.obj->ctx->document;
-    UiList *list = event->var->value;
-    e.eventdata = list->get(list, cbs->item_position - 1);
-    e.intval = cbs->item_position - 1;
-    event->event.callback(&e, event->event.userdata);
-}
-
-
-/* --------------------------- ComboBox ---------------------------  */
-
-UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_combobox_var(obj, var, getvalue, f, udata);
+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));
+    }
+    return sel;
 }
 
-UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = var->value;
-        return ui_combobox_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    UiListView *listview = list->obj;
+    XmListDeselectAllItems(listview->widget);
+    for(int i=0;i<selection.count;i++) {
+        XmListSelectPos(listview->widget, selection.rows[i]+1, False);
     }
-    return NULL;
 }
 
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiListView *listview = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiListView));
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_NONE);
-    n++;
-    XtSetArg(args[n], XmNtraversalOn, FALSE);
-    n++;
-    XtSetArg(args[n], XmNwidth, 160);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget combobox = XmCreateDropDownList(parent, "combobox", args, n);
-    XtManageChild(combobox);
-    listview->widget = combobox;
-    listview->list = var;
-    listview->getvalue = getvalue;
-    
-    ui_listview_update(NULL, listview);
-    
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
 }
--- a/ui/motif/list.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -38,24 +38,38 @@
 #endif
 
 typedef struct UiListView {
-    Widget          widget;
-    UiVar           *list;
+    UiObject *obj;
+    Widget widget;
+    UiVar *var;
+    UiModel* model;
     ui_getvaluefunc getvalue;
+    
+    UiListSelection current_selection;
+    
+    ui_callback onactivate;
+    void* onactivatedata;
+    ui_callback onselection;
+    void* onselectiondata;
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropsdata;
+    UiBool multiselection;
 } UiListView;
 
-typedef struct UiListViewEventData {
-    UiEventData event;
-    UiVar *var;
-} UiListViewEventData;
+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_update(UiList *list, int i);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
 
 void* ui_strmodel_getvalue(void *elm, int column);
 
-XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count);
-void ui_listview_update(UiEvent *event, UiListView *view);
-void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data);
-
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/menu.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/menu.c	Sat Jan 04 16:38:48 2025 +0100
@@ -36,275 +36,89 @@
 #include "stock.h"
 #include "container.h"
 #include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/types.h"
 #include "../ui/window.h"
 
-UcxList *menus;
-UcxList *current;
-
-void ui_menu(char *label) {
-    // free current menu hierarchy
-    ucx_list_free(current);
-    
-    // create menu
-    UiMenu *menu = malloc(sizeof(UiMenu));
-    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
-    
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;    
-    
-    current = ucx_list_prepend(NULL, menu);
-    menus = ucx_list_append(menus, menu);
-    
-}
-
-void ui_submenu(char *label) {
-    UiMenu *menu = malloc(sizeof(UiMenu));
-    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
-    
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;
-    
-    // add submenu to current menu
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, menu);
-    
-    // set the submenu to current menu
-    current = ucx_list_prepend(current, menu);
-}
-
-void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
-        return;
-    }
-    current = ucx_list_remove(current, current);
-}
-
-void ui_menuitem(char *label, ui_callback f, void *userdata) {
-    ui_menuitem_gr(label, f, userdata, -1);
-}
-
-void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) {
-    ui_menuitem_stgr(stockid, f, userdata, -1);
-}
-
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItem *item = malloc(sizeof(UiMenuItem));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
-    
-    item->label = label;
-    item->userdata = userdata;
-    item->callback = f;
-    item->groups = NULL;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
-    if(!current) {
-        return;
-    }
-    
-    UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
-    
-    item->stockid = stockid;
-    item->userdata = userdata;
-    item->callback = f;
-    item->groups = NULL;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_menuseparator() {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItemI *item = malloc(sizeof(UiMenuItemI));
-    item->add_to = (ui_menu_add_f)add_menuseparator_widget;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
 
 
-void ui_checkitem(char *label, ui_callback f, void *userdata) {
-    if(!current) {
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+void ui_create_menubar(UiObject *obj, Widget window) {
+    UiMenu *menus_begin = uic_get_menu_list();
+    if(!menus_begin) {
         return;
     }
     
-    UiCheckItem *item = malloc(sizeof(UiCheckItem));
-    item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
-    item->label = label;
-    item->callback = f;
-    item->userdata = userdata;
+    Widget menubar = XmCreateMenuBar(window, "menubar", NULL, 0);
+    XtManageChild(menubar);
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-void ui_checkitem_nv(char *label, char *vname) {
-    if(!current) {
-        return;
+    UiMenu *ls = menus_begin;
+    while(ls) {
+        UiMenu *menu = ls;
+        add_menu_widget(menubar, 0, &menu->item, obj);
+        ls = (UiMenu*)ls->item.next;
     }
-    
-    UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
-    item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
-    item->varname = vname;
-    item->label = label;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
 }
 
-void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
-    item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
-    item->callback = f;
-    item->userdata = userdata;
-    item->list = items;
-    
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
-}
-
-
-// private menu functions
-void ui_create_menubar(UiObject *obj) {
-    if(!menus) {
-        return;
-    }
-    
-    Widget menubar = XmCreateMenuBar(obj->widget, "main_list", NULL, 0);
-    XtManageChild(menubar);
-    
-    UcxList *ls = menus;
-    int menu_index = 0;
-    while(ls) {
-        UiMenu *menu = ls->data;
-        menu_index += menu->item.add_to(menubar, menu_index, &menu->item, obj);
-        
-        ls = ls->next;
+void ui_add_menu_items(Widget parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it, obj);
+        it = it->next;
+        index++;
     }
 }
 
-int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
     UiMenu *menu = (UiMenu*)item;
+    Arg args[4];
+    int n = 0;
     
-    Widget menuItem = XtVaCreateManagedWidget(
-            menu->label,
-            xmCascadeButtonWidgetClass,
-            parent,
-            NULL);
-    Widget m = XmVaCreateSimplePulldownMenu(parent, menu->label, i, NULL, NULL);
-    
-    UcxList *ls = menu->items;
-    int menu_index = 0;
-    while(ls) {
-        UiMenuItemI *mi = ls->data;
-        menu_index += mi->add_to(m, menu_index, mi, obj);
-        ls = ls->next;
+    XmString s = NULL;
+    if(menu->label) {
+        s = XmStringCreateLocalized((char*)menu->label);
+        XtSetArg(args[n], XmNlabelString, s); n++;
     }
     
-    return 1;
-}
-
-int add_menuitem_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiMenuItem *mi = (UiMenuItem*)item;
-    
-    Arg args[1];
-    XmString label = XmStringCreateLocalized(mi->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    
-    Widget mitem = XtCreateManagedWidget(
-            "menubutton",
-            xmPushButtonWidgetClass,
+    Widget submenu = XmVaCreateSimplePulldownMenu(parent, "menu_pulldown", i, NULL, NULL);
+    XtSetArg(args[n], XmNsubMenuId, submenu); n++;
+    Widget menuItem = XtCreateManagedWidget(
+            "menuitem",
+            xmCascadeButtonWidgetClass,
             parent,
             args,
-            1);
-    XmStringFree(label);
+            n);
+    
     
-    if(mi->callback != NULL) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = mi->userdata;
-        event->callback = mi->callback;
-        event->value = 0;
-        XtAddCallback(
-                mitem,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
+    if(s) {
+        XmStringFree(s);
     }
     
-    if(mi->groups) {
-        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
-    }
-    
-    return 1;
+    ui_add_menu_items(submenu, i, menu, obj);
 }
 
-int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
-    UiStMenuItem *mi = (UiStMenuItem*)item;
+void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *it = (UiMenuItem*)item;
     
-    UiStockItem *si = ui_get_stock_item(mi->stockid);
-    if(!si) {
-        fprintf(stderr, "UI Error: unknown stock id: %s\n", mi->stockid);
-        return 0;
-    }
-    
-    int n = 0;
+    XmString s = NULL;
     Arg args[4];
-    XmString label = XmStringCreateLocalized(si->label);
-    XmString at = NULL;
-    
-    XtSetArg(args[n], XmNlabelString, label);
-    n++;
-    if(si->accelerator) {
-        XtSetArg(args[n], XmNaccelerator, si->accelerator);
-        n++;
-    }
-    if(si->accelerator_label) {
-        at = XmStringCreateLocalized(si->accelerator_label);
-        XtSetArg(args[n], XmNacceleratorText, at);
-        n++;
+    int n = 0;
+    if(it->label) {
+        s = XmStringCreateLocalized((char*)it->label);
+        XtSetArg(args[n], XmNlabelString, s); n++;
     }
     
     Widget mitem = XtCreateManagedWidget(
@@ -313,150 +127,119 @@
             parent,
             args,
             n);
-    XmStringFree(label);
-    if(at) {
-        XmStringFree(at);
+    if(s) {
+        XmStringFree(s);
     }
     
-    if(mi->callback != NULL) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = mi->userdata;
-        event->callback = mi->callback;
-        event->value = 0;
+    if(it->callback) {
+        UiEventData *eventdata = malloc(sizeof(UiEventData));
+        eventdata->callback = it->callback;
+        eventdata->userdata = it->userdata;
+        eventdata->obj = obj;
+        eventdata->value = 0;
         XtAddCallback(
                 mitem,
                 XmNactivateCallback,
                 (XtCallbackProc)ui_push_button_callback,
-                event);
+                eventdata);
+       XtAddCallback(
+                mitem,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_destroy_eventdata,
+                eventdata);
     }
     
-    if(mi->groups) {
-        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
-    }
-    
-    return 1;
+    ui_set_widget_groups(obj->ctx, mitem, it->groups);
 }
 
-int add_menuseparator_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    Widget s = XmCreateSeparatorGadget (parent, "menu_separator", NULL, 0);
+void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) {
+    Widget s = XmCreateSeparatorGadget (p, "menuseparator", NULL, 0);
     XtManageChild(s);
-    return 1;
 }
 
-int add_checkitem_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiCheckItem *ci = (UiCheckItem*)item;
+void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenuCheckItem *it = (UiMenuCheckItem*)item;
     
-    Arg args[3];
-    XmString label = XmStringCreateLocalized(ci->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNvisibleWhenOff, 1);
+    Arg args[4];
+    int n = 0;
+    XmString s = NULL;
+    if(it->label) {
+        s = XmStringCreateLocalized(it->label);
+        XtSetArg(args[n], XmNlabelString, s); n++;
+    }
+    
+    //XtSetArg(args[n], XmNvisibleWhenOff, 0); n++;
     Widget checkbox = XtCreateManagedWidget(
             "menutogglebutton",
             xmToggleButtonWidgetClass,
-            parent,
+            p,
             args,
-            2);
-    XmStringFree(label);
-    
-    if(ci->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = ci->userdata;
-        event->callback = ci->callback;
-        XtAddCallback(
-            checkbox,
-            XmNvalueChangedCallback,
-            (XtCallbackProc)ui_toggle_button_callback,
-            event);
+            n);
+    if(s) {
+        XmStringFree(s);
     }
     
-    return 1;
+    ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0);
+    
+    ui_set_widget_groups(obj->ctx, checkbox, it->groups);
 }
 
-int add_checkitemnv_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiCheckItemNV *ci = (UiCheckItemNV*)item;
+void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuRadioItem *it = (UiMenuRadioItem*)item;
     
-    Arg args[3];
-    XmString label = XmStringCreateLocalized(ci->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNvisibleWhenOff, 1);
-    Widget checkbox = XtCreateManagedWidget(
-            "menutogglebutton",
-            xmToggleButtonWidgetClass,
-            parent,
-            args,
-            2);
-    XmStringFree(label);
+    Arg args[4];
+    int n = 0;
+    XmString s = NULL;
+    if(it->label) {
+        s = XmStringCreateLocalized(it->label);
+        XtSetArg(args[n], XmNlabelString, s); n++;
+    }
+    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++;
     
-    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
-    if(var) {
-        UiInteger *value = var->value;
-        value->obj = checkbox;
-        value->get = ui_toggle_button_get;
-        value->set = ui_toggle_button_set;
-        value = 0;
-    } else {
-        // TODO: error
-    }
+    Widget button = XmCreateToggleButton(p, "menuradiobutton", args, n);
+    XtManageChild(button);
     
-    return 1;
+    ui_bind_radiobutton(obj, button, NULL, it->varname, it->callback, it->userdata, 0);
 }
 
-int add_menuitem_list_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
+void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item;
-    UcxMempool *mp = obj->ctx->mempool;
+    const CxAllocator *a = obj->ctx->allocator;
     
-    UiActiveMenuItemList *ls = ucx_mempool_malloc(
-            mp,
+    UiActiveMenuItemList *ls = cxMalloc(
+            a,
             sizeof(UiActiveMenuItemList));
-    
     ls->object = obj;
-    ls->menu = parent;
+    ls->menu = p;
     ls->index = i;
     ls->oldcount = 0;
-    ls->list = il->list;
+    ls->getvalue = il->getvalue;
     ls->callback = il->callback;
     ls->userdata = il->userdata;
+    ls->addseparator = il->addseparator;
     
-    ls->list->observers = ui_add_observer(
-            ls->list->observers,
-            (ui_callback)ui_update_menuitem_list,
-            ls);
+    ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); //uic_widget_var(obj->ctx, obj->ctx, NULL, il->varname, UI_VAR_LIST);
+    UiList *list = ls->var->value;
+    
+    UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
+    list->observers = ui_obsvlist_add(list->observers, observer);
+    uic_list_register_observer_destructor(obj->ctx, list, observer);
     
     ui_update_menuitem_list(NULL, ls);
-    
-    return 0;
 }
 
 void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
+    XmString s = NULL;
     Arg args[4];
+    int n;
     
-    // remove old items
+    UiList *ls;
+    if(list->var && list->var->value) {
+        ls = list->var->value;
+    } else {
+        return;
+    }
+    
     if(list->oldcount > 0) {
         Widget *children;
         int nc;
@@ -474,153 +257,57 @@
         }
     }
     
-    char *str = ui_list_first(list->list);
-    if(str) {
-        // add separator
+    void* elm = ui_list_first(ls);
+    int i = 0;
+    if(elm && list->addseparator) {
         XtSetArg(args[0], XmNpositionIndex, list->index);
-        Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1);
+        Widget s = XmCreateSeparatorGadget(list->menu, "menuseparator", args, 1);
         XtManageChild(s);
+        i++;
     }
-    int i = 1;
-    while(str) {
-        XmString label = XmStringCreateLocalized(str);
-        XtSetArg(args[0], XmNlabelString, label);
-        XtSetArg(args[1], XmNpositionIndex, list->index + i);
-
+    
+    ui_getvaluefunc getvalue = list->getvalue;
+    int pos = list->index;
+    while(elm) {
+        n = 0;
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        if(label) {
+            s = XmStringCreateLocalized(label);
+            XtSetArg(args[n], XmNlabelString, s); n++;
+        }
+        XtSetArg(args[n], XmNpositionIndex, pos+i); n++;
+        
         Widget mitem = XtCreateManagedWidget(
                 "menubutton",
                 xmPushButtonWidgetClass,
                 list->menu,
                 args,
-                2);
-        XmStringFree(label);
-        
+                n);
+        if(s) {
+            XmStringFree(s);
+        }
+
         if(list->callback) {
-            // TODO: use mempool
-            UiEventData *event = malloc(sizeof(UiEventData));
-            event->obj = list->object;
-            event->userdata = list->userdata;
-            event->callback = list->callback;
-            event->value = i - 1;
-
+            UiEventData *eventdata = malloc(sizeof(UiEventData));
+            eventdata->callback = list->callback;
+            eventdata->userdata = list->userdata;
+            eventdata->obj = list->object;
+            eventdata->value = 0;
             XtAddCallback(
-                mitem,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
+                    mitem,
+                    XmNactivateCallback,
+                    (XtCallbackProc)ui_push_button_callback,
+                    eventdata);
+            XtAddCallback(
+                    mitem,
+                    XmNdestroyCallback,
+                    (XtCallbackProc)ui_destroy_eventdata,
+                    eventdata);
         }
         
-        str = ui_list_next(list->list);
+        elm = ui_list_next(ls);
         i++;
     }
     
     list->oldcount = i;
 }
-
-void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) {
-    UiEventData *event = udata;
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.intval = 0;
-    event->callback(&e, event->userdata);    
-}
-
-
-/*
- * widget menu functions
- */
-
-static void ui_popup_handler(Widget widget, XtPointer data,  XEvent *event, Boolean *c) {
-    Widget menu = data;
-    XmMenuPosition(menu, (XButtonPressedEvent *)event);
-    XtManageChild(menu);
-    
-    *c = FALSE;
-}
-
-UIMENU ui_contextmenu(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(ct->current) {
-        return ui_contextmenu_w(obj, ct->current);
-    } else {
-        return NULL; // TODO: warn
-    }
-}
-
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0);
-    ct->menu = menu;
-    
-    XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu);
-    
-    return menu;
-}
-
-void ui_contextmenu_popup(UIMENU menu) {
-    
-}
-
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
-    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
-}
-
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    Arg args[4];
-    XmString labelstr = XmStringCreateLocalized(label);
-    XtSetArg(args[0], XmNlabelString, labelstr);
-    
-    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
-    XtManageChild(item);
-    XmStringFree(labelstr);
-}
-
-void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
-    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
-}
-
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    UiStockItem *stockItem = ui_get_stock_item(stockid);
-    Arg args[4];
-    XmString labelstr = XmStringCreateLocalized(stockItem->label);
-    XtSetArg(args[0], XmNlabelString, labelstr);
-    
-    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
-    XtManageChild(item);
-    XmStringFree(labelstr);
-}
--- a/ui/motif/menu.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/menu.h	Sat Jan 04 16:38:48 2025 +0100
@@ -30,94 +30,41 @@
 #define	MENU_H
 
 #include "../ui/menu.h"
-#include <ucx/list.h>
+#include "../common/menu.h"
+#include "../common/context.h"
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
-typedef struct UiMenuItemI      UiMenuItemI;
-typedef struct UiMenu           UiMenu;
-typedef struct UiMenuItem       UiMenuItem;
-typedef struct UiStMenuItem     UiStMenuItem;
-typedef struct UiCheckItem      UiCheckItem;
-typedef struct UiCheckItemNV    UiCheckItemNV;
-typedef struct UiMenuItemList   UiMenuItemList;
-
 typedef struct UiActiveMenuItemList UiActiveMenuItemList;
-
-typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
-    
-struct UiMenuItemI {
-    ui_menu_add_f  add_to;
+struct UiActiveMenuItemList {
+    UiObject         *object;
+    Widget           menu;
+    int              index;
+    int              oldcount;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
+    bool             addseparator;
 };
-
-struct UiMenu {
-    UiMenuItemI    item;
-    char           *label;
-    UcxList        *items;
-    UiMenu         *parent;
-};
-
-struct UiMenuItem {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    char           *label;
-    void           *userdata;
-    UcxList        *groups;
-};
-
-struct UiStMenuItem {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    char           *stockid;
-    void           *userdata;
-    UcxList        *groups;
-};
+    
+typedef void(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
+    
+void ui_create_menubar(UiObject *obj, Widget window);
+void ui_add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
 
-struct UiCheckItem {
-    UiMenuItemI    item;
-    char           *label;
-    ui_callback    callback;
-    void           *userdata;
-};
-
-struct UiCheckItemNV {
-    UiMenuItemI    item;
-    char           *label;
-    char           *varname;
-};
-
-struct UiMenuItemList {
-    UiMenuItemI    item;
-    ui_callback    callback;
-    void           *userdata;
-    UiList         *list;
-};
-
-struct UiActiveMenuItemList {
-    UiObject     *object;
-    Widget       menu;
-    int          index;
-    int          oldcount;
-    UiList       *list;
-    ui_callback  callback;
-    void         *userdata;
-};
-
-void ui_create_menubar(UiObject *obj);
-
-int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuseparator_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_checkitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_checkitemnv_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_list_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_st_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
+void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj);
+void add_checkitemnv_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
 
 void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
-void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata);
-
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/objs.mk	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/objs.mk	Sat Jan 04 16:38:48 2025 +0100
@@ -39,11 +39,11 @@
 MOTIFOBJ += label.o
 MOTIFOBJ += text.o
 MOTIFOBJ += list.o
-MOTIFOBJ += tree.o
 MOTIFOBJ += graphics.o
 MOTIFOBJ += range.o
 MOTIFOBJ += dnd.o
 MOTIFOBJ += image.o
+MOTIFOBJ += Grid.o
 
 TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
 TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- a/ui/motif/range.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/range.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,108 +31,6 @@
 
 #include "range.h"
 #include "container.h"
-#include <ucx/mempool.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
-
-static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    int n = 0;
-    Arg args[16];
-    XtSetArg(args[n], XmNorientation, orientation == UI_HORIZONTAL ? XmHORIZONTAL : XmVERTICAL);
-    n++;
-    XtSetArg (args[n], XmNmaximum, 10);
-    n++;
-    XtSetArg (args[n], XmNsliderSize, 1);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget scrollbar = XmCreateScrollBar(parent, "scrollbar", args, n);
-    XtManageChild(scrollbar);
-    ct->add(ct, scrollbar);
-    
-    if(range) {
-        range->get = ui_scrollbar_get;
-        range->set = ui_scrollbar_set;
-        range->setrange = ui_scrollbar_setrange;
-        range->setextent = ui_scrollbar_setextent;
-        range->obj = scrollbar;
-    }
-    
-    if(f) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
-        event->value = 0;
-        XtAddCallback(
-                scrollbar,
-                XmNvalueChangedCallback,
-                (XtCallbackProc)ui_scrollbar_callback,
-                event);
-    }
-    
-    return scrollbar;
-}
-
-UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
-    return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
-}
-
-UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
-    return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
-}
-
-void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata) {
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.intval = event->value;
-    event->callback(&e, event->userdata);
-}
-
-double ui_scrollbar_get(UiRange *range) {
-    int intval;
-    XtVaGetValues(
-            range->obj,
-            XmNvalue,
-            &intval,
-            NULL);
-    double value = (double)intval / 10;
-    range->value = value;
-    return value;
-}
-
-void   ui_scrollbar_set(UiRange *range, double value) {
-    XtVaSetValues(
-            range->obj,
-            XmNvalue,
-            (int)(value * 10),
-            NULL);
-    range->value = value;
-}
-
-void   ui_scrollbar_setrange(UiRange *range, double min, double max) {
-    XtVaSetValues(
-            range->obj,
-            XmNminimum,
-            (int)(min * 10),
-            XmNmaximum,
-            (int)(max * 10),
-            NULL);
-    range->min = min;
-    range->max = max;
-}
-
-void   ui_scrollbar_setextent(UiRange *range, double extent) {
-    XtVaSetValues(
-            range->obj,
-            XmNsliderSize,
-            (int)(extent * 10),
-            NULL);
-    range->extent = extent;
-}
--- a/ui/motif/range.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/range.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,11 +36,6 @@
 extern "C" {
 #endif
 
-void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata);
-double ui_scrollbar_get(UiRange *range);
-void   ui_scrollbar_set(UiRange *range, double value);
-void   ui_scrollbar_setrange(UiRange *range, double min, double max);
-void   ui_scrollbar_setextent(UiRange *range, double extent);
 
 
 #ifdef __cplusplus
--- a/ui/motif/stock.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/stock.c	Sat Jan 04 16:38:48 2025 +0100
@@ -31,46 +31,6 @@
 
 #include "stock.h"
 #include "../ui/properties.h"
-#include <ucx/map.h>
-
-static UcxMap *stock_items;
+#include <cx/hash_map.h>
 
-void ui_stock_init() {
-    stock_items = ucx_map_new(64);
-    
-    ui_add_stock_item(UI_STOCK_NEW, "New", "Ctrl<Key>N", "Ctrl+N", NULL);
-    ui_add_stock_item(UI_STOCK_OPEN, "Open", "Ctrl<Key>O", "Ctrl+O", NULL);
-    ui_add_stock_item(UI_STOCK_SAVE, "Save", "Ctrl<Key>S", "Ctrl+S", NULL);
-    ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_CLOSE, "Close", "Ctrl<Key>W", "Ctrl+W", NULL);
-    ui_add_stock_item(UI_STOCK_UNDO, "Undo", "Ctrl<Key>Z", "Ctrl+Z", NULL);
-    ui_add_stock_item(UI_STOCK_REDO, "Redo", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_GO_BACK, "Back", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_CUT, "Cut", "Ctrl<Key>X", "Ctrl+X", NULL);
-    ui_add_stock_item(UI_STOCK_COPY, "Copy", "Ctrl<Key>C", "Ctrl+C", NULL);
-    ui_add_stock_item(UI_STOCK_PASTE, "Paste", "Ctrl<Key>V", "Ctrl+V", NULL);
-    ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL);
-}
 
-void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) {
-    UiStockItem *i = malloc(sizeof(UiStockItem));
-    i->label = label;
-    i->accelerator = accelerator;
-    i->accelerator_label = accelerator_label;
-    // TODO: icon
-    
-    ucx_map_cstr_put(stock_items, id, i);
-}
-
-UiStockItem* ui_get_stock_item(char *id) {
-    UiStockItem *item = ucx_map_cstr_get(stock_items, id);
-    if(item) {
-        char *label = uistr_n(id);
-        if(label) {
-            item->label = label;
-        }
-    }
-    return item;
-}
--- a/ui/motif/stock.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/stock.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,18 +35,7 @@
 extern "C" {
 #endif
 
-typedef struct UiStockItem {
-    char *label;
-    char *accelerator;
-    char *accelerator_label;
-    // TODO: icon
-} UiStockItem;
-    
-void ui_stock_init();
 
-void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon);
-
-UiStockItem* ui_get_stock_item(char *id);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/text.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/text.c	Sat Jan 04 16:38:48 2025 +0100
@@ -28,461 +28,666 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
 
 #include "text.h"
 #include "container.h"
 
-
-UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
-    UiContainer *ct = uic_get_current_container(obj);
-    int n = 0;
-    Arg args[16];
-    
-    //XtSetArg(args[n], XmNeditable, TRUE);
-    //n++;
-    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
-    ct->add(ct, XtParent(text_area));
-    XtManageChild(text_area);
-    
-    UiTextArea *uitext = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiTextArea));
-    uitext->ctx = obj->ctx;
-    uitext->last_selection_state = 0;
-    XtAddCallback(
-                text_area,
-                XmNmotionVerifyCallback,
-                (XtCallbackProc)ui_text_selection_callback,
-                uitext);
-    
-    // bind value
-    if(var->value) {
-        UiText *value = var->value;
-        if(value->value.ptr) {
-            XmTextSetString(text_area, value->value.ptr);
-            value->value.free(value->value.ptr);
-        }
-        
-        value->set = ui_textarea_set;
-        value->get = ui_textarea_get;
-        value->getsubstr = ui_textarea_getsubstr;
-        value->insert = ui_textarea_insert;
-        value->setposition = ui_textarea_setposition;
-        value->position = ui_textarea_position;
-        value->selection = ui_textarea_selection;
-        value->length = ui_textarea_length;
-        value->value.ptr = NULL;
-        value->obj = text_area;
-        
-        if(!value->undomgr) {
-            value->undomgr = ui_create_undomgr();
-        }
-        
-        XtAddCallback(
-                text_area,
-                XmNmodifyVerifyCallback,
-                (XtCallbackProc)ui_text_modify_callback,
-                var);
-    }
-    
-    return text_area;
-}
-
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = value;
-    var->type = UI_VAR_SPECIAL;
-    return ui_textarea_var(obj, var);
-}
-
-UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
-    if(var) {
-        return ui_textarea_var(obj, var);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
+#include <cx/string.h>
 
-char* ui_textarea_get(UiText *text) {
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    char *str = XmTextGetString(text->obj);
-    text->value.ptr = str;
-    text->value.free = (ui_freefunc)XtFree;
-    return str;
-}
-
-void ui_textarea_set(UiText *text, char *str) {
-    XmTextSetString(text->obj, str);
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    text->value.ptr = NULL;
-}
-
-char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    int length = end - begin;
-    char *str = XtMalloc(length + 1);
-    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
-    text->value.ptr = str;
-    text->value.free = (ui_freefunc)XtFree;
-    return str;
-}
-
-void ui_textarea_insert(UiText *text, int pos, char *str) {
-    text->value.ptr = NULL;
-    XmTextInsert(text->obj, pos, str);
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-}
-
-void ui_textarea_setposition(UiText *text, int pos) {
-    XmTextSetInsertionPosition(text->obj, pos);
-}
-
-int ui_textarea_position(UiText *text) {
-    long begin;
-    long end;
-    XmTextGetSelectionPosition(text->obj, &begin, &end);
-    text->pos = begin;
-    return text->pos;
-}
-
-void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
-}
-
-int ui_textarea_length(UiText *text) {
-    return (int)XmTextGetLastPosition(text->obj);
-}
-
-
-void ui_text_set(UiText *text, char *str) {
-    if(text->set) {
-        text->set(text, str);
-    } else {
-        if(text->value.ptr) {
-            text->value.free(text->value.ptr);
-        }
-        text->value.ptr = XtNewString(str);
-        text->value.free = (ui_freefunc)XtFree;
-    }
-}
-
-char* ui_text_get(UiText *text) {
-    if(text->get) {
-        return text->get(text);
-    } else {
-        return text->value.ptr;
-    }
-}
 
 
-UiUndoMgr* ui_create_undomgr() {
-    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
-    mgr->begin = NULL;
-    mgr->cur = NULL;
-    mgr->length = 0;
-    mgr->event = 1;
-    return mgr;
-}
-
-void ui_text_selection_callback(
-        Widget widget,
-        UiTextArea *textarea,
-        XtPointer data)
-{
-    long left = 0;
-    long right = 0;
-    XmTextGetSelectionPosition(widget, &left, &right);
-    int sel = left < right ? 1 : 0;
-    if(sel != textarea->last_selection_state) {
-        if(sel) {
-            ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
-        } else {
-            ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
-        }
-    }
-    textarea->last_selection_state = sel;
-}
+/* ------------------------------ Text Field ------------------------------ */
 
-void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
-    UiText *value = var->value;
-    if(!value->obj) {
-        // TODO: bug, fix
-        return;
-    }
-    if(!value->undomgr) {
-        value->undomgr = ui_create_undomgr();
-    }
-    
-    XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
-    int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
-    UiUndoMgr *mgr = value->undomgr;
-    if(!mgr->event) {
-        return;
-    }
-    
-    char *text = txv->text->ptr;
-    int length = txv->text->length;
-    
-    if(mgr->cur) {
-        UcxList *elm = mgr->cur->next;
-        if(elm) {
-            mgr->cur->next = NULL;
-            while(elm) {
-                elm->prev = NULL;   
-                UcxList *next = elm->next;
-                ui_free_textbuf_op(elm->data);
-                free(elm);
-                elm = next;
-            }
-        }
-        
-        if(type == UI_TEXTBUF_INSERT) {
-            UiTextBufOp *last_op = mgr->cur->data;
-            if(
-                last_op->type == UI_TEXTBUF_INSERT &&
-                ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
-            {
-                // append text to last op
-                int ln = last_op->len;
-                char *newtext = malloc(ln + length + 1);
-                memcpy(newtext, last_op->text, ln);
-                memcpy(newtext+ln, text, length);
-                newtext[ln+length] = '\0';
-                
-                last_op->text = newtext;
-                last_op->len = ln + length;
-                last_op->end += length;
-
-                return;
-            }
-        }
-    }
-    
-    char *str;
-    if(type == UI_TEXTBUF_INSERT) {
-        str = malloc(length + 1);
-        memcpy(str, text, length);
-        str[length] = 0;
-    } else {
-        length = txv->endPos - txv->startPos;
-        str = malloc(length + 1);
-        XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
-    }
+static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs args, int frameless, int password) {
+    Arg xargs[16];
+    int n = 0;
     
-    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
-    op->type = type;
-    op->start = txv->startPos;
-    op->end = txv->endPos + 1;
-    op->len = length;
-    op->text = str;
-    
-    UcxList *elm = ucx_list_append(NULL, op);
-    mgr->cur = elm;
-    mgr->begin = ucx_list_concat(mgr->begin, elm);
-}
-
-int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
-    // return 1 if oldstr + newstr are one word
-    
-    int has_space = 0;
-    for(int i=0;i<oldlen;i++) {
-        if(oldstr[i] < 33) {
-            has_space = 1;
-            break;
-        }
-    }
-    
-    for(int i=0;i<newlen;i++) {
-        if(has_space && newstr[i] > 32) {
-            return 1;
-        }
-    }
-    
-    return 0;
-}
-
-void ui_free_textbuf_op(UiTextBufOp *op) {
-    if(op->text) {
-        free(op->text);
-    }
-    free(op);
-}
-
-
-void ui_text_undo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
-    
-    if(mgr->cur) {
-        UiTextBufOp *op = mgr->cur->data;
-        mgr->event = 0;
-        switch(op->type) {
-            case UI_TEXTBUF_INSERT: {
-                XmTextReplace(value->obj, op->start, op->end, "");
-                break;
-            }
-            case UI_TEXTBUF_DELETE: {
-                XmTextInsert(value->obj, op->start, op->text);
-                break;
-            }
-        }
-        mgr->event = 1;
-        mgr->cur = mgr->cur->prev;
-    }
-}
-
-void ui_text_redo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
-    
-    UcxList *elm = NULL;
-    if(mgr->cur) {
-        if(mgr->cur->next) {
-            elm = mgr->cur->next;
-        }
-    } else if(mgr->begin) {
-        elm = mgr->begin;
-    }
-    
-    if(elm) {
-        UiTextBufOp *op = elm->data;
-        mgr->event = 0;
-        switch(op->type) {
-            case UI_TEXTBUF_INSERT: {
-                XmTextInsert(value->obj, op->start, op->text);
-                break;
-            }
-            case UI_TEXTBUF_DELETE: {
-                XmTextReplace(value->obj, op->start, op->end, "");
-                break;
-            }
-        }
-        mgr->event = 1;
-        mgr->cur = elm;
-    }
-}
-
-
-/* ------------------------- textfield ------------------------- */
-
-static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
-    UiContainer *ct = uic_get_current_container(obj);
-    int n = 0;
-    Arg args[16];
-    XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
-    n++;
-    if(width > 0) {
-        XtSetArg(args[n], XmNcolumns, width / 2 + 1);
-        n++;
-    }
     if(frameless) {
-        XtSetArg(args[n], XmNshadowThickness, 0);
+        XtSetArg(xargs[n], XmNshadowThickness, 0);
         n++;
     }
     if(password) {
         // TODO
     }
     
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget textfield = XmCreateText(parent, "text_field", args, n);
-    ct->add(ct, textfield);
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    char *name = args.name ? (char*)args.name : "textfield";
+    Widget textfield = XmCreateTextField(parent, name, xargs, n);
     XtManageChild(textfield);
     
-    // bind value
-    if(value) {
+    ui_set_widget_groups(obj->ctx, textfield, args.groups);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING);
+    if(var) {
+        UiString *value = (UiString*)var->value;
+        value->obj = textfield;
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+        
         if(value->value.ptr) {
-            XmTextSetString(textfield, value->value.ptr);
-            value->value.free(value->value.ptr);
+            ui_textfield_set(value, value->value.ptr);
         }
-        
-        value->set = ui_textfield_set;
-        value->get = ui_textfield_get;
-        value->value.ptr = NULL;
-        value->obj = textfield;
     }
     
     return textfield;
 }
 
-static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
-    if(var) {
-        UiString *value = var->value;
-        return ui_textfield(obj, value);
-    } else {
-        // TODO: error
-    }
-    return NULL;
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, FALSE, FALSE);
 }
 
-UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, TRUE, FALSE);
 }
 
-UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, TRUE, FALSE, value);
-}
-
-UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
-}
-
-UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, TRUE, value);
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, FALSE, FALSE);
 }
 
-UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
-}
-
-UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
-}
-
-
 char* ui_textfield_get(UiString *str) {
-    if(str->value.ptr) {
-        str->value.free(str->value.ptr);
-    }
-    char *value = XmTextGetString(str->obj);
+    str->value.free(str->value.ptr);
+    char *value = XmTextFieldGetString(str->obj);
     str->value.ptr = value;
     str->value.free = (ui_freefunc)XtFree;
     return value;
 }
 
-void ui_textfield_set(UiString *str, char *value) {
-    XmTextSetString(str->obj, value);
-    if(str->value.ptr) {
-        str->value.free(str->value.ptr);
+void ui_textfield_set(UiString *str, const char *value) {
+    XmTextFieldSetString(str->obj, (void*)value);
+    str->value.ptr = NULL;
+    str->value.free(str->value.ptr);
+}
+
+
+
+
+
+/* -------------------- 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;i<p->numSegments;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;i<p->numSegments;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);
     }
-    str->value.ptr = 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;i<bar->numSegments;i++) {  
+        if(bar->pathSegments[i] == w) {
+            bar->selection = i;
+            XmToggleButtonSetState(w, True, False);
+            break;
+        }
+    }
+    
+    UiPathElm elm = bar->current_pathelms[i];
+    cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len));
+    if(bar->updateDir) {
+        bar->updateDir(bar->updateDirData, name.ptr, i);
+    }
+    free(name.ptr);
+}
+
+static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
+    for(int i=0;i<nelm;i++) {
+        free(elms[i].name);
+        free(elms[i].path);
+    }
+    free(elms);
+}
+
+void PathBarSetPath(PathBar *bar, const char *path)
+{
+    if(bar->path) {
+        free(bar->path);
+    }
+    bar->path = strdup(path);
+    
+    for(int i=0;i<bar->numSegments;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;i<nelm;i++) {
+        UiPathElm elm = path_elm[i];
+        
+        cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len));
+        str = XmStringCreateLocalized(elm.name);
+        free(name.ptr);
+        
+        XtSetArg(args[0], XmNlabelString, str);
+        XtSetArg(args[1], XmNfillOnSelect, True);
+        XtSetArg(args[2], XmNindicatorOn, False);
+        Widget button = XmCreateToggleButton(bar->widget, "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) {
+    PathBar *pathbar = (PathBar*)data;
+    // TODO: check if there is somonething missing
+    XtFree((void*)pathbar->pathSegments);
+    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;
+    evt.obj = event->obj;
+    evt.window = evt.obj->window;
+    evt.document = evt.obj->ctx->document;
+    evt.eventdata = path;
+    evt.intval = index;
+    event->callback(&evt, event->userdata);
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    // TODO: name
+    
+
+    PathBar *pathbar = CreatePathBar(parent, xargs, n);
+    if(!args.getpathelm) {
+        pathbar->getpathelm= default_pathelm_func;
+    } else {
+        pathbar->getpathelm = args.getpathelm;
+        pathbar->getpathelmdata = args.getpathelmdata;
+    }
+    
+    
+    XtManageChild(pathbar->widget);
+    ctn->add(ctn, pathbar->widget);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = pathbar;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    if(args.onactivate) {
+        UiEventData *eventdata = malloc(sizeof(UiEventData));
+        eventdata->callback = args.onactivate;
+        eventdata->userdata = args.onactivatedata;
+        eventdata->obj = obj;
+        eventdata->value = 0;
+        
+        pathbar->updateDir = pathbar_activate;
+        pathbar->updateDirData = eventdata;
+        
+        XtAddCallback(
+                pathbar->widget,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_destroy_eventdata,
+                eventdata);
+    }
+    
+    XtAddCallback(
+            pathbar->widget,
+            XmNdestroyCallback,
+            (XtCallbackProc)destroy_pathbar,
+            pathbar);
+    
+    return pathbar->widget;
+}
+
+char* ui_path_textfield_get(UiString *str) {
+    PathBar *pathbar = str->obj;
+    str->value.free(str->value.ptr);
+    char *value = XmTextFieldGetString(pathbar->textfield);
+    str->value.ptr = value;
+    str->value.free = (ui_freefunc)XtFree;
+    return value;
+}
+
+void ui_path_textfield_set(UiString *str, const char *value) {
+    PathBarSetPath(str->obj, value);
+}
--- a/ui/motif/text.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/text.h	Sat Jan 04 16:38:48 2025 +0100
@@ -31,55 +31,19 @@
 
 #include "../ui/text.h"
 #include "toolkit.h"
-#include <ucx/list.h>
+#include <cx/list.h>
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
-#define UI_TEXTBUF_INSERT 0
-#define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp {
-    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
-    int  start;
-    int  end;
-    int  len;
-    char *text;
-} UiTextBufOp;
-    
-typedef struct UiUndoMgr {
-    UcxList *begin;
-    UcxList *cur;
-    int     length;
-    int     event;
-} UiUndoMgr;
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, const char *value);
 
-typedef struct UiTextArea {
-    UiContext *ctx;
-    int last_selection_state;
-} UiTextArea;
+char* ui_path_textfield_get(UiString *str);
+void ui_path_textfield_set(UiString *str, const char *value);
+
     
-char* ui_textarea_get(UiText *text);
-void ui_textarea_set(UiText *text, char *str);
-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_selection(UiText *text, int *begin, int *end);
-int ui_textarea_length(UiText *text);
-
-UiUndoMgr* ui_create_undomgr();
-void ui_text_selection_callback(
-        Widget widget,
-        UiTextArea *textarea,
-        XtPointer data);
-void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
-int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
-void ui_free_textbuf_op(UiTextBufOp *op);
-
-char* ui_textfield_get(UiString *str);
-void ui_textfield_set(UiString *str, char *value);
-
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/toolbar.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/toolbar.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -35,332 +35,10 @@
 #include "button.h"
 #include "stock.h"
 #include "list.h"
-#include <ucx/mempool.h>
+
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
 #include "../common/context.h"
 
-static UcxMap *toolbar_items;
-static UcxList *defaults;
-
-void ui_toolbar_init() {
-    toolbar_items = ucx_map_new(16);
-}
-
-void ui_toolitem(char *name, char *label, ui_callback f, void *userdata) {
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
-    item->label = label;
-    item->image = NULL;
-    item->callback = f;
-    item->userdata = userdata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgr(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    UiStToolItem *item = malloc(sizeof(UiStToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
-    item->stockid = stockid;
-    item->callback = f;
-    item->userdata = userdata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
-    // TODO
-    
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
-    item->label = label;
-    item->image = img;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-
-void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
-    // TODO
-    
-    UiStToolItem *item = malloc(sizeof(UiStToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_toggle_widget;
-    item->stockid = stockid;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, udata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle_imggr(char *name, char *label, char *img, ui_callback f, void *udata, ...) {
-    // TODO
-    
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = label;
-    item->image = img;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, udata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolbar_combobox(
-        char *name,
-        UiList *list,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;  
-    cb->list = list;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    ucx_map_cstr_put(toolbar_items, name, cb);
-}
-
-void ui_toolbar_combobox_str(
-        char *name,
-        UiList *list,
-        ui_callback f,
-        void *udata)
-{
-    ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
-}
-
-void ui_toolbar_combobox_nv(
-        char *name,
-        char *listname,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;  
-    cb->listname = listname;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    ucx_map_cstr_put(toolbar_items, name, cb);
-}
-
-
-void ui_toolbar_add_default(char *name) {
-    char *s = strdup(name);
-    defaults = ucx_list_append(defaults, s);
-}
-
-Widget ui_create_toolbar(UiObject *obj, Widget parent) {
-    if(!defaults) {
-        return NULL;
-    }
-    
-    Arg args[8];
-    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
-    Widget frame = XmCreateFrame(parent, "toolbar_frame", args, 5);
-    
-    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
-    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
-    XtSetArg(args[2], XmNspacing, 1);
-    Widget toolbar = XmCreateRowColumn (frame, "toolbar", args, 3);
-    
-    UCX_FOREACH(elm, defaults) {
-        UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data);
-        if(item) {
-            item->add_to(toolbar, item, obj);
-        } else if(!strcmp(elm->data, "@separator")) {
-            // TODO
-        } else {
-            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
-        }
-    }
-    
-    XtManageChild(toolbar);
-    XtManageChild(frame);
-    
-    return frame;
-}
-
-void add_toolitem_widget(Widget parent, UiToolItem *item, UiObject *obj) {
-    Arg args[4];
-    
-    XmString label = XmStringCreateLocalized(item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
-    
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_st_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
-    Arg args[8];
-    
-    UiStockItem *stock_item = ui_get_stock_item(item->stockid);
-     
-    XmString label = XmStringCreateLocalized(stock_item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
- 
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_toggle_widget(Widget parent, UiToolItem *item, UiObject *obj) {
-    Arg args[8];
-    
-    XmString label = XmStringCreateLocalized(item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    XtSetArg(args[3], XmNindicatorOn, XmINDICATOR_NONE);
-    Widget button = XmCreateToggleButton(parent, "toolbar_toggle_button", args, 4);
-    
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNvalueChangedCallback,
-                (XtCallbackProc)ui_toggle_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_st_toggle_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
-    
-}
-
-void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj) {
-    UiListView *listview = ucx_mempool_malloc(
-                obj->ctx->mempool,
-                sizeof(UiListView));
-    
-    UiVar *var = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiVar));
-    var->value = item->list;
-    var->type = UI_VAR_SPECIAL;
-    
-    Arg args[8];
-    XtSetArg(args[0], XmNshadowThickness, 1);
-    XtSetArg(args[1], XmNindicatorOn, XmINDICATOR_NONE);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    XtSetArg(args[3], XmNwidth, 120);
-    Widget combobox = XmCreateDropDownList(tb, "toolbar_combobox", args, 4);
-    XtManageChild(combobox);
-    listview->widget = combobox;
-    listview->list = var;
-    listview->getvalue = item->getvalue;
-    
-    ui_listview_update(NULL, listview);
-    
-    if(item->callback) {
-        // TODO:
-        
-    }
-}
-
-void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj) {
-    
-}
--- a/ui/motif/toolbar.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/toolbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -30,73 +30,13 @@
 #define	TOOLBAR_H
 
 #include "../ui/toolbar.h"
-#include <ucx/map.h>
-#include <ucx/list.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
-typedef struct UiToolItemI    UiToolItemI;
-typedef struct UiToolItem     UiToolItem;
-typedef struct UiStToolItem   UiStToolItem;
-
-typedef struct UiToolbarComboBox   UiToolbarComboBox;
-typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
-
-typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*);
-
-struct UiToolItemI {
-    ui_toolbar_add_f  add_to;
-};
-
-struct UiToolItem {
-    UiToolItemI item;
-    char           *label;
-    void           *image;
-    ui_callback    callback;
-    void           *userdata;
-    UcxList        *groups;
-    Boolean        isimportant;
-};
-
-struct UiStToolItem {
-    UiToolItemI    item;
-    char           *stockid;
-    ui_callback    callback;
-    void           *userdata;
-    UcxList        *groups;
-    Boolean        isimportant;
-};
-
-struct UiToolbarComboBox {
-    UiToolItemI         item;
-    UiList              *list;
-    ui_getvaluefunc     getvalue;
-    ui_callback         callback;
-    void                *userdata;
-};
-
-struct UiToolbarComboBoxNV {
-    UiToolItemI         item;
-    char                *listname;
-    ui_getvaluefunc     getvalue;
-    ui_callback         callback;
-    void                *userdata;
-};
-
-void ui_toolbar_init();
-
-Widget ui_create_toolbar(UiObject *obj, Widget parent);
-
-void add_toolitem_widget(Widget tb, UiToolItem *item, UiObject *obj);
-void add_toolitem_st_widget(Widget tb, UiStToolItem *item, UiObject *obj);
-void add_toolitem_toggle_widget(Widget tb, UiToolItem *item, UiObject *obj);
-void add_toolitem_st_toggle_widget(Widget tb, UiStToolItem *item, UiObject *obj);
-
-void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj);
-void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj);
-
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/toolkit.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/toolkit.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -33,15 +33,21 @@
 
 #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 <ucx/buffer.h>
+#include <cx/buffer.h>
+
+#include <X11/Intrinsic.h>
+#include <Xm/CutPaste.h>
 
 static XtAppContext app;
 static Display *display;
 static Widget active_window;
-static char *application_name;
+static const char *application_name;
 
 static ui_callback   startup_func;
 static void          *startup_data;
@@ -68,6 +74,11 @@
         "*rt*fontType: FONT_IS_XFT",
         "*rt*fontName: Sans",
         "*rt*fontSize: 11",
+        
+        "*window_frame.shadowType: SHADOW_ETCHED_OUT",
+        "*window_frame.shadowThickness: 1",
+        "*togglebutton.shadowThickness: 1",
+        "*togglebutton.highlightThickness: 2",
 	NULL
 };
 
@@ -76,8 +87,9 @@
     read(event_pipe[0], &ptr, sizeof(void*));
 }
 
-void ui_init(char *appname, int argc, char **argv) { 
+void ui_init(const char *appname, int argc, char **argv) { 
     application_name = appname;
+    uic_init_global_context();
     
     XtToolkitInitialize();
     XtSetLanguageProc(NULL, NULL, NULL);
@@ -85,15 +97,10 @@
     XtAppSetFallbackResources(app, fallback);
     
     display =  XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv);
-    char **missing = NULL;
-    int nm = 0;
-    char *def = NULL;
-    XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def);
     
     uic_docmgr_init();
-    ui_toolbar_init();
-    ui_stock_init();
-    
+    uic_menu_init();
+    uic_toolbar_init();
     uic_load_app_properties();
     
     if(pipe(event_pipe)) {
@@ -108,11 +115,11 @@
             NULL);
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
-Display* ui_get_display() {
+Display* ui_motif_get_display() {
     return display;
 }
 
@@ -157,11 +164,12 @@
 void ui_show(UiObject *obj) {
     uic_check_group_widgets(obj->ctx);
     XtRealizeWidget(obj->widget);
-    ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if
 }
 
-// implemented in window.c
-//void ui_close(UiObject *obj)
+void ui_close(UiObject *obj) {
+    
+}
+
 
 void ui_set_enabled(UIWIDGET widget, int enabled) {
     XtSetSensitive(widget, enabled);
@@ -182,17 +190,16 @@
 }
 
 static Boolean ui_job_finished(void *data) {
-    printf("WorkProc\n");
     UiJob *job = data;
-    
-    UiEvent event;
-    event.obj = job->obj;
-    event.window = job->obj->window;
-    event.document = job->obj->ctx->document;
-    event.intval = 0;
-    event.eventdata = NULL;
-
-    job->finish_callback(&event, job->finish_data);
+    if(job->finish_callback) {
+        UiEvent event;
+        event.obj = job->obj;
+        event.window = job->obj->window;
+        event.document = job->obj->ctx->document;
+        event.intval = 0;
+        event.eventdata = NULL;
+        job->finish_callback(&event, job->finish_data);
+    }
     free(job);
     return TRUE;
 }
@@ -201,11 +208,11 @@
     UiJob *job = data;
     int result = job->job_func(job->job_data);
     if(!result) {
-        printf("XtAppAddWorkProc\n");
         write(event_pipe[1], &job, sizeof(void*)); // hack
         XtAppAddWorkProc(app, ui_job_finished, job);
         
     }
+    return NULL;
 }
 
 void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
@@ -220,7 +227,6 @@
 }
 
 void ui_clipboard_set(char *str) {
-    printf("copy: {%s}\n", str);
     int length = strlen(str) + 1;
     
     Display *dp = XtDisplayOfObject(active_window);
@@ -286,6 +292,9 @@
     return active_window;
 }
 
+/*
+ * doesn't work with gnome anymore
+ */
 void ui_window_dark_theme(Display *dp, Window window) {
     Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False);
     Atom type = XInternAtom(dp, "UTF8_STRING", False);
@@ -299,3 +308,23 @@
             (const unsigned char*)"dark",
             4);
 }
+
+void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) {
+    free(data);
+}
+
+void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) {
+    if(!groups) {
+        return;
+    }
+    size_t ngroups = uic_group_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);
+        ui_set_enabled(widget, FALSE);
+    }
+}
+
--- a/ui/motif/toolkit.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/toolkit.h	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -38,15 +38,39 @@
 extern "C" {
 #endif
 
-Display* ui_get_display();
-
 typedef struct UiEventData {
     UiObject    *obj;
     ui_callback callback;
     void        *userdata;
     int         value;
+    void        *customdata;
 } UiEventData;
 
+typedef struct UiEventDataExt {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    ui_callback callback2;
+    void        *userdata2;
+    int         value0;
+    int         value1;
+    int         value2;
+    int         value3;
+    void        *customdata0;
+    void        *customdata1;
+    void        *customdata2;
+    void        *customdata3;
+} UiEventDataExt;
+
+typedef struct UiVarEventData {
+    UiObject    *obj;
+    UiVar       *var;
+    UiObserver  **observers;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+} UiVarEventData;
+
 typedef struct UiJob {
     UiObject      *obj;
     ui_threadfunc job_func;
@@ -60,12 +84,19 @@
 
 void ui_exit_mainloop();
 
+Display* ui_motif_get_display(void);
+
 void ui_set_active_window(Widget w);
 Widget ui_get_active_window();
 
 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_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);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/tree.c	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,324 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include "tree.h"
-
-#include "container.h"
-#include "../common/object.h"
-#include "../common/context.h"
-#include <ucx/utils.h>
-
-UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
-    // TODO: check if modelinfo is complete
-    
-    Arg args[32];
-    int n = 0;
-    
-    // create scrolled window
-    UiContainer *ct = uic_get_current_container(obj);
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    
-    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC);
-    n++;
-    XtSetArg(args[n], XmNshadowThickness, 0);
-    n++;
-    Widget scrollw = XmCreateScrolledWindow(parent, "scroll_win", args, n);
-    ct->add(ct, scrollw);
-    XtManageChild(scrollw);
-    
-    // create table headers
-    XmStringTable header = (XmStringTable)XtMalloc(
-            model->columns * sizeof(XmString));
-    for(int i=0;i<model->columns;i++) {
-        header[i] = XmStringCreateLocalized(model->titles[i]);
-    }
-    n = 0;
-    XtSetArg(args[n], XmNdetailColumnHeading, header);
-    n++;
-    XtSetArg(args[n], XmNdetailColumnHeadingCount, model->columns);
-    n++;
-    
-    // set res
-    XtSetArg(args[n], XmNlayoutType, XmDETAIL);
-    n++;
-    XtSetArg(args[n], XmNentryViewType, XmSMALL_ICON);
-    n++;
-    XtSetArg(args[n], XmNselectionPolicy, XmSINGLE_SELECT);
-    n++;
-    XtSetArg(args[n], XmNwidth, 600);
-    n++;
-    
-    // create widget
-    //UiContainer *ct = uic_get_current_container(obj);
-    //Widget parent = ct->add(ct, args, &n);
-    
-    Widget container = XmCreateContainer(scrollw, "table", args, n);
-    XtManageChild(container);
-    
-    // add callbacks
-    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
-    event->obj = obj;
-    event->activate = cb.activate;
-    event->selection = cb.selection;
-    event->userdata = cb.userdata;
-    event->last_selection = NULL;
-    if(cb.selection) {
-        XtAddCallback(
-                container,
-                XmNselectionCallback,
-                (XtCallbackProc)ui_table_select_callback,
-                event);
-    }
-    if(cb.activate) {
-        XtAddCallback(
-                container,
-                XmNdefaultActionCallback,
-                (XtCallbackProc)ui_table_action_callback,
-                event);
-    }
-    
-    // add initial data
-    UiList *list = var->value;
-    void *data = list->first(list);
-    int width = 0;
-    while(data) {
-        int w = ui_add_icon_gadget(container, model, data);
-        if(w > width) {
-            width = w;
-        }
-        data = list->next(list);
-    }
-    
-    UiTableView *tableview = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiTableView));
-    tableview->widget = container;
-    tableview->var = var;
-    tableview->model = model;
-    
-    // set new XmContainer width
-    XtVaSetValues(container, XmNwidth, width, NULL);
-    
-    // cleanup
-    for(int i=0;i<model->columns;i++) {
-        XmStringFree(header[i]);
-    }
-    XtFree((char*)header);
-    
-    return scrollw;
-}
-
-UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = data;
-    var->type = UI_VAR_SPECIAL;
-    return ui_table_var(obj, var, model, cb);
-}
-
-void ui_table_update(UiEvent *event, UiTableView *view) {
-    // clear container
-    Widget *children;
-    int nc;
-
-    XtVaGetValues(
-            view->widget,
-            XmNchildren,
-            &children,
-            XmNnumChildren,
-            &nc,
-            NULL);
-
-    for(int i=0;i<nc;i++) {
-        XtDestroyWidget(children[i]);
-    }
-    
-    UiList *list = view->var->value;
-    
-    void *data = list->first(list);
-    int width = 0;
-    while(data) {
-        int w = ui_add_icon_gadget(view->widget, view->model, data);
-        if(w > width) {
-            width = w;
-        }
-        data = list->next(list);
-    }
-    
-}
-
-#define UI_COL_CHAR_WIDTH 12
-
-int ui_add_icon_gadget(Widget container, UiModel *model, void *data) {
-    int width = 50;
-    
-    if(model->columns == 0) {
-        return width;
-    }
-    
-    XmString label = NULL;
-    Arg args[8];
-    Boolean f;
-    // first column
-    if(model->types[0] != 12345678) { // TODO: icon/label type
-        char *str = ui_type_to_string(
-                model->types[0],
-                model->getvalue(data, 0),
-                &f);
-        
-        // column width
-        width += strlen(str) * UI_COL_CHAR_WIDTH;
-        
-        
-        XmString label = XmStringCreateLocalized(str);
-        XtSetArg(args[0], XmNlabelString, label);
-        if(f) {
-            free(str);
-        }
-    } else {
-        // TODO
-    }
-            
-    // remaining columns are the icon gadget details
-    XmStringTable details = (XmStringTable)XtMalloc(
-            (model->columns - 1) * sizeof(XmString));
-    for(int i=1;i<model->columns;i++) {
-        char *str = ui_type_to_string(
-                model->types[i],
-                model->getvalue(data, i),
-                &f);
-        
-        // column width
-        width += strlen(str) * UI_COL_CHAR_WIDTH;
-        
-        details[i - 1] = XmStringCreateLocalized(str);
-        if(f) {
-            free(str);
-        }
-    }
-    XtSetArg(args[1], XmNdetail, details);
-    XtSetArg(args[2], XmNdetailCount, model->columns - 1);
-    XtSetArg(args[3], XmNshadowThickness, 0); 
-    // create widget
-    Widget item = XmCreateIconGadget(container, "table_item", args, 4);
-    XtManageChild(item);
-    
-    // cleanup
-    XmStringFree(label);
-    for(int i=0;i<model->columns-1;i++) {
-        XmStringFree(details[i]);
-    }
-    XtFree((char*)details);
-    
-    return width;
-}
-
-char* ui_type_to_string(UiModelType type, void *data, Boolean *free) {
-    switch(type) {
-        case UI_STRING: *free = FALSE; return data;
-        case UI_INTEGER: {
-            *free = TRUE;
-            int *val = data;
-            sstr_t str = ucx_asprintf(ucx_default_allocator(), "%d", *val);
-            return str.ptr;
-        }
-    }
-    *free = FALSE;
-    return NULL;
-}
-
-void ui_table_action_callback(
-        Widget widget,
-        UiTreeEventData *event,
-        XmContainerSelectCallbackStruct *sel)
-{ 
-    UiListSelection *selection = ui_list_selection(sel);
-    
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    event->activate(&e, event->userdata);
-    
-    free(event->last_selection->rows);
-    free(event->last_selection);
-    event->last_selection = selection;
-}
-
-void ui_table_select_callback(
-        Widget widget,
-        UiTreeEventData *event,
-        XmContainerSelectCallbackStruct *sel)
-{ 
-    UiListSelection *selection = ui_list_selection(sel);
-    if(!ui_compare_list_selection(selection, event->last_selection)) {
-        UiEvent e;
-        e.obj = event->obj;
-        e.window = event->obj->window;
-        e.document = event->obj->ctx->document;
-        e.eventdata = selection;
-        e.intval = selection->count > 0 ? selection->rows[0] : -1;
-        event->selection(&e, event->userdata);
-    }
-    if(event->last_selection) {
-        free(event->last_selection->rows);
-        free(event->last_selection);
-    }
-    event->last_selection = selection;
-}
-
-UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs) {
-    UiListSelection *selection = malloc(sizeof(UiListSelection));
-    selection->count = xs->selected_item_count;
-    selection->rows = calloc(selection->count, sizeof(int));
-    for(int i=0;i<selection->count;i++) {
-        int index;
-        XtVaGetValues(xs->selected_items[i], XmNpositionIndex, &index, NULL);
-        selection->rows[i] = index;
-    }
-    return selection;
-}
-
-Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2) {
-    if(!s1 || !s2) {
-        return FALSE;
-    } 
-    if(s1->count != s2->count) {
-        return FALSE;
-    }
-    for(int i=0;i<s1->count;i++) {
-        if(s1->rows[i] != s2->rows[i]) {
-            return FALSE;
-        }
-    }
-    return TRUE;
-}
--- a/ui/motif/tree.h	Sun May 23 09:44:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 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 TREE_H
-#define	TREE_H
-
-#include "../ui/tree.h"
-#include "../common/context.h"
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-typedef struct UiTreeEventData {
-    UiObject        *obj;
-    ui_callback     activate;
-    ui_callback     selection;
-    void            *userdata;
-    UiListSelection *last_selection;
-} UiTreeEventData;    
-
-typedef struct UiTableView {
-    Widget      widget;
-    UiVar       *var;
-    UiModel     *model;
-} UiTableView;
-
-void ui_table_update(UiEvent *event, UiTableView *view);
-int ui_add_icon_gadget(Widget container, UiModel *model, void *data);
-char* ui_type_to_string(UiModelType type, void *data, Boolean *free);
-
-void ui_table_action_callback(
-        Widget widget,
-        UiTreeEventData *event,
-        XmContainerSelectCallbackStruct *sel);
-void ui_table_select_callback(
-        Widget widget,
-        UiTreeEventData *event,
-        XmContainerSelectCallbackStruct *sel);
-
-UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs);
-
-Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2);
-
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* TREE_H */
-
--- a/ui/motif/window.c	Sun May 23 09:44:43 2021 +0200
+++ b/ui/motif/window.c	Sat Jan 04 16:38:48 2025 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * 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:
@@ -36,6 +36,10 @@
 #include "../ui/window.h"
 #include "../common/context.h"
 
+#include "Grid.h"
+
+#include <cx/mempool.h>
+
 static int nwindows = 0;
 
 static int window_default_width = 600;
@@ -43,17 +47,7 @@
 
 static void window_close_handler(Widget window, void *udata, void *cdata) {
     UiObject *obj = udata;
-    UiEvent ev;
-    ev.window = obj->window;
-    ev.document = obj->ctx->document;
-    ev.obj = obj;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    if(obj->ctx->close_callback) {
-        obj->ctx->close_callback(&ev, obj->ctx->close_data);
-    }
-    // TODO: free UiObject
+    uic_object_destroy(obj);
     
     nwindows--;
     if(nwindows == 0) {
@@ -61,31 +55,30 @@
     }
 }
 
-static UiObject* create_window(char *title, void *window_data, UiBool simple) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));
+
+static UiObject* create_window(const char *title, void *window_data, Boolean simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    const CxAllocator *a = mp->allocator;
+    UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     
     Arg args[16];
     int n = 0;
-    
-    XtSetArg(args[0], XmNtitle, title);
-    //XtSetArg(args[1], XmNbaseWidth, window_default_width);
-    //XtSetArg(args[2], XmNbaseHeight, window_default_height);
-    XtSetArg(args[1], XmNminWidth, 100);
-    XtSetArg(args[2], XmNminHeight, 50);
-    XtSetArg(args[3], XmNwidth, window_default_width);
-    XtSetArg(args[4], XmNheight, window_default_height);
+    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++;
     
     Widget toplevel = XtAppCreateShell(
-            "Test123",
-            "abc",
+            ui_appname(),
+            "mainwindow",
             //applicationShellWidgetClass,
             vendorShellWidgetClass,
-            ui_get_display(),
+            ui_motif_get_display(),
             args,
-            5);
+            n);
     
     Atom wm_delete_window;
     wm_delete_window = XmInternAtom(
@@ -98,111 +91,43 @@
             window_close_handler,
             obj);
     
-    // TODO: use callback
-    ui_set_active_window(toplevel);
-    
     Widget window = XtVaCreateManagedWidget(
             title,
             xmMainWindowWidgetClass,
             toplevel,
             NULL);
-    obj->widget = window;
-    Widget form = XtVaCreateManagedWidget(
-            "window_form",
-            xmFormWidgetClass,
-            window,
-            NULL);
-    Widget toolbar = NULL;
     
+    // menu
     if(!simple) {
-        ui_create_menubar(obj);
-        toolbar = ui_create_toolbar(obj, form);
+        ui_create_menubar(obj, window);
     }
     
-    // window content
-    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    XtSetArg(args[1], XmNshadowThickness, 0);
-    XtSetArg(args[2], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    if(toolbar) {
-        XtSetArg(args[5], XmNtopAttachment, XmATTACH_WIDGET);
-        XtSetArg(args[6], XmNtopWidget, toolbar);
-        n = 7;
-    } else {
-        XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
-        n = 6;
-    }
-    Widget frame = XmCreateFrame(form, "content_frame", args, n);
+    // content frame
+    n = 0;
+    Widget frame = XmCreateFrame(window, "window_frame", args, n);
     XtManageChild(frame);
     
-    Widget content_form = XmCreateForm(frame, "content_form", NULL, 0);
-    XtManageChild(content_form);
-    obj->container = ui_box_container(obj, content_form, 0, 0, UI_BOX_VERTICAL);
+    Widget form = XmCreateForm(frame, "window_form", args, 0);
+    XtManageChild(form);
     
-    XtManageChild(form);
-      
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, form, args, n);
+    UiContainerX *container = ui_box_container(obj, vbox, UI_BOX_VERTICAL);
+    uic_object_push_container(obj, container);
+    
     obj->widget = toplevel;
     nwindows++;
     return obj;
-}
+} 
 
-UiObject* ui_window(char *title, void *window_data) {
+UiObject* ui_window(const char *title, void *window_data) {
     return create_window(title, window_data, FALSE);
 }
 
-UiObject* ui_simplewindow(char *title, void *window_data) {
+UiObject* ui_simple_window(const char *title, void *window_data) {
     return create_window(title, window_data, TRUE);
 }
-
-void ui_close(UiObject *obj) {
-    XtDestroyWidget(obj->widget);
-    window_close_handler(obj->widget, obj, NULL);
-}
-
-typedef struct FileDialogData {
-    int  running;
-    char *file;
-} FileDialogData;
-
-static void filedialog_select(
-        Widget widget,
-        FileDialogData *data,
-        XmFileSelectionBoxCallbackStruct *selection)
-{
-    char *path = NULL;
-    XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &path);
-    data->running = 0;
-    data->file = strdup(path);
-    XtFree(path);
-    XtUnmanageChild(widget);
-}
-
-static void filedialog_cancel(
-        Widget widget,
-        FileDialogData *data,
-        XmFileSelectionBoxCallbackStruct *selection)
-
-{
-    data->running = 0;
-    XtUnmanageChild(widget);
-}
-
-char* ui_openfiledialog(UiObject *obj) {
-    Widget dialog = XmCreateFileSelectionDialog(obj->widget, "openfiledialog", NULL, 0);
-    XtManageChild(dialog);
-    
-    FileDialogData data;
-    data.running = 1;
-    data.file = NULL;
-    
-    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, &data);
-    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, &data);
-    
-    ui_secondary_event_loop(&data.running);
-    return data.file;
-}
-
-char* ui_savefiledialog(UiObject *obj) {
-    return ui_openfiledialog(obj);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/window.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+
--- a/ui/ui/button.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/button.h	Sat Jan 04 16:38:48 2025 +0100
@@ -34,14 +34,64 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+
+typedef struct UiButtonArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    UiLabelType labeltype;
+    ui_callback onclick;
+    void* onclickdata;
     
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data);
+    const int* groups;
+} UiButtonArgs;
 
-UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value);
-UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname);
+typedef struct UiToggleArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    UiLabelType labeltype;
+    UiInteger* value;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+    int enable_group;
+    
+    const int* groups;
+} UiToggleArgs;
+ 
+#define ui_button(obj, ...) ui_button_create(obj, (UiButtonArgs){ __VA_ARGS__ } )
+#define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_checkbox(obj, ...) ui_checkbox_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_switch(obj, ...) ui_switch_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup);
-UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname);
+UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args);
+UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args);
+
 
 
 #ifdef	__cplusplus
--- a/ui/ui/container.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/container.h	Sat Jan 04 16:38:48 2025 +0100
@@ -34,54 +34,262 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
+   
+typedef enum UiSubContainerType {
+    UI_CONTAINER_VBOX = 0,
+    UI_CONTAINER_HBOX,
+    UI_CONTAINER_GRID,
+    UI_CONTAINER_NO_SUB
+} UiSubContainerType;
+
+typedef enum UiTabViewType {
+    UI_TABVIEW_DEFAULT = 0,
+    UI_TABVIEW_DOC,
+    UI_TABVIEW_NAVIGATION_SIDE,
+    UI_TABVIEW_NAVIGATION_TOP,
+    UI_TABVIEW_NAVIGATION_TOP2,
+    UI_TABVIEW_INVISIBLE
+} UiTabViewType;
+
+typedef enum UiHeaderbarAlternative {
+    UI_HEADERBAR_ALTERNATIVE_DEFAULT = 0,
+    UI_HEADERBAR_ALTERNATIVE_TOOLBAR,
+    UI_HEADERBAR_ALTERNATIVE_BOX
+} UiHeaderbarAlternative;
+
+typedef struct UiContainerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+} UiContainerArgs;
+
+typedef struct UiFrameArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiSubContainerType subcontainer;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    const char* label;
+    UiBool isexpanded;
+} UiFrameArgs;
+
+typedef struct UiTabViewArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiTabViewType tabview;
+
+    UiSubContainerType subcontainer;
     
-#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_VBOX(obj) for(ui_vbox(obj);ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_HBOX(obj) for(ui_hbox(obj);ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_VBOX_SP(obj, margin, spacing) for(ui_vbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_HBOX_SP(obj, margin, spacing) for(ui_hbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_GRID(obj) for(ui_grid(obj);ui_container_finish(obj);ui_container_begin_close(obj))
-#define UI_GRID_SP(obj, margin, columnspacing, rowspacing) for(ui_grid_sp(obj,margin,columnspacing,rowspacing);ui_container_finish(obj);ui_container_begin_close(obj))
+    UiInteger *value;
+    const char* varname;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    const char* label;
+    UiBool isexpanded;
+} UiTabViewArgs;
+
+typedef struct UiHeaderbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
     
-void ui_end(UiObject *obj);
+    UiBool showtitle;
+    UiBool showwindowbuttons;
     
-UIWIDGET ui_vbox(UiObject *obj);
-UIWIDGET ui_hbox(UiObject *obj);
-UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing);
-UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing);
+    UiHeaderbarAlternative alternative;
+    int alt_spacing;
+} UiHeaderbarArgs;
+
+typedef struct UiSidebarArgs {
+    const char *name;
+    const char *style_class;
+    int margin;
+    int spacing;
+} UiSidebarArgs;
 
-UIWIDGET ui_grid(UiObject *obj);
-UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing);
+typedef struct UiItemListContainerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    int margin;
+    int spacing;
+    
+    int sub_margin;
+    int sub_spacing;
+    int sub_columnspacing;
+    int sub_rowspacing;
+    
+    UiList *value;
+    const char *varname;
+    /*
+     * void create_ui(UiObject *obj, int index, void *elm, void *userdata)
+     * 
+     * UI constructor for each list element
+     * 
+     * This callback is executed for each list element. A new UiObject is
+     * created for every item.
+     */
+    void (*create_ui)(UiObject *, int, void *, void *);
+    void *userdata;
+    
+    /*
+     * ItemList container type
+     * Only UI_CONTAINER_VBOX or UI_CONTAINER_HBOX are supported
+     */
+    UiSubContainerType container;
+    
+    /*
+     * item root container
+     */
+    UiSubContainerType subcontainer;
+} UiItemListContainerArgs;
 
-UIWIDGET ui_scrolledwindow(UiObject *obj);
+struct UiContainerX {
+    void *container;
+    int close;
+    UiContainerX *prev;
+    UiContainerX *next;
+};
+
+
+#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
 
-UIWIDGET ui_sidebar(UiObject *obj);
+#define ui_vbox(obj, ...) for(ui_vbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox(obj, ...) for(ui_hbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid(obj, ...) for(ui_grid_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame(obj, ...) for(ui_frame_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander(obj, ...) for(ui_expander_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow(obj, ...) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview(obj, ...) for(ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_sidebar(obj, ...) for(ui_sidebar_create(obj, (UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid0(obj) for(ui_grid_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame0(obj) for(ui_frame_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander0(obj) for(ui_expande_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview0(obj) for(ui_tabview_create(obj, (UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_sidebar0(obj) for(ui_sidebar_create(obj, (UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
 
-UIWIDGET ui_hsplitpane(UiObject *obj, int max);
-UIWIDGET ui_vsplitpane(UiObject *obj, int max);
+#define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_itemlist(obj, ...) ui_itemlist_create(obj, (UiItemListContainerArgs) { __VA_ARGS__} )
+
+UIEXPORT void ui_end(UiObject *obj); // deprecated
+UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end
+    
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs args);
 
-UIWIDGET ui_tabview(UiObject *obj);
-void ui_tab(UiObject *obj, char *title);
-void ui_select_tab(UIWIDGET tabview, int tab);
+UIEXPORT UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args);
+UIEXPORT void ui_tab_create(UiObject *obj, const char* title);
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab);
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab);
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index);
+
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args);
+UIEXPORT void ui_headerbar_start_create(UiObject *obj);
+UIEXPORT void ui_headerbar_center_create(UiObject *obj);
+UIEXPORT void ui_headerbar_end_create(UiObject *obj);
+
+UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args);
+
+UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args);
+
+UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
+UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
+
 
 // box container layout functions
-void ui_layout_fill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
 // grid container layout functions
-void ui_layout_hexpand(UiObject *obj, UiBool expand);
-void ui_layout_vexpand(UiObject *obj, UiBool expand);
-void ui_layout_width(UiObject *obj, int width);
-void ui_layout_gridwidth(UiObject *obj, int width);
-void ui_newline(UiObject *obj);
+UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_hfill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_width(UiObject *obj, int width);
+UIEXPORT void ui_layout_height(UiObject* obj, int width);
+UIEXPORT void ui_layout_colspan(UiObject *obj, int cols);
+UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows);
+UIEXPORT void ui_newline(UiObject *obj);
 
-
-UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
-
-UiObject* ui_document_tab(UiTabbedPane *view);
+// TODO
+UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
+UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
 
 
 /* used for macro */
-void ui_container_begin_close(UiObject *obj);
-int ui_container_finish(UiObject *obj);
+UIEXPORT void ui_container_begin_close(UiObject *obj);
+UIEXPORT int ui_container_finish(UiObject *obj);
+
+#define UI_APPLY_LAYOUT1(obj, args) \
+    if(args.fill != UI_DEFAULT) ui_layout_fill(obj, args.fill == UI_ON ? 1 : 0 ); \
+    if(args.hexpand) ui_layout_hexpand(obj, 1); \
+    if(args.vexpand) ui_layout_vexpand(obj, 1); \
+    if(args.hfill) ui_layout_hfill(obj, 1); \
+    if(args.vfill) ui_layout_vfill(obj, 1); \
+    if(args.colspan > 0) ui_layout_colspan(obj, args.colspan); \
+    if(args.rowspan > 0) ui_layout_rowspan(obj, args.rowspan); \
+    /*force caller to add ';'*/(void)0
 
 
 #ifdef __cplusplus
--- a/ui/ui/display.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/display.h	Sat Jan 04 16:38:48 2025 +0100
@@ -39,16 +39,94 @@
 extern "C" {
 #endif
     
+enum UiAlignment {
+    UI_ALIGN_DEFAULT = 0,
+    UI_ALIGN_LEFT,
+    UI_ALIGN_RIGHT,
+    UI_ALIGN_CENTER
+};
+
+typedef enum UiAlignment UiAlignment;
+
+enum UiLabelStyle {
+    UI_LABEL_STYLE_DEFAULT = 0,
+    UI_LABEL_STYLE_TITLE,
+    UI_LABEL_STYLE_SUBTITLE,
+    UI_LABEL_STYLE_DIM
+};
+
+typedef enum UiLabelStyle UiLabelStyle;
+
+typedef struct UiLabelArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    const char* label;
+    UiAlignment align;
+    UiLabelStyle style;
+    UiString* value;
+    const char* varname;
+} UiLabelArgs;
+
+typedef struct UiProgressbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
+
+    double min;
+    double max;
+    UiDouble* value;
+    const char* varname;
+} UiProgressbarArgs;
+
+typedef struct UiProgressbarSpinnerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+
+    UiInteger* value;
+    const char* varname;
+} UiProgressbarSpinnerArgs;
+
 /* label widgets */
-UIWIDGET ui_label(UiObject *obj, char *label);
-UIWIDGET ui_llabel(UiObject *obj, char *label);
-UIWIDGET ui_rlabel(UiObject *obj, char *label);
-UIWIDGET ui_space(UiObject *obj);
-UIWIDGET ui_separator(UiObject *obj);
+
+#define ui_label(obj, ...) ui_label_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_llabel(obj, ...) ui_llabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_rlabel(obj, ...) ui_rlabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+
+
+UIEXPORT UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args);
 
-/* progress bar */
-UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value);
-UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname);
+UIWIDGET ui_space_deprecated(UiObject *obj);
+UIWIDGET ui_separator_deprecated(UiObject *obj);
+
+/* progress bar/spinner */
+
+#define ui_progressbar(obj, ...) ui_progressbar_create(obj, (UiProgressbarArgs) { __VA_ARGS__ } )
+#define ui_progressspinner(obj, ...) ui_progressspinner_create(obj, (UiProgressbarSpinnerArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args);
+UIEXPORT UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args);
 
 
 #ifdef __cplusplus
--- a/ui/ui/dnd.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/dnd.h	Sat Jan 04 16:38:48 2025 +0100
@@ -37,11 +37,16 @@
     
 #define UI_DND_FILE_TARGET "XdndDirectSave0"
     
-void ui_selection_settext(UiSelection *sel, char *str, int len);
-void ui_selection_seturis(UiSelection *sel, char **uris, int nelm);
+UIEXPORT void ui_selection_settext(UiDnD *sel, char *str, int len);
+UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm);
 
-char* ui_selection_gettext(UiSelection *sel);
-char** ui_selection_geturis(UiSelection *sel, size_t *nelm);
+UIEXPORT char* ui_selection_gettext(UiDnD *sel);
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel);
+
+UIEXPORT UiDnDAction ui_dnd_result(UiDnD *dnd);
+UIEXPORT UiBool ui_dnd_need_delete(UiDnD *dnd);
+
+UIEXPORT void ui_dnd_accept(UiDnD *dnd, UiBool accept);
 
 
 #ifdef __cplusplus
--- a/ui/ui/entry.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/entry.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,13 +35,35 @@
 extern "C" {
 #endif
 
-UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i);
-UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d);
-UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r);
+
+typedef struct UiSpinnerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
 
-UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname);
-UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname);
-UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname);
+    double step;
+    int digits;
+    UiInteger *intvalue;
+    UiDouble* doublevalue;
+    UiRange *rangevalue;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+    
+    const int *groups;
+} UiSpinnerArgs;
+
+
+    
+UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args);
+
+#define ui_spinner(obj, ...) ui_spinner_create(obj, (UiSpinnerArgs){ __VA_ARGS__ } )
 
 void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
 void ui_spinner_setdigits(UIWIDGET spinner, int digits);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/icons.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#ifndef UI_ICONS_H
+#define UI_ICONS_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef UI_GTK
+    
+#define UI_ICON_HOME "go-home"
+#define UI_ICON_NEW_WINDOW "list-add"
+#define UI_ICON_REFRESH "view-refresh"
+#define UI_ICON_NEW_FOLDER "folder-new"
+#define UI_ICON_ADD "document-new"
+#define UI_ICON_UPLOAD "document-open"
+#define UI_ICON_SAVE_LOCAL "document-save-as"
+#define UI_ICON_DELETE "edit-delete"
+#define UI_ICON_DOCK_LEFT ""
+#define UI_ICON_DOCK_RIGHT ""
+#define UI_ICON_GO_BACK "go-previous"
+#define UI_ICON_GO_FORWARD "go-next"
+    
+#endif /* UI_GTK */
+    
+    
+
+#ifdef UI_WINUI
+    
+#define UI_ICON_HOME "Home"
+#define UI_ICON_NEW_WINDOW "NewWindow"
+#define UI_ICON_REFRESH "Refresh"
+#define UI_ICON_NEW_FOLDER "NewFolder"
+#define UI_ICON_ADD "Add"
+#define UI_ICON_UPLOAD "Upload"
+#define UI_ICON_SAVE_LOCAL "SaveLocal"
+#define UI_ICON_DELETE "Delete"
+#define UI_ICON_DOCK_LEFT "DockLeft"
+#define UI_ICON_DOCK_RIGHT "DockRight"
+#define UI_ICON_GO_BACK "Back"
+#define UI_ICON_GO_FORWARD "Forward"
+    
+#endif /* UI_WINUI */
+    
+    
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
+UIEXPORT UiIcon* ui_icon_unscaled(const char *name, int size);
+UIEXPORT UiIcon* ui_imageicon(const char* file);
+UIEXPORT void ui_icon_free(UiIcon* icon);
+
+UIEXPORT UiIcon* ui_foldericon(size_t size);
+UIEXPORT UiIcon* ui_fileicon(size_t size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ICONS_H */
+
--- a/ui/ui/image.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/image.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,16 +35,31 @@
 extern "C" {
 #endif
 
-UiIcon* ui_icon(const char *name, int size);
-UiIcon* ui_icon_unscaled(const char *name, int size);
-void ui_free_icon(UiIcon *icon);
+#define UI_IMAGE_OBJECT_TYPE "image"
+    
+typedef struct UiImageViewerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
 
-UiImage* ui_icon_image(UiIcon *icon);
-UiImage* ui_image(const char *filename);
-UiImage* ui_named_image(const char *filename, const char *name);
-UiImage* ui_load_image_from_path(const char *path, const char *name);
-void ui_free_image(UiImage *img);
+    UiBool scrollarea;
+    UiBool autoscale;
+    UiGeneric *value;
+    const char *varname;
+    UiMenuBuilder *contextmenu;
+} UiImageViewerArgs;
+    
+#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
+    
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args);
 
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path);
 
 #ifdef __cplusplus
 }
--- a/ui/ui/menu.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/menu.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,36 +35,74 @@
 extern "C" {
 #endif
 
-/*
- * application menu functions
- */
-void ui_menu(char *label);
-void ui_submenu(char *label);
-void ui_submenu_end();
+
+typedef struct UiMenuItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	ui_callback onclick;
+	void* onclickdata;
+
+	const int* groups;
+} UiMenuItemArgs;
+
+typedef struct UiMenuToggleItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	const char* varname;
+	ui_callback onchange;
+	void* onchangedata;
+
+	const int* groups;
+} UiMenuToggleItemArgs;
 
-void ui_menuitem(char *label, ui_callback f, void *userdata);
-void ui_menuitem_st(char *stockid, ui_callback f, void *userdata);
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...);
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...);
+typedef struct UiMenuItemListArgs {
+	const char* varname;
+	ui_getvaluefunc getvalue;
+	ui_callback onselect;
+	void* onselectdata;
+        UiBool addseparator;
+} UiMenuItemListArgs;
+
+#define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close())
 
-void ui_menuseparator();
+#define ui_menuitem(...) ui_menuitem_create((UiMenuItemArgs){ __VA_ARGS__ })
+#define ui_menu_toggleitem(...) ui_menu_toggleitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+#define ui_menu_radioitem(...) ui_menu_radioitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+#define ui_menu_itemlist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS__ } )
+#define ui_menu_togglelist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} )
+#define ui_menu_radiolist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} )
 
-void ui_checkitem(char *label, ui_callback f, void *userdata);
-void ui_checkitem_nv(char *label, char *vname);
+UIEXPORT void ui_menu_create(const char* label);
+UIEXPORT void ui_menuitem_create(UiMenuItemArgs args);
+UIEXPORT void ui_menu_toggleitem_create(UiMenuToggleItemArgs args);
+UIEXPORT void ui_menu_radioitem_create(UiMenuToggleItemArgs args);
 
-void ui_menuitem_list(UiList *items, ui_callback f, void *userdata);
+UIEXPORT void ui_menuseparator();
+
+UIEXPORT void ui_menu_itemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_toggleitemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_radioitemlist_create(UiMenuItemListArgs args);
+
+UIEXPORT void ui_menu_end(void); // TODO: private
 
 /*
  * widget menu functions
  */
-UIMENU ui_contextmenu(UiObject *obj);
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget);
-void ui_contextmenu_popup(UIMENU menu);
+
+#define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close())
 
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata);
-void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata);
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...);
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...);
+UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder);
+UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder);
+UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget);
+UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y);
+
+// used for macro
+UIEXPORT void ui_menu_close(void);
+UIEXPORT int ui_menu_is_open(void);
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/properties.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/properties.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,15 +35,11 @@
 extern "C" {
 #endif
 
-typedef struct UcxMap UiProperties;
-    
-char* ui_getappdir();
-char* ui_configfile(char *name);
+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);
 
-char* ui_get_property(char *name);
-void  ui_set_property(char *name, char *value);
-
-void ui_set_default_property(char *name, char *value);
+int ui_properties_store(void);
 
 void ui_locales_dir(char *path);
 void ui_pixmaps_dir(char *path);
--- a/ui/ui/text.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/text.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,28 +35,106 @@
 extern "C" {
 #endif
 
-UIWIDGET ui_textarea(UiObject *obj, UiText *value);
-UIWIDGET ui_textarea_nv(UiObject *obj, char *varname);
-
-UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+typedef struct UiTextAreaArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
 
-void ui_text_undo(UiText *value);
-void ui_text_redo(UiText *value);
+    UiText *value;
+    const char *varname;
+    ui_callback onchange;
+    void *onchangedata;
+    
+    const int *groups;
+} UiTextAreaArgs;
+    
+typedef struct UiTextFieldArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
 
-UIWIDGET ui_textfield(UiObject *obj, UiString *value);
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname);
+    UiString* value;
+    const char *varname;
+    ui_callback onchange;
+    void *onchangedata;
+    ui_callback onactivate;
+    void *onactivatedata;
+    
+    const int *groups;
+} UiTextFieldArgs;
+
+typedef struct UiPathElmRet {
+    char *name;
+    size_t name_len;
+    char *path;
+    size_t path_len;
+} UiPathElm;
 
-UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value);
-UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname);
+typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void *data);
+
+
 
-UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value);
-UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname);
+typedef struct UiPathTextFieldArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiString *value;
+    const char *varname;
+
+    ui_pathelm_func getpathelm;
+    void *getpathelmdata;
 
-UIWIDGET ui_passwordfield(UiObject *obj, UiString *value);
-UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname);
-UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value);
-UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname);
+    ui_callback onactivate;
+    void *onactivatedata;
+    
+    ui_callback ondragstart;
+    void *ondragstartdata;
+    ui_callback ondragcomplete;
+    void *ondragcompletedata;
+    ui_callback ondrop;
+    void *ondropsdata;
+} UiPathTextFieldArgs;
+
+#define ui_textarea(obj, ...) ui_textarea_create(obj, (UiTextAreaArgs) { __VA_ARGS__ })
+
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args);
 
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+
+UIEXPORT void ui_text_undo(UiText *value);
+UIEXPORT void ui_text_redo(UiText *value);
+
+#define ui_textfield(obj, ...) ui_textfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_frameless_textfield(obj, ...) ui_frameless_field_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_passwordfield(obj, ...) ui_passwordfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_path_textfield(obj, ...) ui_path_textfield_create(obj, (UiPathTextFieldArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args);
+
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args);
         
 #ifdef	__cplusplus
 }
--- a/ui/ui/toolbar.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/toolbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -36,23 +36,55 @@
 extern "C" {
 #endif
 
-void ui_toolitem(char *name, char *label, ui_callback f, void *udata);
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata);
-void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *udata);
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...);
-void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...);
-void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata);
+typedef struct UiToolbarItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	ui_callback onclick;
+	void* onclickdata;
+        
+        const int *groups;
+} UiToolbarItemArgs;
+
+typedef struct UiToolbarToggleItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	const char* varname;
+	ui_callback onchange;
+	void* onchangedata;
+        
+        const int *groups;
+} UiToolbarToggleItemArgs;
 
-void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i);
-void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i);
-void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar);
-void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar);
+typedef struct UiToolbarMenuArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+} UiToolbarMenuArgs;
+
+enum UiToolbarPos {
+	UI_TOOLBAR_LEFT = 0,
+	UI_TOOLBAR_CENTER,
+	UI_TOOLBAR_RIGHT
+};
 
-void ui_toolbar_combobox(char *name, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-void ui_toolbar_combobox_str(char *name, UiList *list, ui_callback f, void *udata);
-void ui_toolbar_combobox_nv(char *name, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+#define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, (UiToolbarItemArgs){ __VA_ARGS__ } )
+#define ui_toolbar_toggleitem(name, ...) ui_toolbar_toggleitem_create(name, (UiToolbarToggleItemArgs){ __VA_ARGS__ } )
+
+#define ui_toolbar_menu(name, ...) for(ui_toolbar_menu_create(name, (UiToolbarMenuArgs){ __VA_ARGS__ });ui_menu_is_open();ui_menu_close())
+#define ui_toolbar_appmenu() for(ui_toolbar_menu_create(NULL, (UiToolbarMenuArgs){ 0 });ui_menu_is_open();ui_menu_close())
+
 
-void ui_toolbar_add_default(char *name);
+UIEXPORT void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args);
+UIEXPORT void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args);
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args);
+
+UIEXPORT void ui_toolbar_add_default(const char *name, enum UiToolbarPos pos);
+
+
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/toolkit.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/toolkit.h	Sat Jan 04 16:38:48 2025 +0100
@@ -33,22 +33,32 @@
 
 #ifdef UI_COCOA
 
+#include <stdlib.h>
 #ifdef __OBJC__
 #import <Cocoa/Cocoa.h>
-#define UIWIDGET NSView*
-#define UIMENU   NSMenu*
-#else
-typedef void* UIWIDGET;
-typedef void* UIMENU;
 #endif
+typedef void* UIWIDGET; // NSView*
+typedef void* UIWINDOW; // NSWindow*
+typedef void* UIMENU;   // NSMenu*
 
-#elif UI_GTK2 || UI_GTK3
+#elif UI_GTK2 || UI_GTK3 || UI_GTK4
 
 #include <gtk/gtk.h>
 #define UIWIDGET GtkWidget*
+
+#if UI_GTK2 || UI_GTK3
 #define UIMENU   GtkMenu*
+#endif
+#ifdef UI_GTK4
+#define UIMENU   GtkPopoverMenu*
+#endif
+
 #define UI_GTK
 
+#ifdef UI_LIBADWAITA
+#include <adwaita.h>
+#endif
+
 #elif UI_MOTIF
 
 #include <Xm/XmAll.h> 
@@ -67,9 +77,58 @@
 #define UIMENU   void*
 #endif
 
-#elif UI_WPF
+#elif UI_WINUI
+
+#define UIEXPORT __declspec(dllexport) 
+
+#ifdef __cplusplus
+
+#include <functional>
+#ifndef UI_WINUI_PCH
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#endif
+
+class UiWindow {
+public:
+    winrt::Microsoft::UI::Xaml::Window window { nullptr };
+
+    UiWindow(winrt::Microsoft::UI::Xaml::Window& win);
+};
+
+class UiWidget {
+public:
+    winrt::Microsoft::UI::Xaml::UIElement uielement;
+    void* data1 = nullptr;
+    void* data2 = nullptr;
+    void* data3 = nullptr;
+    std::function<void()> Show;
+    UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm);
+
+};
+
+#define UIWIDGET UiWidget*
+#define UIWINDOW UiWindow*
+#define UIMENU   void*
+
+/*
+// winrt::Microsoft::UI::Xaml::UIElement
 #define UIWIDGET void*
+// winrt::Microsoft::UI::Xaml::Window
+#define UIWINDOW void*
 #define UIMENU   void*
+*/
+
+#else
+#define UIWIDGET void*
+#define UIWINDOW void*
+#define UIMENU   void*
+#endif
+
+
 #endif
 
 #ifndef TRUE
@@ -79,16 +138,23 @@
 #define FALSE 0
 #endif
 
+#ifndef UIEXPORT
+#define UIEXPORT
+#endif
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
 #define UI_GROUP_SELECTION  20000
+
+#define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 }
     
 /* public types */
-typedef int UiBool;
+typedef _Bool UiBool;
 
 typedef struct UiObject     UiObject;
+typedef struct UiContainerX UiContainerX;
 typedef struct UiEvent      UiEvent;
 typedef struct UiMouseEvent UiMouseEvent;
 typedef struct UiObserver   UiObserver;
@@ -99,24 +165,39 @@
 typedef struct UiText       UiText;
 typedef struct UiList       UiList;
 typedef struct UiRange      UiRange;
+typedef struct UiGeneric    UiGeneric;
 
 typedef struct UiStr        UiStr;
 
-/* begin opaque types */
-typedef struct UiContext    UiContext;
-typedef struct UiContainer  UiContainer;
+typedef struct UiFileList   UiFileList;
+
+typedef struct UiListSelection UiListSelection;
 
-typedef struct UiIcon       UiIcon;
-typedef struct UiImage      UiImage;
+/* begin opaque types */
+typedef struct UiContext     UiContext;
+typedef struct UiContainer   UiContainer;
+typedef struct UiMenuBuilder UiMenuBuilder;
 
-typedef struct UiSelection  UiSelection;
+typedef struct UiIcon        UiIcon;
+typedef struct UiImage       UiImage;
+
+typedef struct UiDnD         UiDnD;
+
+typedef struct UiThreadpool  UiThreadpool;
 /* end opaque types */
 
 typedef struct UiTabbedPane UiTabbedPane;
 
+typedef enum UiTri UiTri;
+typedef enum UiLabelType UiLabelType;
+
+typedef enum UiDnDAction UiDnDAction;
+
 enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
 
+enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON };
 
+enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM };
   
 typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
 
@@ -126,13 +207,20 @@
 
 typedef void(*ui_freefunc)(void*);
 
-typedef void(*ui_enablefunc)(void*, UiBool);
+typedef void(*ui_enablefunc)(void*, int);
 
 struct UiObject {
     /*
      * native widget
      */
     UIWIDGET    widget;
+
+#if defined(UI_COCOA) || defined(UI_WINUI)
+    /*
+     * native window object 
+     */
+    UIWINDOW    wobj;
+#endif
     
     /*
      * user window data
@@ -145,14 +233,31 @@
     UiContext   *ctx;
     
     /*
-     * container interface
+     * container interface (deprecated)
      */
     UiContainer *container;
     
     /*
+     * container list
+     * TODO: remove old UiContainer and rename UiContainerX to UiContainer
+     */
+    UiContainerX *container_begin;
+    UiContainerX *container_end;
+    
+    /*
      * next container object
      */
     UiObject    *next;
+    
+    /*
+     * obj destroy func
+     */
+    void (*destroy)(UiObject *obj);
+    
+    /*
+     * reference counter
+     */
+    unsigned int ref;
 };
 
 struct UiTabbedPane {
@@ -218,7 +323,7 @@
 
 struct UiString {
     char* (*get)(UiString*);
-    void  (*set)(UiString*, char*);
+    void  (*set)(UiString*, const char*);
     void  *obj;
     
     UiStr value;
@@ -226,7 +331,7 @@
 };
 
 struct UiText {
-    void  (*set)(UiText*, char*);
+    void  (*set)(UiText*, const char*);
     char* (*get)(UiText*);
     char* (*getsubstr)(UiText*, int, int); /* text, begin, end */
     void  (*insert)(UiText*, int, char*);
@@ -242,6 +347,17 @@
     // TODO: replacefunc, ...
     UiObserver *observers;
 };
+
+struct UiGeneric {
+    void* (*get)(UiGeneric*);
+    const char* (*get_type)(UiGeneric*);
+    int (*set)(UiGeneric*, void *, const char *type);
+    void *obj;
+    
+    void *value;
+    const char *type;
+    UiObserver *observers;
+};
     
 /*
  * abstract list
@@ -260,8 +376,10 @@
     /* private - implementation dependent */
     void *data;
     
-    /* binding function */
+    /* binding functions */
     void (*update)(UiList *list, int i);
+    UiListSelection (*getselection)(UiList *list);
+    void (*setselection)(UiList *list, UiListSelection selection);
     /* binding object */
     void *obj;
     
@@ -269,6 +387,19 @@
     UiObserver *observers;
 };
 
+
+struct UiListSelection {
+    /*
+    * number of selected items
+    */
+    int count;
+
+    /*
+    * indices of selected rows
+    */
+    int *rows;
+};
+
 struct UiRange {
     double (*get)(UiRange *range);
     void   (*set)(UiRange *range, double value);
@@ -283,87 +414,169 @@
     UiObserver *observers;
 };
 
-
-void ui_init(char *appname, int argc, char **argv);
-char* ui_appname();
+enum UiTri {
+    UI_DEFAULT = 0,
+    UI_ON,
+    UI_OFF
+};
 
-UiContext* ui_global_context(void);
+struct UiFileList {
+    char **files;
+    size_t nfiles;
+};
 
-void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
+typedef struct UiCondVar {
+    void *data;
+    int intdata;
+} UiCondVar;
 
-void ui_onstartup(ui_callback f, void *userdata);
-void ui_onopen(ui_callback f, void *userdata);
-void ui_onexit(ui_callback f, void *userdata);
+
+UIEXPORT void ui_init(const char *appname, int argc, char **argv);
+UIEXPORT const char* ui_appname();
+
+UIEXPORT UiContext* ui_global_context(void);
+
+UIEXPORT void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
 
-void ui_main();
-void ui_show(UiObject *obj);
-void ui_close(UiObject *obj);
+UIEXPORT void ui_context_destroy(UiContext *ctx);
 
-void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd);
+UIEXPORT void ui_object_ref(UiObject *obj);
+UIEXPORT int ui_object_unref(UiObject *obj);
 
-void* ui_document_new(size_t size);
-void  ui_document_destroy(void *doc);
+UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
+UIEXPORT void ui_onopen(ui_callback f, void *userdata);
+UIEXPORT void ui_onexit(ui_callback f, void *userdata);
 
-void ui_set_document(UiObject *obj, void *document);    // deprecated
-void ui_detach_document(UiObject *obj);                 // deprecated
-void* ui_get_document(UiObject *obj);                   // deprecated
-void ui_set_subdocument(void *document, void *sub);     // deprecated
-void ui_detach_subdocument(void *document, void *sub);  // deprecated
-void* ui_get_subdocument(void *document);               // deprecated
+UIEXPORT void ui_main(void);
+UIEXPORT void ui_show(UiObject *obj);
+UIEXPORT void ui_close(UiObject *obj);
+
+UIEXPORT void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd);
+UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td);
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads);
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool);
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd);
 
-UiContext* ui_document_context(void *doc);
+UIEXPORT void* ui_document_new(size_t size);
+UIEXPORT void  ui_document_destroy(void *doc);
+
+UIEXPORT void ui_set_document(UiObject *obj, void *document);    // deprecated
+UIEXPORT void ui_detach_document(UiObject *obj);                 // deprecated
+UIEXPORT void* ui_get_document(UiObject *obj);                   // deprecated
+UIEXPORT void ui_set_subdocument(void *document, void *sub);     // deprecated
+UIEXPORT void ui_detach_subdocument(void *document, void *sub);  // deprecated
+UIEXPORT void* ui_get_subdocument(void *document);               // deprecated
 
-void ui_attach_document(UiContext *ctx, void *document);
-void ui_detach_document2(UiContext *ctx, void *document);
+UIEXPORT UiContext* ui_document_context(void *doc);
 
-void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
+UIEXPORT void ui_attach_document(UiContext *ctx, void *document);
+UIEXPORT void ui_detach_document2(UiContext *ctx, void *document);
+
+UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
 
-void ui_set_group(UiContext *ctx, int group);
-void ui_unset_group(UiContext *ctx, int group);
-int* ui_active_groups(UiContext *ctx, int *ngroups);
+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);
     
-void* ui_malloc(UiContext *ctx, size_t size);
-void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
-void  ui_free(UiContext *ctx, void *ptr);
-void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+UIEXPORT void* ui_allocator(UiContext *ctx);
+UIEXPORT void* ui_cx_mempool(UiContext *ctx);
+
+UIEXPORT void* ui_malloc(UiContext *ctx, size_t size);
+UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
+UIEXPORT void  ui_free(UiContext *ctx, void *ptr);
+UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str);
 
 // types
 
-UiInteger* ui_int_new(UiContext *ctx, char *name);
-UiDouble* ui_double_new(UiContext *ctx, char *name);
-UiString* ui_string_new(UiContext *ctx, char *name);
-UiText* ui_text_new(UiContext *ctx, char *name);
-UiRange* ui_range_new(UiContext *ctx, char *name);
+UIEXPORT UiInteger* ui_int_new(UiContext *ctx, char *name);
+UIEXPORT UiDouble* ui_double_new(UiContext *ctx, char *name);
+UIEXPORT UiString* ui_string_new(UiContext *ctx, char *name);
+UIEXPORT UiText* ui_text_new(UiContext *ctx, char *name);
+UIEXPORT UiRange* ui_range_new(UiContext *ctx, char *name);
+UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name);
+
+#define ui_get(v) _Generic(v, \
+    UiInteger*: ui_int_get, \
+    UiDouble*: ui_double_get, \
+    UiString*: ui_string_get, \
+    UiText*:ui_text_get) (v)
+
+#define ui_set(v, n) _Generic(v, \
+    UiInteger*: ui_int_set, \
+    UiDouble*: ui_double_set, \
+    UiString*: ui_string_set, \
+    UiText*:ui_text_set) (v, n)
 
-UiObserver* ui_observer_new(ui_callback f, void *data);
-UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer);
-UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data);
-void ui_notify(UiObserver *observer, void *data);
-void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data);
-void ui_notify_evt(UiObserver *observer, UiEvent *event);
+UIEXPORT void ui_int_set(UiInteger *i, int64_t value);
+UIEXPORT int64_t ui_int_get(UiInteger *i);
+UIEXPORT void ui_double_set(UiDouble *d, double value);
+UIEXPORT double ui_double_get(UiDouble *d);
+UIEXPORT void ui_string_set(UiString *s, const char *value);
+UIEXPORT char* ui_string_get(UiString *s);
+UIEXPORT void ui_text_set(UiText *s, const char* value);
+UIEXPORT char* ui_text_get(UiText *s);
+
+UIEXPORT void ui_var_set_int(UiContext *ctx, const char *name, int64_t value);
+UIEXPORT int64_t ui_var_get_int(UiContext *ctx, const char *name);
+UIEXPORT void ui_var_set_double(UiContext *ctx, const char *name, double value);
+UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name);
+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 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);
+UIEXPORT void ui_notify(UiObserver *observer, void *data);
+UIEXPORT void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data);
+UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event);
 
 
-UiList* ui_list_new(UiContext *ctx, char *name);
-void* ui_list_first(UiList *list);
-void* ui_list_next(UiList *list);
-void* ui_list_get(UiList *list, int i);
-int   ui_list_count(UiList *list);
-void  ui_list_append(UiList *list, void *data);
-void  ui_list_prepend(UiList *list, void *data);
-void ui_list_clear(UiList *list);
-void  ui_list_addobsv(UiList *list, ui_callback f, void *data);
-void  ui_list_notify(UiList *list);
+UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name);
+UIEXPORT void* ui_list_first(UiList *list);
+UIEXPORT void* ui_list_next(UiList *list);
+UIEXPORT void* ui_list_get(UiList *list, int i);
+UIEXPORT int ui_list_count(UiList *list);
+UIEXPORT void ui_list_append(UiList *list, void *data);
+UIEXPORT void ui_list_prepend(UiList *list, void *data);
+UIEXPORT void ui_list_remove(UiList *list, int i);
+UIEXPORT void ui_list_clear(UiList *list);
+UIEXPORT void ui_list_update(UiList *list);
+UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data);
+UIEXPORT void ui_list_notify(UiList *list);
 
-void ui_clipboard_set(char *str);
-char* ui_clipboard_get();
+UIEXPORT UiListSelection ui_list_getselection(UiList *list);
+UIEXPORT void ui_list_setselection(UiList *list, int index);
 
-void ui_add_image(char *imgname, char *filename); // TODO: remove?
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list);
+UIEXPORT void ui_filelist_free(UiFileList list);
+
+UIEXPORT void ui_clipboard_set(char *str);
+UIEXPORT char* ui_clipboard_get();
+
+UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove?
 
 // general widget functions
-void ui_set_enabled(UIWIDGET widget, int enabled);
-void ui_set_show_all(UIWIDGET widget, int value);
-void ui_set_visible(UIWIDGET widget, int visible);
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled);
+UIEXPORT void ui_set_show_all(UIWIDGET widget, int value);
+UIEXPORT void ui_set_visible(UIWIDGET widget, int visible);
+
+
+
+UIEXPORT void ui_listselection_free(UiListSelection selection);
+
 
+UIEXPORT UiStr ui_str(char *cstr);
+UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v));
+
+
+UIEXPORT char* ui_getappdir(void);
+UIEXPORT char* ui_configfile(char *name);
+
+UIEXPORT UiCondVar* ui_condvar_create(void);
+UIEXPORT void ui_condvar_wait(UiCondVar *var);
+UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata);
+UIEXPORT void ui_condvar_destroy(UiCondVar *var);
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/tree.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/tree.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,15 +35,23 @@
 extern "C" {
 #endif
 
-typedef struct UiModel         UiModel;
-typedef struct UiListCallbacks UiListCallbacks;
-typedef struct UiListSelection UiListSelection;
+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
 } UiModelType;
 
 struct UiModel {
@@ -65,18 +73,17 @@
     char **titles;
     
     /*
+     * array of column size hints
+     */
+    int *columnsize;
+    
+    /*
      * function for translating model data to view data
      * first argument is the pointer returned by UiList->get or UiTree->get
      * second argument is the column index
      * TODO: return
      */
     void*(*getvalue)(void*, int);
-    
-    UiBool(*candrop)(UiEvent*, UiSelection*, UiList*, int);
-    void(*drop)(UiEvent*, UiSelection*, UiList*, int);
-    UiBool(*candrag)(UiEvent*, UiList*, int);
-    void(*data_get)(UiEvent*, UiSelection*, UiList*, int);
-    void(*data_delete)(UiEvent*, UiList*, int);
 };
 
 struct UiListCallbacks {
@@ -96,36 +103,129 @@
     void *userdata;
 };
 
-struct UiListSelection {
-    /*
-     * number of selected items
-     */
-    int count;
+struct UiListArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiList* list;
+    const char* varname;
+    UiModel* model;
+    ui_getvaluefunc getvalue;
+    ui_callback onactivate;
+    void* onactivatedata;
+    ui_callback onselection;
+    void* onselectiondata;
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropsdata;
+    UiBool multiselection;
+    UiMenuBuilder *contextmenu;
     
-    /*
-     * indices of selected rows
-     */
-    int *rows;
+    const int *groups;
+};
+
+typedef void (*ui_sublist_getvalue_func)(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item);
+
+struct UiSubList {
+    UiList *value;
+    const char *varname;
+    const char *header;
+    UiBool separator;
+    void *userdata;
+};
+
+/*
+ * 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;
+    char *badge;
+    void *eventdata;
 };
 
-UiModel* ui_model(UiContext *ctx, ...);
-void ui_model_free(UiContext *ctx, UiModel *mi);
-
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata);
-UIWIDGET ui_listview_nv(UiObject *obj, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+struct UiSourceListArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    const int *groups;
+    
+    /*
+     * 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
+     */
+    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;
+    
+    /*
+     * callback for each list item, that should fill all necessary
+     * UiSubListItem fields
+     */
+    ui_sublist_getvalue_func getvalue;
+    
+    /*
+     * 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;
+};
 
-UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb);
-UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb);
+#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ }
+#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} }
+
+
+UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
+UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
+UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
 
-void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...);
-void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
-void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...);
-void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+#define ui_listview(obj, ...) ui_listview_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_combobox(obj, ...) ui_combobox_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__ } )
 
-UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata);
-UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args);
+
+UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args);
+
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/ui.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/ui.h	Sat Jan 04 16:38:48 2025 +0100
@@ -45,6 +45,7 @@
 #include "image.h"
 #include "display.h"
 #include "dnd.h"
+#include "icons.h"
 
 #endif	/* UI_H */
 
--- a/ui/ui/window.h	Sun May 23 09:44:43 2021 +0200
+++ b/ui/ui/window.h	Sat Jan 04 16:38:48 2025 +0100
@@ -35,11 +35,61 @@
 extern "C" {
 #endif
 
-UiObject* ui_window(char *title, void *window_data);
-UiObject* ui_simplewindow(char *title, void *window_data);
+#define UI_FILEDIALOG_SELECT_SINGLE       0
+#define UI_FILEDIALOG_SELECT_MULTI        1
+#define UI_FILEDIALOG_SELECT_FOLDER       2
+
+typedef struct UiDialogArgs {
+    const char *title;
+    const char *content;
+    const char *button1_label;
+    const char *button2_label;
+    const char *closebutton_label;
+    const char *input_value;
+    UiBool input;
+    UiBool password;
+    ui_callback result;
+    void *resultdata;
+} UiDialogArgs;
 
-char* ui_openfiledialog(UiObject *obj);
-char* ui_savefiledialog(UiObject *obj);
+typedef struct UiDialogWindowArgs {
+    UiTri modal;
+    UiTri titlebar_buttons;
+    UiTri show_closebutton;
+    const char *title;
+    const char *lbutton1;
+    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;
+    int default_button;
+    int width;
+    int height;
+    ui_callback onclick;
+    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_simple_window(const char *title, void *window_data);
+UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
+
+#define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ });
+#define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 });
+
+UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
+
+#define ui_dialog(parent, ...) ui_dialog_create(parent, (UiDialogArgs){ __VA_ARGS__ } )
+
+UIEXPORT void ui_dialog_create(UiObject *parent, UiDialogArgs args);
+
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata);
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata);
+
+
 
 #ifdef	__cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.idl	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+namespace winui
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Application
+    x:Class="winui.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:winui">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
+                <!-- Other merged dictionaries here -->
+            </ResourceDictionary.MergedDictionaries>
+            <!-- Other app resources here -->
+            <SolidColorBrush x:Key="WindowCaptionBackground">Transparent</SolidColorBrush>
+            <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">Transparent</SolidColorBrush>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
+
+#include "App.xaml.h"
+#include "MainWindow.xaml.h"
+
+#include "toolkit.h"
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::Navigation;
+using namespace winui;
+using namespace winui::implementation;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+/// <summary>
+/// Initializes the singleton application object.  This is the first line of authored code
+/// executed, and as such is the logical equivalent of main() or WinMain().
+/// </summary>
+App::App()
+{
+    InitializeComponent();
+
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
+    UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)
+    {
+        if (IsDebuggerPresent())
+        {
+            auto errorMessage = e.Message();
+            __debugbreak();
+        }
+    });
+#endif
+}
+
+/// <summary>
+/// Invoked when the application is launched.
+/// </summary>
+/// <param name="e">Details about the launch request and process.</param>
+void App::OnLaunched(LaunchActivatedEventArgs const&)
+{
+    ui_app_run_startup();
+    //window = make<MainWindow>();
+    //window.Activate();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "App.xaml.g.h"
+
+namespace winrt::winui::implementation
+{
+    struct App : AppT<App>
+    {
+        App();
+
+        void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
+
+    private:
+        winrt::Microsoft::UI::Xaml::Window window{ nullptr };
+    };
+}
Binary file ui/winui/Assets/LockScreenLogo.scale-200.png has changed
Binary file ui/winui/Assets/SplashScreen.scale-200.png has changed
Binary file ui/winui/Assets/Square150x150Logo.scale-200.png has changed
Binary file ui/winui/Assets/Square44x44Logo.scale-200.png has changed
Binary file ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png has changed
Binary file ui/winui/Assets/StoreLogo.png has changed
Binary file ui/winui/Assets/Wide310x150Logo.scale-200.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.idl	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+namespace winui
+{
+    [default_interface]
+    runtimeclass MainWindow : Microsoft.UI.Xaml.Window
+    {
+        MainWindow();
+        Int32 MyProperty;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Window
+    x:Class="winui.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:winui"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d">
+
+    <Window.SystemBackdrop>
+        <MicaBackdrop />
+    </Window.SystemBackdrop>
+
+    <Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*"  Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+        <StackPanel x:Name="AppTitleBar" Orientation="Horizontal" Grid.Column="0" Grid.Row="0" Padding="10,5,5,10">
+            <TextBlock x:Name="AppTitleTextBlock" Text="Window Title" CanDrag="True"></TextBlock>
+        </StackPanel>
+    </Grid>
+</Window>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "MainWindow.xaml.h"
+#if __has_include("MainWindow.g.cpp")
+#include "MainWindow.g.cpp"
+#endif
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace winrt::winui::implementation
+{
+    MainWindow::MainWindow()
+    {
+        InitializeComponent();
+        //ExtendsContentIntoTitleBar(true);
+        //SetTitleBar(AppTitleBar());
+    }
+
+    int32_t MainWindow::MyProperty()
+    {
+        throw hresult_not_implemented();
+    }
+
+    void MainWindow::MyProperty(int32_t /* value */)
+    {
+        throw hresult_not_implemented();
+    }
+
+    void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
+    {
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "MainWindow.g.h"
+
+namespace winrt::winui::implementation
+{
+    struct MainWindow : MainWindowT<MainWindow>
+    {
+        MainWindow();
+
+        int32_t MyProperty();
+        void MyProperty(int32_t value);
+
+        void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
+    };
+}
+
+namespace winrt::winui::factory_implementation
+{
+    struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
+    {
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/Package.appxmanifest	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+  IgnorableNamespaces="uap rescap">
+
+  <Identity
+    Name="e3554f39-079b-4a0b-aa08-4ce5c4306644"
+    Publisher="CN=Olaf"
+    Version="1.0.0.0" />
+
+  <mp:PhoneIdentity PhoneProductId="e3554f39-079b-4a0b-aa08-4ce5c4306644" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+  <Properties>
+    <DisplayName>winui</DisplayName>
+    <PublisherDisplayName>Olaf</PublisherDisplayName>
+    <Logo>Assets\StoreLogo.png</Logo>
+  </Properties>
+
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+  </Dependencies>
+
+  <Resources>
+    <Resource Language="x-generate"/>
+  </Resources>
+
+  <Applications>
+    <Application Id="App"
+      Executable="$targetnametoken$.exe"
+      EntryPoint="$targetentrypoint$">
+      <uap:VisualElements
+        DisplayName="winui"
+        Description="winui"
+        BackgroundColor="transparent"
+        Square150x150Logo="Assets\Square150x150Logo.png"
+        Square44x44Logo="Assets\Square44x44Logo.png">
+        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
+        <uap:SplashScreen Image="Assets\SplashScreen.png" />
+      </uap:VisualElements>
+    </Application>
+  </Applications>
+
+  <Capabilities>
+    <rescap:Capability Name="runFullTrust" />
+  </Capabilities>
+</Package>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/app.manifest	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="winui.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. 
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 
+      
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+    </application>
+  </compatibility>
+  
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+</assembly>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/appmenu.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,295 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "appmenu.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include "util.h"
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+static void add_top_menu_widget(MenuBar &parent, int i, UiMenuItemI* item, UiObject* obj);
+
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_radioitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj) {
+	MenuBar mb = MenuBar();
+
+    UiMenu* menus_begin = uic_get_menu_list();
+
+    UiMenu* ls = menus_begin;
+    while (ls) {
+        UiMenu* menu = ls;
+        add_top_menu_widget(mb, 0, &menu->item, obj);
+
+        ls = (UiMenu*)ls->item.next;
+    }
+
+	return mb;
+}
+
+static void add_top_menu_widget(MenuBar& parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenu* menu = (UiMenu*)item;
+
+    MenuBarItem mi = MenuBarItem();
+    wchar_t* wlabel = str2wstr(menu->label, NULL);
+    mi.Title(wlabel);
+    free(wlabel);
+
+    UiMenuItemI* it = menu->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](mi.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    parent.Items().Append(mi);
+}
+
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenu* menu = (UiMenu*)item;
+
+    MenuFlyoutSubItem mi = MenuFlyoutSubItem();
+    wchar_t* wlabel = str2wstr(menu->label, NULL);
+    mi.Text(wlabel);
+    free(wlabel);
+
+    parent.Append(mi);
+
+    UiMenuItemI* it = menu->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](mi.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    
+}
+
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenuItem* it = (UiMenuItem*)item;
+    
+    MenuFlyoutItem mi = MenuFlyoutItem();
+    wchar_t* wlabel = str2wstr(it->label, NULL);
+    mi.Text(wlabel);
+    free(wlabel);
+
+    parent.Append(mi);
+}
+
+static void add_menuseparator_widget(
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,
+    int i,
+    UiMenuItemI* item,
+    UiObject* obj)
+{
+
+}
+
+static void add_checkitem_widget(
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,
+    int i,
+    UiMenuItemI* item,
+    UiObject* obj)
+{
+
+}
+
+static void add_radioitem_widget(
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,
+    int i,
+    UiMenuItemI* item,
+    UiObject* obj)
+{
+
+}
+
+
+class UiMenuList {
+public:
+    UiObject *obj = nullptr;
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent = { nullptr };
+    UiMenuItemType type;
+    int prevSize = 0;
+    int insertPos = 0;
+    UiVar* var = nullptr;
+    ui_getvaluefunc getvalue = nullptr;
+    ui_callback callback = nullptr;
+    void* userdata = nullptr;
+
+    UiMenuList() {
+
+    }
+
+    void updateItems() {
+        UiList* list = (UiList*)var->value;
+
+        // delete previous items
+        for (int i = 0; i < prevSize; i++) {
+            parent.RemoveAt(insertPos);
+        }
+        
+        // insert new items
+        int count = 0;
+        void* elm = list->first(list);
+        while (elm) {
+            char *menuItemLabel = (char*) (getvalue ? getvalue(elm, 0) : elm);
+
+            MenuFlyoutItem mi = MenuFlyoutItem();
+            wchar_t* wlabel = str2wstr(menuItemLabel ? menuItemLabel : "", NULL);
+            mi.Text(wlabel);
+            free(wlabel);
+
+            if (callback) {
+                mi.Click([this, elm, count](Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& e)
+                    {
+                        UiEvent evt;
+                        evt.obj = obj;
+                        evt.window = obj->window;
+                        evt.document = obj->ctx->document;
+                        evt.eventdata = elm;
+                        evt.intval = count;
+                        callback(&evt, userdata);
+                    });
+            }
+
+            parent.InsertAt(insertPos + count, mi);
+
+            elm = list->next(list);
+            count++;
+        }
+
+        prevSize = count;
+    }
+};
+
+extern "C" void destroy_ui_menu_list(void* ptr) {
+    UiMenuList* ls = (UiMenuList*)ptr;
+    delete ls;
+}
+
+static void ui_context_add_menu_list_destructor(UiContext* ctx, UiMenuList* list) {
+    cxMempoolRegister(ctx->mp, list, destroy_ui_menu_list);
+}
+
+static void ui_menulist_update(UiEvent* event, void* userdata) {
+    UiMenuList* mlist = (UiMenuList*)userdata;
+    mlist->updateItems();
+}
+
+static void add_menuitem_list_widget(
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,
+    int i,
+    UiMenuItemI* item,
+    UiObject* obj)
+{
+    UiMenuItemList* it = (UiMenuItemList*)item;
+    if (!it->varname) {
+        return;
+    }
+    
+    uint32_t size = parent.Size();
+    
+    UiVar* var = uic_create_var(ui_global_context(), it->varname, UI_VAR_LIST);
+
+    UiMenuList* mlist = new UiMenuList();
+    mlist->obj = obj;
+    mlist->parent = parent;
+    mlist->getvalue = it->getvalue;
+    mlist->callback = it->callback;
+    mlist->userdata = it->userdata;
+    mlist->prevSize = 0;
+    mlist->insertPos = size;
+    mlist->type = item->type;
+    mlist->var = var;
+    ui_context_add_menu_list_destructor(obj->ctx, mlist);
+
+    UiList* list = (UiList*)var->value;
+    list->observers = ui_add_observer(list->observers, ui_menulist_update, mlist);
+
+    mlist->updateItems();
+}
+
+
+
+
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef) {
+    MenuFlyout flyout = MenuFlyout();
+
+    UiMenuItemI* it = menudef->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](flyout.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    return flyout;
+}
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+    return NULL;
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/appmenu.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../common/menu.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
+
+typedef void(*ui_menu_add_f)(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int, UiMenuItemI*, UiObject*);
+
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj);
+
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/button.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,408 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "button.h"
+
+#include "util.h"
+#include "container.h"
+#include "icons.h"
+
+#include "../common/object.h"
+#include "../common/context.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+
+
+void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) {
+	// TODO: stockid
+
+	if (type == UI_LABEL_ICON) {
+		label = NULL;
+	}
+	else if (type == UI_LABEL_TEXT) {
+		icon = NULL;
+	}
+
+	IconElement icon_elm = { nullptr };
+	if (icon) {
+		icon_elm = ui_get_icon(icon);
+	}
+
+	if (label && icon_elm) {
+		StackPanel panel = StackPanel();
+		panel.Orientation(Orientation::Horizontal);
+		panel.Spacing(5);
+		
+		panel.Children().Append(icon_elm);
+
+		wchar_t* wlabel = str2wstr(label, nullptr);
+		TextBlock label = TextBlock();
+		label.Text(wlabel);
+		panel.Children().Append(label);
+		free(wlabel);
+
+		button.Content(panel);
+	}
+	else if (label) {
+		wchar_t* wlabel = str2wstr(label, nullptr);
+		button.Content(box_value(wlabel));
+		free(wlabel);
+	}
+	else if (icon_elm) {
+		button.Content(ui_get_icon(icon));
+	}
+}
+
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// create button with label
+	Button button = Button();
+	ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	ui_set_widget_groups(current->ctx, widget, args.groups);
+
+	// register callback
+	if (args.onclick) {
+		ui_callback cbfunc = args.onclick;
+		void* cbdata = args.onclickdata;
+		button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.window = obj->window;
+			evt.document = obj->ctx->document;
+			evt.eventdata = nullptr;
+			evt.intval = 0;
+			cbfunc(&evt, cbdata);
+			});
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+
+void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) {
+	button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) {
+	button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) {
+	ui_callback callback = args.onchange;
+	void* cbdata = args.onchangedata;
+	if (callback) {
+		button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, true);
+			callback(&evt, cbdata);
+			});
+		button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, false);
+			callback(&evt, cbdata);
+			});
+	}
+}
+
+// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same
+// code again, because although everything is basically the same, it is named differently
+static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) {
+	button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) {
+	ui_callback callback = args.onchange;
+	void* cbdata = args.onchangedata;
+	if (callback) {
+		button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, button.IsOn());
+			callback(&evt, cbdata);
+			});
+	}
+}
+
+static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) {
+	if (onchange) {
+		UiEvent evt;
+		evt.obj = obj;
+		evt.window = obj->window;
+		evt.document = obj->ctx->document;
+		evt.eventdata = nullptr;
+		evt.intval = checked;
+		onchange(&evt, onchangedata);
+	}
+	if (enable_state > 0) {
+		if (checked) {
+			ui_set_group(obj->ctx, enable_state);
+		} else {
+			ui_unset_group(obj->ctx, enable_state);
+		}
+	}
+}
+
+static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// set label
+	ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);
+	togglebutton_register_callback(button, obj, args);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	ui_set_widget_groups(current->ctx, widget, args.groups);
+
+	// bind variable
+	UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+		value->obj = widget;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+
+		// listener for notifying observers
+		togglebutton_register_checked_observers(button, obj, var);
+		togglebutton_register_unchecked_observers(button, obj, var);
+	}
+
+	if (args.enable_group > 0 || args.onchange) {
+		button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) {
+			togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group);
+		});
+		button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) {
+			togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group);
+			});
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+	ToggleButton button = ToggleButton();
+	return create_togglebutton(obj, button, args);
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+	CheckBox button = CheckBox();
+	return create_togglebutton(obj, button, args);
+}
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+	ToggleSwitch button = ToggleSwitch();
+	if (args.label) {
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		button.Header(box_value(wlabel));
+		free(wlabel);
+	}
+	switch_register_callback(button, obj, args);
+
+	UiObject* current = uic_current_obj(obj);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	ui_set_widget_groups(current->ctx, widget, args.groups);
+
+	// bind variable
+	UiVar* var = nullptr;
+	if (args.value) {
+		var = uic_create_value_var(current->ctx, args.value);
+	}
+	else if (args.varname) {
+		var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);
+	}
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+		value->obj = widget;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+
+		// listener for notifying observers
+		switch_register_observers(button, obj, var);
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {
+	RadioButton button = RadioButton();
+
+	UiObject* current = uic_current_obj(obj);
+
+	// set label
+	ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);
+	togglebutton_register_callback(button, obj, args);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	ui_set_widget_groups(current->ctx, widget, args.groups);
+
+	UiVar* var = nullptr;
+	if (args.value) {
+		var = uic_create_value_var(current->ctx, args.value);
+	}
+	else if (args.varname) {
+		var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);
+	}
+
+	// bind radio button to the value
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+
+		// store a list of radio buttons in the value
+		CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS));
+		// get or create the group name
+		static int groupCount = 0;
+		winrt::hstring groupName;
+		if (cxListSize(radioButtons) == 0) {
+			groupName = winrt::to_hstring(groupCount++);
+		} else {
+			UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0);
+			RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>();
+			groupName = firstRadioButton.GroupName();
+		}
+
+		// set the group name for the new radiobutton
+		cxListAdd(radioButtons, widget);
+		button.GroupName(groupName);
+
+		value->obj = radioButtons;
+		value->get = ui_radio_button_get;
+		value->set = ui_radio_button_set;
+
+		// listener for notifying observers (only checked, not unchecked)
+		togglebutton_register_checked_observers(button, obj, var);
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+
+int64_t ui_toggle_button_get(UiInteger* integer) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleButton toggleButton = widget->uielement.as<ToggleButton>();
+	int val = toggleButton.IsChecked().GetBoolean();
+	integer->value = val;
+	return val;
+}
+
+void ui_toggle_button_set(UiInteger* integer, int64_t value) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleButton toggleButton = widget->uielement.as<ToggleButton>();
+	toggleButton.IsChecked((bool)value);
+	integer->value = value;
+}
+
+int64_t ui_switch_get(UiInteger * integer) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();
+	int val = toggleButton.IsOn();
+	integer->value = val;
+	return val;
+}
+
+void ui_switch_set(UiInteger * integer, int64_t value) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();
+	toggleButton.IsOn((bool)value);
+	integer->value = value;
+}
+
+int64_t ui_radio_button_get(UiInteger * integer) {
+	CxList* list = (CxList*)integer->obj;
+	CxIterator i = cxListIterator(list);
+	int selection = -1;
+	cx_foreach(UiWidget*, widget, i) {
+		ToggleButton button = widget->uielement.as<ToggleButton>();
+		if (button.IsChecked().GetBoolean()) {
+			selection = i.index;
+			break;
+		}
+	}
+	integer->value = selection;
+	return selection;
+}
+
+void ui_radio_button_set(UiInteger * integer, int64_t value) {
+	CxList* list = (CxList*)integer->obj;
+	UiWidget* widget = (UiWidget*)cxListAt(list, value);
+	if (widget) {
+		ToggleButton button = widget->uielement.as<ToggleButton>();
+		button.IsChecked(true);
+		integer->value = value;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/button.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/container.h"
+
+#include "../ui/button.h"
+
+#include "../common/context.h"
+
+void ui_set_button_label(winrt::Microsoft::UI::Xaml::Controls::Primitives::ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type);
+
+void togglebutton_register_checked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);
+void togglebutton_register_unchecked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);
+void togglebutton_register_callback(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiToggleArgs& args);
+
+extern "C" int64_t ui_toggle_button_get(UiInteger * integer);
+extern "C" void ui_toggle_button_set(UiInteger * integer, int64_t value);
+
+extern "C" int64_t ui_switch_get(UiInteger * integer);
+extern "C" void ui_switch_set(UiInteger * integer, int64_t value);
+
+extern "C" int64_t ui_radio_button_get(UiInteger * integer);
+extern "C" void ui_radio_button_set(UiInteger * integer, int64_t value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/commandbar.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,218 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "commandbar.h"
+
+#include "util.h"
+#include "../common/object.h"
+#include "../common/context.h"
+
+#include "button.h"
+#include "appmenu.h"
+#include "icons.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i);
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item);
+static void create_toggleitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item);
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item);
+
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i);
+
+CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu) {
+	
+	CommandBar cb = CommandBar();
+	cb.DefaultLabelPosition(CommandBarDefaultLabelPosition::Right);
+
+	// add pre-configured items
+	CxIterator i = cxListIterator(defaults);
+	cx_foreach(char*, def, i) {
+		UiToolbarItemI* item = uic_toolbar_get_item(def);
+		if (!item) {
+			exit(-1); // TODO: maybe an error dialog?
+		}
+		create_item(obj, cb.PrimaryCommands(), item);
+	}
+
+	// add appmenu
+	if (addappmenu) {
+		UiToolbarMenuItem* appmenu = uic_get_appmenu();
+		if (appmenu) {
+			create_appmenu_items(obj, cb.SecondaryCommands(), appmenu);
+		}
+	}
+
+	return cb;
+}
+
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i) {
+	switch (i->type) {
+		case UI_TOOLBAR_ITEM: {
+			create_cmditem(obj, cb, (UiToolbarItem*)i);
+			break;
+		}
+		case UI_TOOLBAR_TOGGLEITEM: {
+			create_toggleitem(obj, cb, (UiToolbarToggleItem*)i);
+			break;
+		}
+		case UI_TOOLBAR_MENU: {
+			create_menuitem(obj, cb, (UiToolbarMenuItem*)i);
+			break;
+		}
+	}
+}
+
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i) {
+	for (UiMenuItemI* mi = i->menu.items_begin; mi; mi = mi->next) {
+		// convert UiMenuItemI to UiToolbarItemI
+		switch (mi->type) {
+			case UI_MENU: {
+				UiMenu* mitem = (UiMenu*)mi;
+				UiToolbarMenuItem tbitem;
+				memset(&tbitem, 0, sizeof(UiToolbarMenuItem));
+				tbitem.item.type = UI_TOOLBAR_MENU;
+				tbitem.args.label = mitem->label;
+				tbitem.menu.items_begin = mitem->items_begin;
+				tbitem.menu.items_end = mitem->items_end;
+				create_menuitem(obj, cb, &tbitem);
+				break;
+			}
+			case UI_MENU_ITEM: {
+				UiMenuItem* mitem = (UiMenuItem*)mi;
+				UiToolbarItem tbitem;
+				memset(&tbitem, 0, sizeof(UiToolbarItem));
+				tbitem.item.type = UI_TOOLBAR_ITEM;
+				tbitem.args.label = mitem->label;
+				tbitem.args.onclick = mitem->callback;
+				tbitem.args.onclickdata = mitem->userdata;
+				create_cmditem(obj, cb, &tbitem);
+				break;
+			}
+		}
+	}
+}
+
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item) {
+	AppBarButton button = AppBarButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if(item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(obj->ctx, widget);
+	ui_set_widget_groups(obj->ctx, widget, item->args.groups);
+
+	// register callback
+	if (item->args.onclick) {
+		ui_callback cbfunc = item->args.onclick;
+		void* cbdata = item->args.onclickdata;
+		button.Click([cbfunc, cbdata, obj](Windows::Foundation::IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.window = obj->window;
+			evt.document = obj->ctx->document;
+			evt.eventdata = nullptr;
+			evt.intval = 0;
+			cbfunc(&evt, cbdata);
+			});
+	}
+
+	cb.Append(button);
+}
+
+static void create_toggleitem(UiObject *obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item) {
+	AppBarToggleButton button = AppBarToggleButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if (item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER);
+	if (var) {	
+		UIElement elm = button;
+		UiWidget* widget = new UiWidget(elm);
+		ui_context_add_widget_destructor(obj->ctx, widget);
+		ui_set_widget_groups(obj->ctx, widget, item->args.groups);
+
+		UiInteger* value = (UiInteger*)var->value;
+		int64_t i = value->value;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+		value->obj = widget;
+		ui_toggle_button_set(value, i); // init togglebutton state
+
+		// listener for notifying observers
+		togglebutton_register_checked_observers(button, obj, var);
+		togglebutton_register_unchecked_observers(button, obj, var);
+	}
+
+	UiToggleArgs args = {};
+	args.onchange = item->args.onchange;
+	args.onchangedata = item->args.onchangedata;
+	togglebutton_register_callback(button, obj, args);
+
+
+	cb.Append(button);
+}
+
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item) {
+	AppBarButton button = AppBarButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if (item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	MenuFlyoutItem mi = MenuFlyoutItem();
+
+	MenuFlyout flyout = ui_create_menu_flyout(obj, &item->menu);
+	button.Flyout(flyout);
+
+	cb.Append(button);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/commandbar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+winrt::Microsoft::UI::Xaml::Controls::CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu);
+
+extern "C" int64_t ui_appbar_togglebutton_get(UiInteger * integer);
+extern "C" void ui_appbar_togglebutton_set(UiInteger * integer, int64_t value);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/condvar.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,65 @@
+/*
+ * 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 "pch.h"
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+    UiWinCondVar *var = new UiWinCondVar();
+    var->var.data = NULL;
+    var->var.intdata = 0;
+    var->set = 0;
+    return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    std::unique_lock<std::mutex> lock(p->mutex);
+
+    if(!p->set) {
+        p->cond.wait(lock);
+    }
+    p->set = 0;
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    std::unique_lock<std::mutex> lock(p->mutex);
+    p->var.data = data;
+    p->var.intdata = intdata;
+    p->set = 1;
+    lock.unlock();
+    p->cond.notify_one();
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    delete p;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/condvar.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef UI_WIN_CONDVAR_H
+#define UI_WIN_CONDVAR_H
+
+#include "toolkit.h"
+
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct UiWinCondVar {
+    UiCondVar var;
+    int set;
+    std::mutex mutex;
+    std::condition_variable cond;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_WIN_CONDVAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/container.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,932 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+*
+* Copyright 2023 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 "pch.h"
+
+#include "container.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include "util.h"
+
+
+void ui_container_begin_close(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->close = 1;
+}
+
+int ui_container_finish(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	if (ct->close) {
+		ui_end(obj);
+		return 0;
+	}
+	return 1;
+}
+
+
+// --------------------- UiBoxContainer ---------------------
+
+static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+
+	Grid grid = Grid();
+	current->container->Add(grid, true);
+
+	UIElement elm = grid;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing);
+	ui_context_add_container_destructor(current->ctx, newobj->container);
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) {
+	return ui_box(obj, args, UI_BOX_CONTAINER_VBOX);
+}
+
+UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) {
+	return ui_box(obj, args, UI_BOX_CONTAINER_HBOX);
+}
+
+UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) {
+	this->grid = grid;
+	this->type = type;
+
+	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
+	grid.Margin(t);
+	grid.ColumnSpacing((double)spacing);
+	grid.RowSpacing((double)spacing);
+
+	GridLength gl;
+	gl.Value = 1;
+	gl.GridUnitType = GridUnitType::Star;
+
+	// hbox needs one row def, vbox needs one col def
+	// all other col/row defs are created when elements are added
+	if (type == UI_BOX_CONTAINER_HBOX) {
+		boxRowDef = RowDefinition();
+		boxRowDef.Height(gl);
+		grid.RowDefinitions().Append(boxRowDef);
+	} else {
+		boxColDef = ColumnDefinition();
+		boxColDef.Width(gl);
+		grid.ColumnDefinitions().Append(boxColDef);
+	}
+
+	ui_reset_layout(layout);
+}
+
+void UiBoxContainer::Add(FrameworkElement control, UiBool fill) {
+	if (this->layout.fill != UI_LAYOUT_UNDEFINED) {
+		fill = ui_lb2bool(this->layout.fill);
+	}
+
+	GridLength gl;
+	if (fill) {
+		gl.Value = 1;
+		gl.GridUnitType = GridUnitType::Star;
+	}
+	else {
+		gl.Value = 0;
+		gl.GridUnitType = GridUnitType::Auto;
+	}
+
+	control.HorizontalAlignment(HorizontalAlignment::Stretch);
+	control.VerticalAlignment(VerticalAlignment::Stretch);
+
+	if (type == UI_CONTAINER_HBOX) {
+		ColumnDefinition coldef = ColumnDefinition();
+		coldef.Width(gl);
+		grid.ColumnDefinitions().Append(coldef);
+		grid.SetColumn(control, grid.Children().Size());
+		grid.SetRow(control, 0);
+	} else {
+		RowDefinition rowdef = RowDefinition();
+		rowdef.Height(gl);
+		grid.RowDefinitions().Append(rowdef);
+		grid.SetRow(control, grid.Children().Size());
+		grid.SetColumn(control, 0);
+	}
+
+	grid.Children().Append(control);
+
+	ui_reset_layout(layout);
+}
+
+
+// --------------------- UiGridContainer ---------------------
+
+UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) {
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+
+	Grid grid = Grid();
+	current->container->Add(grid, true);
+
+	UIElement elm = grid;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing);
+	ui_context_add_container_destructor(current->ctx, newobj->container);
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) {
+	this->grid = grid;
+	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
+	grid.Margin(t);
+	grid.ColumnSpacing((double)columnspacing);
+	grid.RowSpacing((double)rowspacing);
+	ui_reset_layout(layout);
+}
+
+void UiGridContainer::Add(FrameworkElement control, UiBool fill) {
+	GridLength gl;
+
+	bool hexpand = false;
+	bool vexpand = false;
+	bool hfill = false;
+	bool vfill = false;
+	if(layout.fill != UI_LAYOUT_UNDEFINED) {
+		fill = ui_lb2bool(layout.fill);
+	}
+	if (layout.hexpand != UI_LAYOUT_UNDEFINED) {
+		hexpand = layout.hexpand;
+		hfill = true;
+	}
+	if (layout.vexpand != UI_LAYOUT_UNDEFINED) {
+		vexpand = layout.vexpand;
+		vfill = true;
+	}
+	if (fill) {
+		hfill = true;
+		vfill = true;
+	}
+
+	// create new RowDefinition for the new line
+	if (layout.newline || y == -1) {
+		x = 0;
+		y++;
+		RowDefinition rowdef = RowDefinition();
+		if (vexpand) {
+			gl.GridUnitType = GridUnitType::Star;
+			gl.Value = 1;
+		}
+		else {
+			gl.GridUnitType = GridUnitType::Auto;
+			gl.Value = 0;
+		}
+		rowdef.Height(gl);
+		grid.RowDefinitions().Append(rowdef);
+	} else if (vexpand) {
+		// adjust row
+		gl.GridUnitType = GridUnitType::Star;
+		gl.Value = 1;
+		grid.RowDefinitions().GetAt(y).Height(gl);
+	}
+
+	// create new columndefinition, if a new column is added
+	if (x == cols) {
+		if (hexpand) {
+			gl.GridUnitType = GridUnitType::Star;
+			gl.Value = 1;
+		}
+		else {
+			gl.GridUnitType = GridUnitType::Auto;
+			gl.Value = 0;
+		}
+		ColumnDefinition coldef = ColumnDefinition();
+		coldef.Width(gl);
+		grid.ColumnDefinitions().Append(coldef);
+		cols++;
+	} else if(hexpand) {
+		// adjust column
+		if (layout.colspan == 0) {
+			gl.GridUnitType = GridUnitType::Star;
+			gl.Value = 1;
+			grid.ColumnDefinitions().GetAt(x).Width(gl);
+		} else {
+			int adjust_col = x;
+			bool adjust = true;
+			for (int i = 0; i < layout.colspan; i++) {
+				if (grid.ColumnDefinitions().Size() == x + i) {
+					break;
+				}
+				adjust_col = x + i;
+				GridLength w = grid.ColumnDefinitions().GetAt(adjust_col).Width();
+				if (w.GridUnitType == GridUnitType::Star) {
+					adjust = false;
+					break;
+				}
+			}
+
+			if (adjust) {
+				gl.GridUnitType = GridUnitType::Star;
+				gl.Value = 1;
+				grid.ColumnDefinitions().GetAt(adjust_col).Width(gl);
+			}
+		}
+	}
+
+	// add control
+	if (hfill) {
+		control.HorizontalAlignment(HorizontalAlignment::Stretch);
+	}
+	if (vfill) {
+		control.VerticalAlignment(VerticalAlignment::Stretch);
+	}
+
+	if (layout.colspan > 0) {
+		grid.SetColumnSpan(control, layout.colspan);
+	}
+	if (layout.rowspan > 0) {
+		grid.SetRowSpan(control, layout.rowspan);
+	}
+
+	grid.SetRow(control, y);
+	grid.SetColumn(control, x);
+	grid.Children().Append(control);
+
+	x++;
+
+	ui_reset_layout(layout);
+}
+
+// --------------------- UI Frame ---------------------
+
+UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) {
+	// create a grid for the frame, that contains the label and a sub-frame
+	Grid frame = Grid();
+
+	GridLength gl;
+	gl.GridUnitType = GridUnitType::Star;
+	gl.Value = 1;
+
+	ColumnDefinition coldef = ColumnDefinition();
+	coldef.Width(gl);
+	frame.ColumnDefinitions().Append(coldef);
+
+	RowDefinition rowdefFrame = RowDefinition();
+	rowdefFrame.Height(gl);
+
+	// label
+	int row = 0;
+	if (args.label) {
+		RowDefinition rowdefLabel = RowDefinition();
+		gl.GridUnitType = GridUnitType::Auto;
+		gl.Value = 0;
+		rowdefLabel.Height(gl);
+		frame.RowDefinitions().Append(rowdefLabel);
+
+		TextBlock label = TextBlock();
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		winrt::hstring hstr(wlabel);
+		label.Text(hstr);
+		free(wlabel);
+
+		frame.SetRow(label, row++);
+		frame.SetColumn(label, 0);
+		frame.Children().Append(label);
+	}
+
+	// workarea frame
+	frame.RowDefinitions().Append(rowdefFrame);
+
+	Grid workarea = Grid();
+	frame.SetRow(workarea, row);
+	frame.SetColumn(workarea, 0);
+	frame.Children().Append(workarea);
+
+	// some styling for the workarea
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) };
+	workarea.BorderBrush(brush);
+	CornerRadius radius{ 8, 8, 8, 8 };
+	Thickness t = { 1, 1, 1, 1 };
+	workarea.CornerRadius(radius);
+	workarea.BorderThickness(t);
+
+	Thickness padding = { 10, 10, 10, 10 };
+	workarea.Padding(padding);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(frame, true);
+
+	UIElement elm = frame;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// sub container
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI Expander ---------------------
+
+UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) {
+	Expander expander = Expander();
+	if (args.label) {
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		expander.Header(box_value(wlabel));
+		free(wlabel);
+	}
+	expander.IsExpanded(args.isexpanded);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(expander, true);
+
+	UIElement elm = expander;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	Grid content = Grid();
+	expander.Content(content);
+
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default: 
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI ScrolledWindow ---------------------
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+	ScrollViewer scrollW = ScrollViewer();
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(scrollW, true);
+
+	UIElement elm = scrollW;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// create child container
+	Grid content = Grid();
+	scrollW.Content(content);
+
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI TabView ---------------------
+
+UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) {
+	this->tabview = tabview;
+}
+
+void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) {
+	// noop
+}
+
+static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) {
+	UiContainer* ctn = nullptr;
+	switch (type) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UIElement elm = subcontainer;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	UiObject* newobj = uic_object_new(current, widget);
+	newobj->container = ctn;
+	return newobj;
+}
+
+static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) {
+	Pivot pivot = Pivot();
+	UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args);
+
+	return tabview;
+}
+
+UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) {
+	this->current = obj;
+	this->pivot = pivot;
+	this->subcontainer = args.subcontainer;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+}
+
+UiObject* UiPivotTabView::AddTab(const char* label, int index) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	PivotItem item = PivotItem();
+	item.Header(text);
+
+	// sub container
+	Grid subcontainer = Grid();
+	item.Content(subcontainer);
+	pivot.Items().Append(item);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+void UiPivotTabView::Remove(int index) {
+	pivot.Items().RemoveAt(index);
+}
+
+void UiPivotTabView::Select(int index) {
+	
+}
+
+FrameworkElement UiPivotTabView::GetFrameworkElement() {
+	return pivot;
+}
+
+
+static UiTabView* tabview_invisible_create(UiObject *obj, UiTabViewArgs args) {
+	Grid container = Grid();
+	container.HorizontalAlignment(HorizontalAlignment::Stretch);
+	container.VerticalAlignment(VerticalAlignment::Stretch);
+	UiInvisibleTabView *tabview = new UiInvisibleTabView(obj, container, args);
+	return tabview;
+}
+
+UiInvisibleTabView::UiInvisibleTabView(UiObject* obj, Grid container, UiTabViewArgs args) {
+	this->current = obj;
+	this->container = container;
+	this->subcontainer = args.subcontainer;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+	this->currentIndex = -1;
+
+	GridLength gl;
+	gl.GridUnitType = GridUnitType::Star;
+	gl.Value = 1;
+
+	ColumnDefinition coldef = ColumnDefinition();
+	coldef.Width(gl);
+	container.ColumnDefinitions().Append(coldef);
+
+	RowDefinition rowdef = RowDefinition();
+	rowdef.Height(gl);
+	container.RowDefinitions().Append(rowdef);
+}
+
+UiObject* UiInvisibleTabView::AddTab(const char* label, int index) {
+	Grid subcontainer = Grid();
+	subcontainer.HorizontalAlignment(HorizontalAlignment::Stretch);
+	subcontainer.VerticalAlignment(VerticalAlignment::Stretch);
+	
+	if (pages.size() == 0) {
+		container.Children().Append(subcontainer);
+		currentIndex = 0;
+	}
+
+	if (index < 0) {
+		pages.push_back(subcontainer);
+	} else {
+		pages.insert(pages.begin() + index, subcontainer);
+	}
+
+	// sub container
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+void UiInvisibleTabView::Remove(int index) {
+	
+}
+
+void UiInvisibleTabView::Select(int index) {
+	if (index >= 0 && index < pages.size()) {
+		if (currentIndex != -1) {
+			container.Children().RemoveAt(0);
+		}
+		
+		container.Children().Append(pages.at(index));
+	}
+}
+
+FrameworkElement UiInvisibleTabView::GetFrameworkElement() {
+	return container;
+}
+
+
+static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) {
+	TabView tabview = TabView();
+	tabview.IsAddTabButtonVisible(false);
+	//tabview.CanDragTabs(false);
+	//tabview.CanReorderTabs(false);
+	UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args);
+
+	return uitabview;
+}
+
+UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) {
+	this->current = obj;
+	this->tabview = tabview;
+	this->subcontainer = args.subcontainer;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+}
+
+UiObject* UiMainTabView::AddTab(const char* label, int index) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	TabViewItem item = TabViewItem();
+	item.Header(text);
+	item.CanDrag(false);
+	item.IsClosable(false);
+
+	// sub container
+	Grid subcontainer = Grid();
+	item.Content(subcontainer);
+	tabview.TabItems().Append(item);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+void UiMainTabView::Remove(int index) {
+	this->tabview.TabItems().RemoveAt(index);
+}
+
+void UiMainTabView::Select(int index) {
+
+}
+
+FrameworkElement UiMainTabView::GetFrameworkElement() {
+	return tabview;
+}
+
+
+static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) {
+	NavigationView navigationview = NavigationView();
+	UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type);
+	navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed);
+	navigationview.IsSettingsVisible(false);
+
+	return tabview;
+}
+
+UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) {
+	this->current = obj;
+	this->navigationview = navigationview;
+	this->type = type;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+
+	if (type == UI_TABVIEW_NAVIGATION_TOP) {
+		navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top);
+	}
+
+	navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged });
+}
+
+UiObject* UiNavigationTabView::AddTab(const char* label, int index1) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	NavigationViewItem item = NavigationViewItem();
+	item.Content(text);
+
+	// sub container
+	Grid subcontainer = Grid();
+	if (pages.size() == 0) {
+		navigationview.Content(subcontainer);
+		navigationview.SelectedItem(item);
+	}
+
+	navigationview.MenuItems().Append(item);
+	auto page = std::tuple<NavigationViewItem, FrameworkElement>{ item, subcontainer };
+	pages.push_back(page);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+void UiNavigationTabView::Remove(int index) {
+	navigationview.MenuItems().RemoveAt(index);
+	pages.erase(pages.begin() + index);
+}
+
+void UiNavigationTabView::Select(int index) {
+
+}
+
+FrameworkElement UiNavigationTabView::GetFrameworkElement() {
+	return navigationview;
+}
+
+void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) {
+	for (auto page : pages) {
+		NavigationViewItem item = std::get<0>(page);
+		FrameworkElement elm = std::get<1>(page);
+		if (item == navigationview.SelectedItem()) {
+			navigationview.Content(elm);
+			break;
+		}
+	}
+}
+
+static int64_t ui_tabview_get(UiInteger *i) {
+	return 0;
+}
+
+static void ui_tabview_set(UiInteger *i, int64_t value) {
+	UiTabView *tabview = (UiTabView*)i->obj;
+	tabview->Select(value);
+}
+
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
+	UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview;
+	UiTabView* tabview = nullptr;
+	switch (type) {
+		default: {
+			tabview = tabview_pivot_create(obj, args);
+			break;
+		}
+		case UI_TABVIEW_DOC: { 
+			tabview = tabview_main_create(obj, args);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_SIDE: { 
+			tabview = tabview_navigationview_create(obj, args, type);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_TOP: { 
+			tabview = tabview_navigationview_create(obj, args, type);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_TOP2: { 
+			tabview = tabview_pivot_create(obj, args);
+			break;
+		}
+		case UI_TABVIEW_INVISIBLE: {
+			tabview = tabview_invisible_create(obj, args);
+			break;
+		}
+	}
+	UiTabViewContainer* ctn = new UiTabViewContainer(tabview);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(tabview->GetFrameworkElement(), true);
+
+	UIElement elm = tabview->GetFrameworkElement();
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	widget->data1 = tabview;
+
+	// TODO: add tabview destructor
+
+	// bind variable
+	UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+	if (var) {
+		UiInteger *i = (UiInteger*)var->value;
+		i->obj = tabview;
+		i->get = ui_tabview_get;
+		i->set = ui_tabview_set;
+	}
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+void ui_tab_create(UiObject* obj, const char* title) {
+	UiObject* current = uic_current_obj(obj);
+	UiTabView* tabview = (UiTabView*)current->widget->data1;
+	UiObject* newobj = tabview->AddTab(title);
+	uic_obj_add(current, newobj);
+}
+
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab) {
+	UiTabView* t = (UiTabView*)tabview->data1;
+	t->Select(tab);
+}
+
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab) {
+	UiTabView* t = (UiTabView*)tabview->data1;
+	t->Remove(tab);
+}
+
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
+	UiTabView* t = (UiTabView*)tabview->data1;
+	UiObject* newobj = t->AddTab(name, tab_index);
+	return newobj;
+}
+
+
+
+// --------------------- UI Headerbar ---------------------
+
+// TODO: replace placeholder implementation
+
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+	UiContainerArgs boxargs = { };
+	boxargs.fill = UI_OFF;
+	return ui_hbox_create(obj, boxargs);
+}
+
+UIEXPORT void ui_headerbar_start_create(UiObject *obj) {
+	UiContainerArgs boxargs = { };
+	boxargs.fill = UI_OFF;
+	ui_hbox_create(obj, boxargs);
+}
+
+UIEXPORT void ui_headerbar_center_create(UiObject *obj) {
+	UiContainerArgs boxargs = { };
+	boxargs.fill = UI_OFF;
+	ui_hbox_create(obj, boxargs);
+}
+
+UIEXPORT void ui_headerbar_end_create(UiObject *obj) {
+	UiContainerArgs boxargs = { };
+	boxargs.fill = UI_OFF;
+	ui_hbox_create(obj, boxargs);
+}
+
+
+/*
+* -------------------- Layout Functions --------------------
+*
+* functions for setting layout attributes for the current container
+*
+*/
+
+void ui_layout_fill(UiObject* obj, UiBool fill) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject* obj, UiBool expand) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject* obj, UiBool expand) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.vexpand = expand;
+}
+
+void ui_layout_hfill(UiObject* obj, UiBool fill) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.hfill = fill;
+}
+
+void ui_layout_vfill(UiObject* obj, UiBool fill) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.vfill = fill;
+}
+
+void ui_layout_width(UiObject* obj, int width) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.width = width;
+}
+
+void ui_layout_height(UiObject* obj, int height) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.height = height;
+}
+
+void ui_layout_colspan(UiObject* obj, int cols) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.colspan = cols;
+}
+
+void ui_layout_rowspan(UiObject* obj, int rows) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.rowspan = rows;
+}
+
+void ui_newline(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.newline = TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/container.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,187 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/container.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiLayoutBool fill;
+    UiBool       newline;
+    char* label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    int          width;
+    int          height;
+    int          colspan;
+    int          rowspan;
+};
+
+struct UiContainer {
+    UiLayout layout;
+    int close = 0;
+
+    virtual void Add(FrameworkElement control, UiBool fill) = 0;
+};
+
+enum UiBoxContainerType {
+    UI_BOX_CONTAINER_VBOX = 0,
+    UI_BOX_CONTAINER_HBOX
+};
+
+enum UiNavigationViewType {
+    UI_NAVIGATIONVIEW_TOP = 0,
+    UI_NAVIGATIONVIEW_SIDE
+};
+
+struct UiBoxContainer : UiContainer {
+    Grid grid;
+    enum UiBoxContainerType type;
+    RowDefinition boxRowDef;
+    ColumnDefinition boxColDef;
+
+    UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiGridContainer : UiContainer {
+    Grid grid;
+    int x = 0;
+    int y = -1;
+    int cols = 0;
+
+    UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiTabView {
+    UiObject* current;
+    UiSubContainerType subcontainer;
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    virtual UiObject* AddTab(const char* label, int index = -1) = 0;
+    virtual void Remove(int index) = 0;
+    virtual void Select(int index) = 0;
+    virtual FrameworkElement GetFrameworkElement() = 0;
+};
+
+struct UiTabViewContainer : UiContainer {
+    UiTabView* tabview;
+
+    UiTabViewContainer(UiTabView* tabview);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiPivotTabView : UiTabView {
+    Pivot pivot;
+
+    UiPivotTabView(UiObject *obj, Pivot pivot, UiTabViewArgs args);
+
+    UiObject* AddTab(const char* label, int index = -1);
+    void Remove(int index);
+    void Select(int index);
+    FrameworkElement GetFrameworkElement();
+};
+
+struct UiMainTabView : UiTabView {
+    TabView tabview;
+
+    UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args);
+
+    UiObject* AddTab(const char* label, int index = -1);
+    void Remove(int index);
+    void Select(int index);
+    FrameworkElement GetFrameworkElement();
+};
+
+struct UiNavigationTabView : UiTabView {
+    NavigationView navigationview;
+    UiTabViewType type;
+    std::vector<std::tuple<NavigationViewItem, FrameworkElement> > pages;
+
+    UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type);
+
+    UiObject* AddTab(const char* label, int index = -1);
+    void Remove(int index);
+    void Select(int index);
+    FrameworkElement GetFrameworkElement();
+
+    void SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args);
+};
+
+struct UiInvisibleTabView : UiTabView {
+    Grid container;
+    std::vector<FrameworkElement> pages;
+    int currentIndex;
+
+    UiInvisibleTabView(UiObject *obj, Grid container, UiTabViewArgs args);
+
+    UiObject* AddTab(const char* label, int index = -1);
+    void Remove(int index);
+    void Select(int index);
+    FrameworkElement GetFrameworkElement();
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/dnd.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,91 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "dnd.h"
+#include "util.h"
+
+#include <thread>
+
+using namespace winrt;
+using namespace Windows::ApplicationModel::DataTransfer;
+using namespace Windows::Storage;
+using namespace Windows::Storage::Streams;
+
+UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) {
+	if (dnd->data) {
+		if (len < 0) {
+			len = strlen(str);
+		}
+		wchar_t *wstr = str2wstr_len(str, len, nullptr);
+
+		dnd->data.SetText(wstr);
+
+		free(wstr);
+
+	}
+}
+
+UIEXPORT void ui_selection_seturis(UiDnD* dnd, char** uris, int nelm) {
+
+}
+
+
+UIEXPORT char* ui_selection_gettext(UiDnD* dnd) {
+	return nullptr;
+}
+
+
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) {
+	UiFileList flist;
+	flist.files = nullptr;
+	flist.nfiles = 0;
+
+    if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) {
+		UiFileList *flist_ptr = &flist;
+
+		// we need to execute this in a different thread
+		// this could block the main gui thread, but shouldn't happen with a simple uri list
+		std::thread getDataThread([dnd, flist_ptr]() {
+				auto items = dnd->dataview.GetStorageItemsAsync().get();
+
+				char **uris = (char**)calloc(items.Size(), sizeof(char*));
+				flist_ptr->files = uris;
+				flist_ptr->nfiles = items.Size();
+
+				int i = 0;
+				for (IStorageItem const& item : items) {
+					winrt::hstring path = item.Path();
+					uris[i++] = wchar2utf8(path.c_str(), path.size());
+				}
+			});
+		getDataThread.join();
+    }
+	return flist;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/dnd.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,40 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/dnd.h"
+
+struct UiDnD {
+	int evttype = 0;
+	winrt::Microsoft::UI::Xaml::DragStartingEventArgs dndstartargs = { nullptr };
+	winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr };
+	winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr };
+	winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr };
+	winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/icons.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,421 @@
+/*
+ * 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 "pch.h"
+
+#include "icons.h"
+#include "../ui/icons.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+#include <Windows.h>
+#include <Shellapi.h>
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;
+//using namespace Windows::Storage::Streams;
+
+static UiIcon* sys_folder_icon16;
+static UiIcon* sys_file_icon16;
+
+static UiIcon* sys_folder_icon32;
+static UiIcon* sys_file_icon32;
+
+std::unordered_map<std::string, Symbol> ui_symbol_icons = {
+	{"Accept", Symbol::Accept },
+	{"Account", Symbol::Account },
+	{"Add", Symbol::Add },
+	{"AddFriend", Symbol::AddFriend },
+	{"Admin", Symbol::Admin },
+	{"AlignCenter", Symbol::AlignCenter },
+	{"AlignLeft", Symbol::AlignLeft },
+	{"AlignRight", Symbol::AlignRight },
+	{"AllApps", Symbol::AllApps },
+	{"Attach", Symbol::Attach },
+	{"AttachCamera", Symbol::AttachCamera },
+	{"Audio", Symbol::Audio },
+	{"Back", Symbol::Back },
+	{"BackToWindow", Symbol::BackToWindow },
+	{"BlockContact", Symbol::BlockContact },
+	{"Bold", Symbol::Bold },
+	{"Bookmarks", Symbol::Bookmarks },
+	{"BrowsePhotos", Symbol::BrowsePhotos },
+	{"Bullets", Symbol::Bullets },
+	{"Calculator", Symbol::Calculator },
+	{"Calendar", Symbol::Calendar },
+	{"CalendarDay", Symbol::CalendarDay },
+	{"CalendarReply", Symbol::CalendarReply },
+	{"CalendarWeek", Symbol::CalendarWeek },
+	{"Camera", Symbol::Camera },
+	{"Cancel", Symbol::Cancel },
+	{"Caption", Symbol::Caption },
+	{"CellPhone", Symbol::CellPhone },
+	{"Character", Symbol::Character },
+	{"Clear", Symbol::Clear },
+	{"ClearSelection", Symbol::ClearSelection },
+	{"Clock", Symbol::Clock },
+	{"ClosedCaption", Symbol::ClosedCaption },
+	{"ClosePane", Symbol::ClosePane },
+	{"Comment", Symbol::Comment },
+	{"Contact", Symbol::Contact },
+	{"Contact2", Symbol::Contact2 },
+	{"ContactInfo", Symbol::ContactInfo },
+	{"ContactPresence", Symbol::ContactPresence },
+	{"Copy", Symbol::Copy },
+	{"Crop", Symbol::Crop },
+	{"Cut", Symbol::Cut },
+	{"Delete", Symbol::Delete },
+	{"Directions", Symbol::Directions },
+	{"DisableUpdates", Symbol::DisableUpdates },
+	{"DisconnectDrive", Symbol::DisconnectDrive },
+	{"Dislike", Symbol::Dislike },
+	{"DockBottom", Symbol::DockBottom },
+	{"DockLeft", Symbol::DockLeft },
+	{"DockRight", Symbol::DockRight },
+	{"Document", Symbol::Document },
+	{"Download", Symbol::Download },
+	{"Edit", Symbol::Edit },
+	{"Emoji", Symbol::Emoji },
+	{"Emoji2", Symbol::Emoji2 },
+	{"Favorite", Symbol::Favorite },
+	{"Filter", Symbol::Filter },
+	{"Find", Symbol::Find },
+	{"Flag", Symbol::Flag },
+	{"Folder", Symbol::Folder },
+	{"Font", Symbol::Font },
+	{"FontColor", Symbol::FontColor },
+	{"FontDecrease", Symbol::FontDecrease },
+	{"FontIncrease", Symbol::FontIncrease },
+	{"FontSize", Symbol::FontSize },
+	{"Forward", Symbol::Forward },
+	{"FourBars", Symbol::FourBars },
+	{"FullScreen", Symbol::FullScreen },
+	{"GlobalNavigationButton", Symbol::GlobalNavigationButton },
+	{"Globe", Symbol::Globe },
+	{"Go", Symbol::Go },
+	{"GoToStart", Symbol::GoToStart },
+	{"GoToToday", Symbol::GoToToday },
+	{"HangUp", Symbol::HangUp },
+	{"Help", Symbol::Help },
+	{"HideBcc", Symbol::HideBcc },
+	{"Highlight", Symbol::Highlight },
+	{"Home", Symbol::Home },
+	{"Import", Symbol::Import },
+	{"ImportAll", Symbol::ImportAll },
+	{"Important", Symbol::Important },
+	{"Italic", Symbol::Italic },
+	{"Keyboard", Symbol::Keyboard },
+	{"LeaveChat", Symbol::LeaveChat },
+	{"Library", Symbol::Library },
+	{"Like", Symbol::Like },
+	{"LikeDislike", Symbol::LikeDislike },
+	{"Link", Symbol::Link },
+	{"List", Symbol::List },
+	{"Mail", Symbol::Mail },
+	{"MailFilled", Symbol::MailFilled },
+	{"MailForward", Symbol::MailForward },
+	{"MailReply", Symbol::MailReply },
+	{"MailReplyAll", Symbol::MailReplyAll },
+	{"Manage", Symbol::Manage },
+	{"Map", Symbol::Map },
+	{"MapDrive", Symbol::MapDrive },
+	{"MapPin", Symbol::MapPin },
+	{"Memo", Symbol::Memo },
+	{"Message", Symbol::Message },
+	{"Microphone", Symbol::Microphone },
+	{"More", Symbol::More },
+	{"MoveToFolder", Symbol::MoveToFolder },
+	{"MusicInfo", Symbol::MusicInfo },
+	{"Mute", Symbol::Mute },
+	{"NewFolder", Symbol::NewFolder },
+	{"NewWindow", Symbol::NewWindow },
+	{"Next", Symbol::Next },
+	{"OneBar", Symbol::OneBar },
+	{"OpenFile", Symbol::OpenFile },
+	{"OpenLocal", Symbol::OpenLocal },
+	{"OpenPane", Symbol::OpenPane },
+	{"OpenWith", Symbol::OpenWith },
+	{"Orientation", Symbol::Orientation },
+	{"OtherUser", Symbol::OtherUser },
+	{"OutlineStar", Symbol::OutlineStar },
+	{"Page", Symbol::Page },
+	{"Page2", Symbol::Page2 },
+	{"Paste", Symbol::Paste },
+	{"Pause", Symbol::Pause },
+	{"People", Symbol::People },
+	{"Permissions", Symbol::Permissions },
+	{"Phone", Symbol::Phone },
+	{"PhoneBook", Symbol::PhoneBook },
+	{"Pictures", Symbol::Pictures },
+	{"Pin", Symbol::Pin },
+	{"Placeholder", Symbol::Placeholder },
+	{"Play", Symbol::Play },
+	{"PostUpdate", Symbol::PostUpdate },
+	{"Preview", Symbol::Preview },
+	{"PreviewLink", Symbol::PreviewLink },
+	{"Previous", Symbol::Previous },
+	{"Print", Symbol::Print },
+	{"Priority", Symbol::Priority },
+	{"ProtectedDocument", Symbol::ProtectedDocument },
+	{"Read", Symbol::Read },
+	{"Redo", Symbol::Redo },
+	{"Refresh", Symbol::Refresh },
+	{"Remote", Symbol::Remote },
+	{"Remove", Symbol::Remove },
+	{"Rename", Symbol::Rename },
+	{"Repair", Symbol::Repair },
+	{"RepeatAll", Symbol::RepeatAll },
+	{"RepeatOne", Symbol::RepeatOne },
+	{"ReportHacked", Symbol::ReportHacked },
+	{"ReShare", Symbol::ReShare },
+	{"Rotate", Symbol::Rotate },
+	{"RotateCamera", Symbol::RotateCamera },
+	{"Save", Symbol::Save },
+	{"SaveLocal", Symbol::SaveLocal },
+	{"Scan", Symbol::Scan },
+	{"SelectAll", Symbol::SelectAll },
+	{"Send", Symbol::Send },
+	{"SetLockScreen", Symbol::SetLockScreen },
+	{"SetTile", Symbol::SetTile },
+	{"Setting", Symbol::Setting },
+	{"Share", Symbol::Share },
+	{"Shop", Symbol::Shop },
+	{"ShowBcc", Symbol::ShowBcc },
+	{"ShowResults", Symbol::ShowResults },
+	{"Shuffle", Symbol::Shuffle },
+	{"SlideShow", Symbol::SlideShow },
+	{"SolidStar", Symbol::SolidStar },
+	{"Sort", Symbol::Sort },
+	{"Stop", Symbol::Stop },
+	{"StopSlideShow", Symbol::StopSlideShow },
+	{"Street", Symbol::Street },
+	{"Switch", Symbol::Switch },
+	{"SwitchApps", Symbol::SwitchApps },
+	{"Sync", Symbol::Sync },
+	{"SyncFolder", Symbol::SyncFolder },
+	{"Tag", Symbol::Tag },
+	{"Target", Symbol::Target },
+	{"ThreeBars", Symbol::ThreeBars },
+	{"TouchPointer", Symbol::TouchPointer },
+	{"Trim", Symbol::Trim },
+	{"TwoBars", Symbol::TwoBars },
+	{"TwoPage", Symbol::TwoPage },
+	{"Underline", Symbol::Underline },
+	{"Undo", Symbol::Undo },
+	{"UnFavorite", Symbol::UnFavorite },
+	{"UnPin", Symbol::UnPin },
+	{"UnSyncFolder", Symbol::UnSyncFolder },
+	{"Up", Symbol::Up },
+	{"Upload", Symbol::Upload },
+	{"Video", Symbol::Video },
+	{"VideoChat", Symbol::VideoChat },
+	{"View", Symbol::View },
+	{"ViewAll", Symbol::ViewAll },
+	{"Volume", Symbol::Volume },
+	{"WebCam", Symbol::WebCam },
+	{"World", Symbol::World },
+	{"XboxOneConsole", Symbol::XboxOneConsole },
+	{"ZeroBars", Symbol::ZeroBars },
+	{"Zoom", Symbol::Zoom },
+	{"ZoomIn", Symbol::ZoomIn },
+	{"ZoomOut", Symbol::ZoomOut }
+};
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name) {
+	if (ui_symbol_icons.find(name) == ui_symbol_icons.end()) {
+		SymbolIcon no_icon = { nullptr };
+		return no_icon;
+	}
+
+	Symbol symbol = ui_symbol_icons[name];
+	SymbolIcon icon = SymbolIcon(symbol);
+	return icon;
+}
+
+
+// symbol icon implementation
+UiSymbolIcon::UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym) {
+	symbol = sym;
+}
+
+UiSymbolIcon::~UiSymbolIcon() {
+	
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiSymbolIcon::getIcon() {
+	return SymbolIcon(symbol);
+}
+
+// image icon implementation
+UiImageIcon::UiImageIcon(const char* uristr) {
+	wchar_t* wuri = str2wstr(uristr, nullptr);
+	Windows::Foundation::Uri uri{ wuri };
+	this->uri = uri;
+	free(wuri);
+}
+
+UiImageIcon::~UiImageIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiImageIcon::getIcon() {
+	BitmapIcon icon = BitmapIcon();
+	icon.UriSource(uri);
+	ImageIcon img = ImageIcon();
+	img.Source();
+	return icon;
+}
+
+// bitmap icon implementation
+UiBitmapIcon::UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap) {
+	this->bitmap = bitmap;
+}
+
+UiBitmapIcon::~UiBitmapIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiBitmapIcon::getIcon() {
+	ImageIcon icon = ImageIcon();
+	icon.Source(bitmap);
+	return icon;
+}
+
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size) {
+	Symbol symbol = ui_symbol_icons[name];
+	UiSymbolIcon* icon = new UiSymbolIcon(symbol);
+	return icon;
+}
+
+
+UIEXPORT UiIcon* ui_imageicon(const char* file) {
+	return new UiImageIcon(file);
+}
+
+UIEXPORT void ui_icon_free(UiIcon* icon) {
+	delete icon;
+}
+
+
+struct __declspec(uuid("905a0fef-bc53-11df-8c49-001e4fc686da")) IBufferByteAccess : ::IUnknown
+{
+	virtual HRESULT __stdcall Buffer(uint8_t** value) = 0;
+};
+
+
+
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large) {
+	WriteableBitmap wbitmap = { nullptr };
+
+	// get the icon from the dll
+	HICON hicon_small;
+	HICON hicon_large;
+	if (ExtractIconExA(dll, iconindex, &hicon_large, &hicon_small, 1) > 0) {
+		HICON hicon = large ? hicon_large : hicon_small;
+
+		// convert icon to (gdi) bitmap
+		ICONINFO info;
+		info.hbmColor = nullptr;
+		info.hbmMask = nullptr;
+		if (GetIconInfo(hicon, &info)) {
+			BITMAP bitmap;
+			if (GetObjectW(info.hbmColor, sizeof(BITMAP), &bitmap) != 0) {
+				size_t bitmap_size = bitmap.bmWidthBytes * bitmap.bmHeight;
+				char *bitmap_data = (char*)malloc(bitmap_size);
+
+				// get the pixel data
+				if (GetBitmapBits(info.hbmColor, bitmap_size, bitmap_data) != 0) {
+					WriteableBitmap wb = WriteableBitmap(bitmap.bmWidth, bitmap.bmHeight);
+					void *wb_data = wb.PixelBuffer().data();
+					memcpy(wb_data, bitmap_data, bitmap_size);
+					wbitmap = wb;
+				}
+				free(bitmap_data);
+			}
+			if (info.hbmMask) {
+				DeleteObject(info.hbmMask);
+			}
+			if (info.hbmColor) {
+				DeleteObject(info.hbmColor);
+			}
+		}
+
+		DestroyIcon(hicon_small);
+		DestroyIcon(hicon_large);
+	}
+
+	return wbitmap;
+}
+
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large) {
+	WriteableBitmap wbitmap = ui_dllicon2bitmap(dll, iconindex, large);
+	return new UiBitmapIcon(wbitmap);
+}
+
+UIEXPORT UiIcon* ui_foldericon(size_t size) {
+	bool large = true;
+	UiIcon** sys_folder_icon = &sys_folder_icon32;
+	if (size <= 24) {
+		large = false;
+		sys_folder_icon = &sys_folder_icon16;
+	}
+
+	if (*sys_folder_icon) {
+		return *sys_folder_icon;
+	}
+
+	UiIcon* icon = ui_dllicon("shell32.dll", 3, large);
+	*sys_folder_icon = icon;
+	return icon;
+}
+
+UIEXPORT UiIcon* ui_fileicon(size_t size) {
+	bool large = true;
+	UiIcon** sys_folder_icon = &sys_file_icon32;
+	if (size <= 24) {
+		large = false;
+		sys_folder_icon = &sys_file_icon16;
+	}
+
+	if (*sys_folder_icon) {
+		return *sys_folder_icon;
+	}
+
+	UiIcon* icon = ui_dllicon("shell32.dll", 0, large);
+	*sys_folder_icon = icon;
+	return icon;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/icons.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "../ui/toolkit.h"
+
+
+
+struct UiIcon {
+	//virtual ~UiIcon() = 0;
+	
+	virtual winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon() = 0;
+};
+
+struct UiSymbolIcon : UiIcon {
+	winrt::Microsoft::UI::Xaml::Controls::Symbol symbol;
+
+	UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym);
+
+	~UiSymbolIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+struct UiImageIcon : UiIcon {
+	winrt::Windows::Foundation::Uri uri{ nullptr };
+
+	UiImageIcon(const char* uristr);
+
+	~UiImageIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+struct UiBitmapIcon : UiIcon {
+	winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap{ nullptr };
+
+	UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap);
+
+	~UiBitmapIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name);
+
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large);
+
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/image.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,124 @@
+/*
+* 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 "pch.h"
+
+#include "image.h"
+
+#include "toolkit.h"
+#include "container.h"
+#include "../common/object.h"
+#include "../common/context.h"
+#include "util.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;
+using namespace winrt::Microsoft::UI::Xaml::Media;
+
+UiImageSource::UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src) : imgsrc(src) {}
+
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    Image image = Image();
+    FrameworkElement elm = image;
+    if (args.scrollarea) {
+        ScrollViewer scroll = ScrollViewer();
+        scroll.Content(image);
+        elm = scroll;
+    }
+
+    // create toolkit wrapper object and register destructor
+    UIElement uielm = image;
+    UiWidget* widget = new UiWidget(uielm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind variable
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
+    if (var) {
+        UiGeneric *value = (UiGeneric*)var->value;
+        value->obj = widget;
+        value->get = ui_image_get;
+        value->set = ui_image_set;
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(elm, true);
+
+    return widget;
+}
+
+extern "C" void* ui_image_get(UiGeneric *g) {
+
+
+    return NULL;
+}
+
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type) {
+    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
+        return 1;
+    }
+
+    UiImageSource *imgdata = (UiImageSource*)data;
+    if (g->value) {
+        UiImageSource *prevData = (UiImageSource*)g->value;
+        delete prevData;
+    }
+    g->value = imgdata;
+
+    UiWidget* widget = (UiWidget*)g->obj;
+    Image image = widget->uielement.as<Image>();
+    image.Source(imgdata->imgsrc);
+
+    return 0;
+}
+
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path) {
+    wchar_t* wpath = str2wstr(path, nullptr);
+    std::wstring wPath = wpath;
+    std::wstring uriPath = L"file:///" + wPath;
+    Uri uri{ uriPath };
+    
+    BitmapImage bitmapImage = BitmapImage();
+    bitmapImage.UriSource(uri);
+    ImageSource src = bitmapImage;
+
+    UiImageSource *imgdata = new UiImageSource(src);
+    obj->set(obj, imgdata, UI_IMAGE_OBJECT_TYPE);
+
+    free(wpath);
+
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/image.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,43 @@
+/*
+* 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.
+*/
+
+#pragma once
+
+#include "../ui/toolkit.h"
+#include "../ui/image.h"
+
+class UiImageSource {
+public:
+    winrt::Microsoft::UI::Xaml::Media::ImageSource imgsrc { nullptr };
+
+    UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src);
+};
+
+
+extern "C" void* ui_image_get(UiGeneric *g);
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/label.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,199 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "label.h"
+#include "text.h"
+#include "util.h"
+
+#include "toolkit.h"
+#include "container.h"
+#include "../common/object.h"
+#include "../common/context.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    TextBlock label = TextBlock();
+    if (args.label) {
+        wchar_t* wlabel = str2wstr(args.label, nullptr);
+        label.Text(wlabel);
+        free(wlabel);
+    }
+
+    UIElement elm = label;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_label_get;
+        value->set = ui_label_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add label to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(label, false);
+
+    return widget;
+}
+
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_LEFT;
+    return ui_label_create(obj, args);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_RIGHT;
+    return ui_label_create(obj, args);
+}
+
+
+
+char* ui_label_get(UiString* str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBlock box = widget->uielement.as<TextBlock>();
+    std::wstring wstr(box.Text());
+    return ui_wstring_get(str, wstr);
+}
+
+void  ui_label_set(UiString* str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBlock box = widget->uielement.as<TextBlock>();
+    box.Text(ui_wstring_set(str, newvalue));
+}
+
+
+// -------------------- progressbar -------------------------
+
+UIWIDGET ui_progressbar_create(UiObject* obj, UiProgressbarArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    ProgressBar progressbar = ProgressBar();
+    progressbar.Minimum(args.min);
+    progressbar.Maximum(args.max == 0 ? 100 : args.max);
+    if (args.width > 0) {
+        progressbar.Width(args.width);
+    }
+
+    UIElement elm = progressbar;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+    if (var) {
+        UiDouble* value = (UiDouble*)var->value;
+        value->obj = widget;
+        value->get = ui_progressbar_get;
+        value->set = ui_progressbar_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(progressbar, false);
+
+    return widget;
+}
+
+double ui_progressbar_get(UiDouble * d) {
+    UiWidget* widget = (UiWidget*)d->obj;
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();
+    d->value = progressbar.Value();
+    return d->value;
+}
+
+void  ui_progressbar_set(UiDouble * d, double newvalue) {
+    UiWidget* widget = (UiWidget*)d->obj;
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();
+    d->value = newvalue;
+    progressbar.Value(newvalue);
+}
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    ProgressRing spinner = ProgressRing();
+    spinner.IsActive(false);
+
+    UIElement elm = spinner;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+    if (var) {
+        UiInteger* value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = ui_progressspinner_get;
+        value->set = ui_progressspinner_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(spinner, false);
+
+    return widget;
+}
+
+int64_t ui_progressspinner_get(UiInteger * i) {
+    UiWidget* widget = (UiWidget*)i->obj;
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();
+    i->value = spinner.IsActive();
+    return i->value;
+}
+
+void  ui_progressspinner_set(UiInteger * i, int64_t newvalue) {
+    UiWidget* widget = (UiWidget*)i->obj;
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();
+    i->value = newvalue != 0 ? 1 : 0;
+    spinner.IsActive(i->value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/label.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,42 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+
+#pragma once
+
+#include "../ui/toolkit.h"
+#include "../ui/display.h"
+
+extern "C" char* ui_label_get(UiString * str);
+extern "C" void  ui_label_set(UiString * str, const char* newvalue);
+
+extern "C" double ui_progressbar_get(UiDouble *d);
+extern "C" void  ui_progressbar_set(UiDouble *d, double newvalue);
+
+extern "C" int64_t ui_progressspinner_get(UiInteger * i);
+extern "C" void  ui_progressspinner_set(UiInteger * i, int64_t newvalue);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/list.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,346 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "list.h"
+#include "container.h"
+#include "util.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Microsoft::UI::Xaml::Media;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    ListView listview = ListView();
+    if (args.multiselection) {
+        listview.SelectionMode(ListViewSelectionMode::Extended);
+    }
+
+    bool clickEnabled = listview.IsItemClickEnabled();
+    listview.IsItemClickEnabled(true);
+    
+
+    UIElement elm = listview;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_simple_list_update;
+        list->getselection = ui_listview_getselection;
+        list->setselection = ui_listview_setselection;
+        list->obj = widget;
+
+        ui_simple_list_update(list, 0);
+    }
+
+    if (args.onselection) {
+        ui_callback onselection = args.onselection;
+        void* cbdata = args.onselectiondata;
+        listview.SelectionChanged([onselection, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());
+
+            UiListSelection selection;
+            selection.rows = selectedRows.data();
+            selection.count = selectedRows.size();
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = 0;
+            onselection(&evt, cbdata);
+            });
+    }
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        listview.ItemClick([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());
+            UiListSelection selection;
+            selection.rows = selectedRows.data();
+            selection.count = selectedRows.size();
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = 0;
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(listview, false);
+
+    return widget;
+}
+
+
+UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    ComboBox combobox = ComboBox();
+
+    UIElement elm = combobox;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_simple_list_update;
+        list->getselection = ui_dropdown_getselection;
+        list->setselection = ui_dropdown_setselection;
+        list->obj = widget;
+        ui_simple_list_update(list, 0);
+    }
+
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        combobox.SelectionChanged([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            int selectedrow = sender.as<ComboBox>().SelectedIndex();
+            UiListSelection selection;
+            selection.count = 1;
+            selection.rows = &selectedrow;
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = selectedrow;
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(combobox, false);
+
+    return widget;
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+    UiWidget *widget = (UiWidget*)list->obj;
+    ListView listview = widget->uielement.as<ListView>();
+    std::vector<int> selectedRows = ui_create_listview_selection(listview);
+
+    UiListSelection selection = { NULL, 0 };
+    if (selectedRows.size() > 0) {
+        selection.count = selectedRows.size();
+        int *data = selectedRows.data();
+        selection.rows = (int*)calloc(selection.count, sizeof(int));
+        memcpy(selection.rows, data, selection.count);
+    }
+
+    return selection;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    if (selection.count > 0) {
+        ListView listview = widget->uielement.as<ListView>();
+        listview.SelectedIndex(selection.rows[0]);
+    }
+}
+
+UiListSelection ui_dropdown_getselection(UiList *list) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    ComboBox cb = widget->uielement.as<ComboBox>();
+    int index = cb.SelectedIndex();
+    UiListSelection selection = { NULL, 0 };
+    if (index >= 0) {
+        selection.rows = (int*)calloc(1, sizeof(int));
+        selection.count = 1;
+        selection.rows[0] = index;
+    }
+    return selection;
+}
+
+void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    if (selection.count > 0) {
+        ComboBox cb = widget->uielement.as<ComboBox>();
+        cb.SelectedIndex(selection.rows[0]);
+    }
+}
+
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    BreadcrumbBar bcbar = BreadcrumbBar();
+
+    UIElement elm = bcbar;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_breadcrumbbar_update;
+        list->obj = widget;
+        ui_breadcrumbbar_update(list, 0);
+    }
+
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        bcbar.ItemClicked([cb, cbdata, obj](IInspectable const& sender, BreadcrumbBarItemClickedEventArgs evtargs) {
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = nullptr;
+            evt.intval = evtargs.Index();
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(bcbar, false);
+
+    return widget;
+}
+
+static void* getstrvalue(void* elm, int ignore) {
+    return elm;
+}
+
+void ui_simple_list_update(UiList* list, int i) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    UiModel* model = (UiModel*)widget->data1;
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;
+    ItemsControl listview = widget->uielement.as<ItemsControl>();
+    auto items = listview.Items();
+
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)
+    if (getvalue == nullptr) {
+        if (model && model->getvalue) {
+            getvalue = model->getvalue;
+        } else {
+            getvalue = getstrvalue;
+        }
+    }
+
+    // add list elements to listview.Items
+    items.Clear();
+    void* elm = list->first(list);
+    while (elm) {
+        char* value = (char*)getvalue(elm, 0);
+        wchar_t* wstr = str2wstr(value, nullptr);
+        items.Append(box_value(wstr));
+        free(wstr);
+
+        elm = list->next(list);
+    }
+}
+
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    UiModel* model = (UiModel*)widget->data1;
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;
+
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)
+    if (getvalue == nullptr) {
+        if (model && model->getvalue) {
+            getvalue = model->getvalue;
+        }
+        else {
+            getvalue = getstrvalue;
+        }
+    }
+
+    BreadcrumbBar bar = widget->uielement.as<BreadcrumbBar>();
+    
+    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> items { winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };
+    void* elm = list->first(list);
+    while (elm) {
+        char* value = (char*)getvalue(elm, 0);
+        wchar_t* wstr = str2wstr(value, nullptr);
+        items.Append(box_value(wstr));
+        free(wstr);
+
+        elm = list->next(list);
+    }
+
+    bar.ItemsSource(items);
+}
+
+
+std::vector<int> ui_create_listview_selection(ListView listview) {
+    std::vector<int> selection;
+    int p = 0;
+    auto ranges = listview.SelectedRanges();
+    for (auto range : ranges) {
+        int begin = range.FirstIndex();
+        int end = range.LastIndex();
+        for (int i = begin; i <= end; i++) {
+            selection.push_back(i);
+        }
+    }
+    return selection;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/list.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+
+#include "../ui/container.h"
+
+
+extern "C" void ui_simple_list_update(UiList * list, int i);
+
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i);
+
+std::vector<int> ui_create_listview_selection(winrt::Microsoft::UI::Xaml::Controls::ListView listview);
+
+extern "C" UiListSelection ui_listview_getselection(UiList *list);
+extern "C" void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+extern "C" UiListSelection ui_dropdown_getselection(UiList *list);
+extern "C" void ui_dropdown_setselection(UiList *list, UiListSelection selection);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/packages.config	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.240405.15" targetFramework="native" />
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240803.1" targetFramework="native" />
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.1742" targetFramework="native" />
+  <package id="Microsoft.WindowsAppSDK" version="1.5.241001000" targetFramework="native" />
+</packages>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/pch.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/pch.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+#include <windows.h>
+#include <unknwn.h>
+#include <restrictederrorinfo.h>
+#include <hstring.h>
+
+// Undefine GetCurrentTime macro to prevent
+// conflict with Storyboard::GetCurrentTime
+#undef GetCurrentTime
+
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.ApplicationModel.Activation.h>
+#include <winrt/Microsoft.UI.Composition.h>
+#include <winrt/Microsoft.UI.Windowing.h>
+#include <winrt/Microsoft.UI.Xaml.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.Data.h>
+#include <winrt/Microsoft.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+#include <winrt/Microsoft.UI.Xaml.Media.h>
+#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>
+#include <winrt/Microsoft.UI.Xaml.Navigation.h>
+#include <winrt/Microsoft.UI.Xaml.Shapes.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Dispatching.h>
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>
+#include <wil/cppwinrt_helpers.h>
+#include <winrt/Microsoft.UI.Xaml.Input.h>
+#include <winrt/Microsoft.UI.Input.h>
+#include <winrt/Windows.UI.Core.h>
+#include <winrt/Windows.ApplicationModel.h>
+#include <winrt/Windows.Storage.Pickers.h>
+
+#include <winrt\Microsoft.UI.Dispatching.h>
+
+#include <winrt/Windows.Storage.Streams.h>
+
+#include <Microsoft.UI.Xaml.Window.h>
+
+#include <shobjidl_core.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/readme.txt	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,27 @@
+========================================================================
+    winui Project Overview
+========================================================================
+
+This project demonstrates how to get started writing WinUI3 apps directly
+with standard C++, using the Windows App SDK and C++/WinRT packages and
+XAML compiler support to generate implementation headers from interface
+(IDL) files. These headers can then be used to implement the local
+Windows Runtime classes referenced in the app's XAML pages.
+
+Steps:
+1. Create an interface (IDL) file to define any local Windows Runtime
+    classes referenced in the app's XAML pages.
+2. Build the project once to generate implementation templates under
+    the "Generated Files" folder, as well as skeleton class definitions
+    under "Generated Files\sources".
+3. Use the skeleton class definitions for reference to implement your
+    Windows Runtime classes.
+
+========================================================================
+Learn more about Windows App SDK here:
+https://docs.microsoft.com/windows/apps/windows-app-sdk/
+Learn more about WinUI3 here:
+https://docs.microsoft.com/windows/apps/winui/winui3/
+Learn more about C++/WinRT here:
+http://aka.ms/cppwinrt/
+========================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/stock.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,5 @@
+
+
+#include "pch.h"
+
+#include "stock.h"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/stock.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "../ui/stock.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/table.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,648 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "table.h"
+#include "container.h"
+#include "util.h"
+#include "icons.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "../common/types.h"
+
+#include <winrt/Microsoft.UI.Xaml.Data.h>
+#include <winrt/Microsoft.UI.Xaml.Media.h>
+#include <winrt/Microsoft.UI.Xaml.Input.h>
+#include <winrt/Windows.UI.Core.h>
+#include <winrt/Windows.ApplicationModel.h>
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media;
+using namespace winrt::Windows::UI::Xaml::Input;
+
+static UINT ui_double_click_time = GetDoubleClickTime();
+
+extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) {
+	// TODO:
+}
+
+static void textblock_set_str(TextBlock& t, const char* str) {
+	if (str) {
+		wchar_t* wstr = str2wstr(str, nullptr);
+		t.Text(winrt::hstring(wstr));
+		free(wstr);
+	}
+}
+
+static void textblock_set_int(TextBlock& t, int i) {
+	wchar_t buf[16];
+	swprintf(buf, 16, L"%d", i);
+	t.Text(winrt::hstring(buf));
+}
+
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) {
+	if (!args.model) {
+		return nullptr;
+	}
+
+	UiObject* current = uic_current_obj(obj);
+
+	// create widgets and wrapper obj
+	ScrollViewer scrollW = ScrollViewer();
+	Grid grid = Grid();
+	scrollW.Content(grid);
+	UiTable* uitable = new UiTable(obj, scrollW, grid);
+	reg_table_destructor(current->ctx, uitable);
+	
+	uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue;
+	uitable->onselection = args.onselection;
+	uitable->onselectiondata = args.onselectiondata;
+	uitable->onactivate = args.onactivate;
+	uitable->onactivatedata = args.onactivatedata;
+	uitable->ondragstart = args.ondragstart;
+	uitable->ondragstartdata = args.ondragstartdata;
+	uitable->ondragcomplete = args.ondragcomplete;
+	uitable->ondrop = args.ondrop;
+	uitable->ondropdata = args.ondropsdata;
+
+	// grid styling
+	winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color
+	SolidColorBrush brush = SolidColorBrush(bg);
+	grid.Background(brush);
+
+	// add columns from args.model
+	uitable->add_header(args.model);
+	
+	// bind var
+	UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+	if (var) {
+		UiList* list = (UiList*)var->value;
+		list->update = ui_table_update;
+		list->getselection = ui_table_selection;
+		list->obj = uitable;
+		uitable->update(list, 0);
+	}
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = scrollW;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// add scrollW to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(scrollW, false);
+
+	return widget;
+}
+
+extern "C" void ui_table_update(UiList * list, int i) {
+	UiTable* table = (UiTable*)list->obj;
+	table->clear();
+	table->update(list, i);
+}
+
+extern "C" UiListSelection ui_table_selection(UiList * list) {
+	UiTable* table = (UiTable*)list->obj;
+	return table->uiselection();
+}
+
+UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) {
+	this->obj = obj;
+
+	this->scrollw = scrollw;
+	this->grid = grid;
+
+	winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 };
+	highlightBrush = SolidColorBrush(highlightBg);
+
+	winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default
+	defaultBrush = SolidColorBrush(defaultBg);
+
+	winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color
+	selectedBrush = SolidColorBrush(selectedBg);
+
+	winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color
+	selectedBorderBrush = SolidColorBrush(selectedFg);
+
+	grid.KeyDown(
+		winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(
+			[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) {
+				// key event for hanling the table cursor or enter
+			})
+	);
+}
+
+UiTable::~UiTable() {
+	ui_model_free(NULL, model);
+}
+
+void UiTable::add_header(UiModel* model) {
+	this->model = ui_model_copy(NULL, model);
+
+	GridLength gl;
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+
+	// add header row definition
+	auto headerRowDef = RowDefinition();
+	headerRowDef.Height(gl);
+	grid.RowDefinitions().Append(headerRowDef);
+
+	winrt::Windows::UI::Color borderColor = { 63, 0, 0, 0 };
+	SolidColorBrush borderBrush = SolidColorBrush(borderColor);
+
+
+	for (int i = 0; i < model->columns;i++) {
+		char* title = model->titles[i];
+		UiModelType type = model->types[i];
+
+		// add grid column definition
+		auto colDef = ColumnDefinition();
+		colDef.Width(gl);
+		grid.ColumnDefinitions().Append(colDef);
+
+		// header column border
+		Border headerBorder = Border();
+		Thickness border = { 0,0,1,0 };
+		headerBorder.BorderThickness(border);
+		headerBorder.BorderBrush(borderBrush);
+
+		// add text
+		auto hLabel = TextBlock();
+		textblock_set_str(hLabel, title);
+		Thickness cellpadding = { 10,4,4,4 };
+		hLabel.Padding(cellpadding);
+		hLabel.VerticalAlignment(VerticalAlignment::Stretch);
+
+		// event handler for highlighting and column resizing
+		headerBorder.PointerPressed(
+			winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+				[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+					// the last column doesn't need resize capabilities
+					if (i + 1 < model->columns) {
+						double width = headerBorder.ActualWidth();
+						auto point = args.GetCurrentPoint(headerBorder);
+						auto position = point.Position();
+						if (position.X + 4 >= width) {
+							this->resize = true;
+							this->resizedCol = headerBorder;
+						}
+					}
+				})
+		);
+		headerBorder.PointerReleased(
+			winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+				[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+					this->resize = false;
+				})
+		);
+		headerBorder.PointerMoved(
+			winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+				[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+					if (this->resize) {
+						auto point = args.GetCurrentPoint(this->resizedCol);
+						auto position = point.Position();
+						if (position.X > 1) {
+							this->resizedCol.Width(position.X);
+						}
+					}
+				})
+		);
+		headerBorder.PointerEntered(
+			winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+				[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+					// TODO: background
+				})
+		);
+		headerBorder.PointerExited(
+			winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+				[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+					// TODO: background
+				})
+		);
+
+
+
+		// add controls
+		headerBorder.Child(hLabel);
+
+		grid.SetColumn(headerBorder, i);
+		grid.SetRow(headerBorder, 0);
+		grid.Children().Append(headerBorder);
+
+		UiTableColumn h;
+		h.header = headerBorder;
+		header.push_back(h);
+	}
+
+	maxrows = 1;
+}
+
+static ULONG64 getsystime() {
+	SYSTEMTIME st;
+	GetSystemTime(&st);
+	return st.wYear   * 10000000000000 +
+		   st.wMonth  * 100000000000   +
+		   st.wDay    * 1000000000     +
+		   st.wHour   * 10000000       +
+		   st.wMinute * 100000         +
+		   st.wSecond * 1000           +
+		   st.wMilliseconds;
+}
+
+void UiTable::update(UiList* list,  int i) {
+	if (getvalue == nullptr) {
+		return;
+	}
+
+	Thickness b1 = { 1, 1, 0, 1 }; // first col
+	Thickness b2 = { 0, 1, 0, 1 }; // middle
+	Thickness b3 = { 0, 1, 1, 1 }; // last col
+
+	GridLength gl;
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+
+	// iterate model
+	int row = 1;
+	void* elm = list->first(list);
+	while (elm) {
+		if (row >= maxrows) {
+			auto rowdef = RowDefinition();
+			rowdef.Height(gl);
+			grid.RowDefinitions().Append(rowdef);
+			maxrows = row;
+		}
+
+		Thickness cellpadding = { 10,0,4,0 };
+
+		// model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model
+		int model_col = 0;
+		for (int col = 0; col < header.size(); col++, model_col++) {
+			// create ui elements with the correct cell border
+			// dependeing on the column
+			Border cellBorder = Border();
+			cellBorder.Background(defaultBrush);
+			cellBorder.BorderBrush(defaultBrush);
+			if (col == 0) {
+				cellBorder.BorderThickness(b1);
+			}
+			else if (col + 1 == header.size()) {
+				cellBorder.BorderThickness(b3);
+			}
+			else {
+				cellBorder.BorderThickness(b2);
+			}
+
+			// dnd
+			if (ondragstart) {
+				cellBorder.CanDrag(true);
+				cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) {
+						UiDnD dnd;
+						dnd.evttype = 0;
+						dnd.dndstartargs = args;
+						dnd.dndcompletedargs = { nullptr };
+						dnd.drageventargs = { nullptr };
+						dnd.data = args.Data();
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dnd;
+						evt.intval = 0;
+					
+						this->ondragstart(&evt, this->ondragstartdata);
+					});
+				cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) {
+						UiDnD dnd;
+						dnd.evttype = 1;
+						dnd.dndstartargs = { nullptr };
+						dnd.dndcompletedargs = args;
+						dnd.drageventargs = { nullptr };
+						dnd.data = { nullptr };
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dnd;
+						evt.intval = 0;
+
+						if (this->ondragcomplete) {
+							this->ondragcomplete(&evt, this->ondragcompletedata);
+						}
+					});
+			}
+			if (ondrop) {
+				cellBorder.AllowDrop(true);
+				cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){
+						UiDnD dnd;
+						dnd.evttype = 2;
+						dnd.dndstartargs = { nullptr };
+						dnd.dndcompletedargs = { nullptr };
+						dnd.drageventargs = args;
+						dnd.dataview = args.DataView();
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dnd;
+						evt.intval = 0;
+
+						this->ondrop(&evt, this->ondropdata);
+					}));
+				cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){
+					args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);
+					}));
+			}
+
+			// set the cell value
+			// depending on the type, we create different cell controls
+			UiModelType type = model->types[col];
+			switch (type) {
+				case UI_STRING_FREE:
+				case UI_STRING: {
+					TextBlock cell = TextBlock();
+					cell.Padding(cellpadding);
+					cell.VerticalAlignment(VerticalAlignment::Stretch);
+					char *val = (char*)getvalue(elm, model_col);
+					textblock_set_str(cell, val);
+					cellBorder.Child(cell);
+					if (type == UI_STRING_FREE && val) {
+						free(val);
+					}
+
+					break;
+				}
+				case UI_INTEGER: {
+					TextBlock cell = TextBlock();
+					cell.Padding(cellpadding);
+					cell.VerticalAlignment(VerticalAlignment::Stretch);
+					int *value = (int*)getvalue(elm, model_col);
+					if (value) {
+						textblock_set_int(cell, *value);
+					}
+					cellBorder.Child(cell);
+					break;
+				}
+				case UI_ICON: {
+					UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col);
+					if (iconConstr) {
+						IconElement icon = iconConstr->getIcon();
+						cellBorder.Child(icon);
+					}
+					break;
+				}
+				case UI_ICON_TEXT_FREE:
+				case UI_ICON_TEXT: {
+					StackPanel cellPanel = StackPanel();
+					cellPanel.Spacing(2);
+					cellPanel.Padding(cellpadding);
+					cellPanel.VerticalAlignment(VerticalAlignment::Stretch);
+
+					cellPanel.Orientation(Orientation::Horizontal);
+					UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++);
+					char* str = (char*)getvalue(elm, model_col);
+					if (iconConstr) {
+						IconElement icon = iconConstr->getIcon();
+						cellPanel.Children().Append(icon);
+					}
+					TextBlock cell = TextBlock();
+					textblock_set_str(cell, str);
+					cellPanel.Children().Append(cell);
+					cellBorder.Child(cellPanel);
+					if (type == UI_ICON_TEXT_FREE && str) {
+						free(str);
+					}
+					break;
+				}
+			}
+
+			// event handler
+			cellBorder.PointerPressed(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers();
+						bool update_selection = true;
+
+						if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) {
+							// add/remove current row
+							if (!is_row_selected(row)) {
+								row_background(row, selectedBrush, selectedBorderBrush);
+								selection.push_back(row);
+							}
+							else {
+								row_background(row, highlightBrush, highlightBrush);
+								remove_from_selection(row);
+							}
+						}
+						else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) {
+							// no modifier or shift is pressed but there is no selection
+							if (selection.size() > 0) {
+								change_rows_bg(selection, defaultBrush, defaultBrush);
+							}
+							
+							row_background(row, selectedBrush, selectedBorderBrush);
+							selection = { row };
+							if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) {
+								SYSTEMTIME st;
+								GetSystemTime(&st);
+								
+								ULONG64 now = getsystime();
+								ULONG64 tdiff = now - lastPointerPress;
+								if (tdiff < ui_double_click_time && onactivate != nullptr) {
+									// two pointer presse events in short time and we have an onactivate handler
+									update_selection = false; // we don't want an additional selection event
+									lastPointerPress = 0; // reset double-click
+
+									int selectedrow = row - 1; // subtract header row
+
+									UiListSelection selection;
+									selection.count = 1;
+									selection.rows = &selectedrow;
+
+									UiEvent evt;
+									evt.obj = obj;
+									evt.window = obj->window;
+									evt.document = obj->ctx->document;
+									evt.eventdata = &selection;
+									evt.intval = selectedrow;
+									onactivate(&evt, onactivatedata);
+								}
+								else {
+									lastPointerPress = now;
+								}
+							}
+						}
+						else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) {
+							// select everything between the first selection and the current row
+							std::sort(selection.begin(), selection.end());
+							int first = selection.front();
+							int last = row;
+							if (first > row) {
+								last = first;
+								first = row;
+							}
+
+							// clear previous selection
+							change_rows_bg(selection, defaultBrush, defaultBrush);
+
+							// create new selection
+							std::vector<int> newselection;
+							for (int s = first; s <= last; s++) {
+								newselection.push_back(s);
+							}
+							selection = newselection;
+							change_rows_bg(selection, selectedBrush, selectedBorderBrush);
+						}
+
+						if (update_selection) {
+							call_handler(onselection, onselectiondata);
+						}
+					})
+			);
+			cellBorder.PointerReleased(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+
+					})
+			);
+			cellBorder.PointerEntered(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						if (!is_row_selected(row)) {
+							row_background(row, highlightBrush, highlightBrush);
+						}
+					})
+			);
+			cellBorder.PointerExited(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						if (!is_row_selected(row)) {
+							row_background(row, defaultBrush, defaultBrush);
+						}
+					})
+			);
+
+			grid.SetColumn(cellBorder, col);
+			grid.SetRow(cellBorder, row);
+			grid.Children().Append(cellBorder);
+		}
+
+		row++;
+		elm = list->next(list);
+	}
+}
+
+void UiTable::clear() {
+	for (int i = grid.Children().Size()-1; i >= 0; i--) {
+		FrameworkElement elm = grid.Children().GetAt(i).as<FrameworkElement>();
+		int child_row = grid.GetRow(elm);
+		if (child_row > 0) {
+			grid.Children().RemoveAt(i);
+		}
+	}
+
+	// TODO: should we clean row definitions?
+}
+
+void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {
+	Thickness b1 = { 1, 1, 0, 1 }; // first col
+	Thickness b2 = { 0, 1, 0, 1 }; // middle
+	Thickness b3 = { 0, 1, 1, 1 }; // last col
+	
+	for (auto child : grid.Children()) {
+		FrameworkElement elm = child.as<FrameworkElement>();
+		int child_row = grid.GetRow(elm);
+		if (child_row == row) {
+			Border b = elm.as<Border>();
+			b.Background(brush);
+			b.BorderBrush(borderBrush);
+		}
+	}
+}
+
+void UiTable::change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {
+	std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); });
+}
+
+bool UiTable::is_row_selected(int row) {
+	return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false;
+}
+
+void UiTable::remove_from_selection(int row) {
+	selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end());
+	selection.shrink_to_fit();
+}
+
+UiListSelection UiTable::uiselection() {
+	std::sort(selection.begin(), selection.end());
+
+	UiListSelection selobj;
+	selobj.count = selection.size();
+	selobj.rows = nullptr;
+	if (selobj.count > 0) {
+		selobj.rows = (int*)calloc(selobj.count, sizeof(int));
+		memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int));
+		for (int i = 0; i < selobj.count; i++) {
+			selobj.rows[i]--;
+		}
+	}
+	return selobj;
+}
+
+void UiTable::call_handler(ui_callback cb, void* cbdata) {
+	if (!cb) {
+		return;
+	}
+
+	UiListSelection selobj = uiselection();
+
+	UiEvent evt;
+	evt.obj = obj;
+	evt.window = obj->window;
+	evt.document = obj->ctx->document;
+	evt.eventdata = &selobj;
+	evt.intval = 0;
+	cb(&evt, cbdata);
+
+	if (selobj.rows) {
+		free(selobj.rows);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/table.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,98 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+#include "dnd.h"
+
+#include "../ui/container.h"
+
+
+typedef struct UiTableColumn {
+	winrt::Microsoft::UI::Xaml::Controls::Border header;
+
+} UiTableColumn;
+
+typedef struct UiTable {
+	winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollw;
+	winrt::Microsoft::UI::Xaml::Controls::Grid grid;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush defaultBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush highlightBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBorderBrush;
+
+	winrt::Microsoft::UI::Xaml::Controls::Border resizedCol{ nullptr };
+	bool resize = false;
+
+	UiObject* obj;
+	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;
+	UiModel* model = nullptr;
+	std::vector<UiTableColumn> header;
+	ui_getvaluefunc getvalue = nullptr;
+	int maxrows = 0;
+	int lastSelection = 0;
+	ULONG64 lastPointerPress = 0;
+	std::vector<int> selection;
+
+	UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid);
+
+	~UiTable();
+	
+	void add_header(UiModel* model);
+
+	void update(UiList* list, int i);
+
+	void clear();
+
+	void row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);
+
+	void change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);
+
+	bool is_row_selected(int row);
+
+	void remove_from_selection(int row);
+
+	UiListSelection uiselection();
+
+	void call_handler(ui_callback cb, void *cbdata);
+} UiTable;
+
+extern "C" void ui_table_update(UiList * list, int i);
+
+extern "C" UiListSelection ui_table_selection(UiList * list);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/text.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,609 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "text.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <cx/string.h>
+#include <cx/allocator.h>
+
+#include "util.h"
+#include "container.h"
+
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Microsoft::UI::Xaml::Media;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Windows::UI::Xaml::Input;
+
+
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textarea and toolkit wrapper
+    TextBox textarea = TextBox();
+    textarea.AcceptsReturn(true);
+    ScrollViewer::SetVerticalScrollBarVisibility(textarea, ScrollBarVisibility::Auto);
+    UIElement elm = textarea;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);
+    if (var) {
+        UiText* value = (UiText*)var->value;
+        value->obj = widget;
+        value->undomgr = NULL;
+        value->set = ui_textarea_set;
+        value->get = ui_textarea_get;
+        value->getsubstr = ui_textarea_getsubstr;
+        value->insert = ui_textarea_insert;
+        value->setposition = ui_textarea_setposition;
+        value->position = ui_textarea_position;
+        value->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->remove = ui_textarea_remove;
+    }
+
+    // add textarea to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(textarea, true);
+
+    return widget;
+}
+
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
+    return textarea;
+}
+
+UIEXPORT void ui_text_undo(UiText *value) {
+
+}
+
+UIEXPORT void ui_text_redo(UiText *value) {
+
+}
+
+// -------------------------- getter/setter for textarea UiText --------------------------
+
+char* ui_wtext_get(UiText *text, std::wstring &value) {
+    if (text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+
+    text->value.ptr = wchar2utf8(value.c_str(), value.length());
+    text->value.free = free;
+
+    return text->value.ptr;
+}
+
+std::wstring ui_wtext_set(UiText *text, const char* value) {
+    if (text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+
+    text->value.ptr = _strdup(value);
+    text->value.free = free;
+
+    int len;
+    wchar_t* wstr = str2wstr(value, &len);
+    std::wstring s(wstr);
+    free(wstr);
+
+    return s;
+}
+
+extern "C" char* ui_textarea_get(UiText *text) {
+    UiWidget* widget = (UiWidget*)text->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    std::wstring wstr(box.Text());
+    return ui_wtext_get(text, wstr);
+}
+
+extern "C" void  ui_textarea_set(UiText *text, const char *newvalue) {
+    UiWidget* widget = (UiWidget*)text->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    box.Text(ui_wtext_set(text, newvalue));
+}
+
+extern "C" char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    return NULL;
+}
+
+extern "C" void  ui_textarea_insert(UiText *text, int pos, char *str) {
+
+}
+
+extern "C" void  ui_textarea_setposition(UiText *text, int pos) {
+
+}
+
+extern "C" int   ui_textarea_position(UiText *text) {
+    return 0;
+}
+
+extern "C" void  ui_textarea_selection(UiText *text, int *begin, int *end) {
+
+}
+
+extern "C" int   ui_textarea_length(UiText *text) {
+    return 0;
+}
+
+extern "C" void  ui_textarea_remove(UiText *text, int begin, int end) {
+
+}
+
+
+
+
+UIWIDGET ui_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    TextBox textfield = TextBox();
+    UIElement elm = textfield;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+    
+    // add textfield to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(textfield, false);
+
+    return widget;
+}
+
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return ui_textfield_create(obj, args);
+}
+
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    PasswordBox textfield = PasswordBox();
+    UIElement elm = textfield;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+    ui_set_widget_groups(current->ctx, widget, args.groups);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_passwordfield_get;
+        value->set = ui_passwordfield_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add textfield to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(textfield, false);
+
+    return widget;
+}
+
+
+// -------------------------- getter/setter for textfield UiString --------------------------
+
+char* ui_wstring_get(UiString* str, std::wstring &value) {
+    if (str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+
+    str->value.ptr = wchar2utf8(value.c_str(), value.length());
+    str->value.free = free;
+
+    return str->value.ptr;
+}
+
+std::wstring ui_wstring_set(UiString* str, const char* value) {
+    if (str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+
+    str->value.ptr = _strdup(value);
+    str->value.free = free;
+
+    int len;
+    wchar_t* wstr = str2wstr(value, &len);
+    std::wstring s(wstr);
+    free(wstr);
+
+    return s;
+}
+
+char* ui_textfield_get(UiString * str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    std::wstring wstr(box.Text());
+    return ui_wstring_get(str, wstr);
+}
+
+void  ui_textfield_set(UiString * str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    box.Text(ui_wstring_set(str, newvalue));
+}
+
+
+char* ui_passwordfield_get(UiString * str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    PasswordBox box = widget->uielement.as<PasswordBox>();
+    std::wstring wstr(box.Password());
+    return ui_wstring_get(str, wstr);
+}
+
+void  ui_passwordfield_set(UiString * str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    PasswordBox box = widget->uielement.as<PasswordBox>();
+    box.Password(ui_wstring_set(str, newvalue));
+}
+
+
+// ------------------------ path textfield --------------------------------------
+
+extern "C" static void destroy_ui_pathtextfield(void* ptr) {
+    UiPathTextField* pb = (UiPathTextField*)ptr;
+    delete pb;
+}
+
+static void ui_context_add_pathtextfield_destructor(UiContext* ctx, UiPathTextField* pb) {
+    cxMempoolRegister(ctx->mp, pb, destroy_ui_pathtextfield);
+}
+
+static void ui_pathtextfield_clear(StackPanel& buttons) {
+    for (int i = buttons.Children().Size() - 1; i >= 0; i--) {
+        buttons.Children().RemoveAt(i);
+    }
+}
+
+static void ui_pathfield_free_pathelms(UiPathElm* elms, size_t nelm) {
+    if (!elms) {
+        return;
+    }
+    for (int i = 0; i < nelm; i++) {
+        UiPathElm e = elms[i];
+        free(e.name);
+        free(e.path);
+    }
+    free(elms);
+}
+
+UiPathTextField::~UiPathTextField() {
+    ui_pathfield_free_pathelms(this->current_path, this->current_path_nelms);
+}
+
+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 nullptr;
+    }
+
+    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;
+}
+
+int ui_pathtextfield_update(UiPathTextField* pb, const char *full_path) {
+    Grid grid = pb->grid;
+
+    ui_pathelm_func getpathelm = pb->getpathelm;
+    void* getpathelmdata = pb->getpathelmdata;
+
+    size_t full_path_len = full_path ? strlen(full_path) : 0;
+
+    size_t nelm = 0;
+    UiPathElm* path_elm = getpathelm(full_path, full_path_len, &nelm, getpathelmdata);
+    if (!path_elm) {
+        return 1;
+    }
+
+    // hide textbox, show button panel
+    pb->textbox.Visibility(Visibility::Collapsed);
+    pb->buttons.Visibility(Visibility::Visible);
+
+    // clear old buttons
+    ui_pathtextfield_clear(pb->buttons); 
+
+    ui_pathfield_free_pathelms(pb->current_path, pb->current_path_nelms);
+    pb->current_path = path_elm;
+    pb->current_path_nelms = nelm;
+
+    // add new buttons
+    int j = 0;
+    for (int i = 0; i < nelm;i++) {
+        UiPathElm elm = path_elm[i];
+        wchar_t* wstr = str2wstr_len(elm.name, elm.name_len, nullptr);
+        Button button = Button();
+        button.Content(box_value(wstr));
+        free(wstr);
+
+        if (pb->onactivate) {
+            button.Click([pb, j, elm](IInspectable const& sender, RoutedEventArgs) {
+                // copy elm.path because it could be a non-terminated string
+                cxmutstr elmpath = cx_strdup(cx_strn(elm.path, elm.path_len));
+
+                UiEvent evt;
+                evt.obj = pb->obj;
+                evt.window = evt.obj->window;
+                evt.document = evt.obj->ctx->document;
+                evt.eventdata = elmpath.ptr;
+                evt.intval = j;
+                pb->onactivate(&evt, pb->onactivatedata);
+
+                free(elmpath.ptr);
+                });
+        }
+
+        Thickness t = { 0, 0, 1, 0 };
+        CornerRadius c = { 0 ,0, 0, 0 };
+        button.BorderThickness(t);
+        button.CornerRadius(c);
+
+        pb->buttons.Children().Append(button);
+
+        j++;
+    }
+
+    return 0;
+}
+
+char* ui_path_textfield_get(UiString * str) {
+    UiPathTextField* widget = (UiPathTextField*)str->obj;
+    TextBox box = widget->textbox;
+    std::wstring wstr(box.Text());
+    return ui_wstring_get(str, wstr);
+}
+
+void  ui_path_textfield_set(UiString* str, const char* newvalue) {
+    UiPathTextField* widget = (UiPathTextField*)str->obj;
+    TextBox box = widget->textbox;
+    box.Text(ui_wstring_set(str, newvalue));
+    ui_pathtextfield_update(widget, newvalue);
+}
+
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create view and toolkit wrapper
+    Border pathbar = Border();
+
+    IInspectable bgRes = Application::Current().Resources().Lookup(box_value(L"TextControlBackground"));
+    IInspectable borderThicknessRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderThemeThickness"));
+    IInspectable borderBrushRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderBrush"));
+    // IInspectable cornerRes = Application::Current().Resources().Lookup(box_value(L"TextControlCornerRadius"));
+
+    Brush bgBrush = unbox_value<Brush>(bgRes);
+    Thickness border = unbox_value<Thickness>(borderThicknessRes);
+    Brush borderBrush = unbox_value<Brush>(borderBrushRes);
+    CornerRadius cornerRadius = { 4, 4, 4, 4 }; //unbox_value<CornerRadius>(cornerRes);
+
+    pathbar.Background(bgBrush);
+    pathbar.BorderBrush(borderBrush);
+    pathbar.BorderThickness(border);
+    pathbar.CornerRadius(cornerRadius);
+
+    Grid content = Grid();
+    pathbar.Child(content);
+
+    GridLength gl;
+    gl.Value = 0;
+    gl.GridUnitType = GridUnitType::Auto;
+
+    ColumnDefinition coldef = ColumnDefinition();
+    coldef.Width(gl);
+    content.ColumnDefinitions().Append(coldef);
+
+    gl.Value = 1;
+    gl.GridUnitType = GridUnitType::Star;
+
+    ColumnDefinition coldef2 = ColumnDefinition();
+    coldef2.Width(gl);
+    content.ColumnDefinitions().Append(coldef2);
+
+    TextBox pathTextBox = TextBox();
+    Thickness t = { 0, 0, 0, 0 };
+    CornerRadius c = { 0 ,0, 0, 0 };
+    pathTextBox.BorderThickness(t);
+    //pathTextBox.CornerRadius(c);
+
+
+    pathTextBox.HorizontalAlignment(HorizontalAlignment::Stretch);
+    content.SetColumn(pathTextBox, 0);
+    content.SetColumnSpan(pathTextBox, 2);
+
+    content.Children().Append(pathTextBox);
+
+    // stackpanel for buttons
+    StackPanel buttons = StackPanel();
+    buttons.Orientation(Orientation::Horizontal);
+    buttons.Visibility(Visibility::Collapsed);
+    content.SetColumn(buttons, 0);
+    content.Children().Append(buttons);
+
+    TextBlock filler = TextBlock();
+    filler.VerticalAlignment(VerticalAlignment::Stretch);
+    //filler.Text(winrt::hstring(L"hello filler"));
+
+    filler.HorizontalAlignment(HorizontalAlignment::Stretch);
+    filler.VerticalAlignment(VerticalAlignment::Stretch);
+    content.SetColumn(filler, 1);
+    content.Children().Append(filler);
+
+    filler.PointerPressed(
+        winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+            [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+                pathTextBox.Visibility(Visibility::Visible);
+                buttons.Visibility(Visibility::Collapsed);
+                filler.Visibility(Visibility::Collapsed);
+                pathTextBox.SelectionStart(pathTextBox.Text().size());
+                pathTextBox.SelectionLength(0);
+                pathTextBox.Focus(FocusState::Keyboard);
+            })
+    );
+
+    //pathTextBox.Visibility(Visibility::Collapsed);
+
+    UiPathTextField* uipathbar = new UiPathTextField;
+    ui_context_add_pathtextfield_destructor(current->ctx, uipathbar);
+    uipathbar->grid = content;
+    uipathbar->buttons = buttons;
+    uipathbar->textbox = pathTextBox;
+    uipathbar->filler = filler;
+    uipathbar->obj = obj;
+    uipathbar->getpathelm = args.getpathelm ? args.getpathelm : default_pathelm_func;
+    uipathbar->getpathelmdata = args.getpathelmdata;
+    uipathbar->onactivate = args.onactivate;
+    uipathbar->onactivatedata = args.onactivatedata;
+    uipathbar->ondragstart = args.ondragstart;
+    uipathbar->ondragstartdata = args.ondragstartdata;
+    uipathbar->ondragcomplete = args.ondragcomplete;
+    uipathbar->ondragcompletedata = args.ondragcompletedata;
+    uipathbar->ondrop = args.ondrop;
+    uipathbar->ondropdata = args.ondropsdata;
+
+
+    pathTextBox.KeyDown(
+        winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(
+            [=](winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) {
+                auto key = e.Key();
+                bool showButtons = false;
+                bool update = false;
+                if (key == Windows::System::VirtualKey::Escape) {
+                    showButtons = true;
+                }
+                else if (key == Windows::System::VirtualKey::Enter) {
+                    showButtons = true;
+                    update = true;
+                }
+
+                if (showButtons) {
+                    pathTextBox.Visibility(Visibility::Collapsed);
+                    buttons.Visibility(Visibility::Visible);
+                    filler.Visibility(Visibility::Visible);
+                    if (update) {
+                        std::wstring value(pathTextBox.Text());
+                        char* full_path = wchar2utf8(value.c_str(), value.length());
+
+                        if (!ui_pathtextfield_update(uipathbar, full_path)) {
+                            UiEvent evt;
+                            evt.obj = obj;
+                            evt.window = obj->window;
+                            evt.document = obj->ctx->document;
+                            evt.eventdata = full_path;
+                            evt.intval = -1;
+                            args.onactivate(&evt, args.onactivatedata);
+                        } 
+
+                        free(full_path);
+                    }
+
+                    //buttons.Focus(FocusState::Keyboard);
+                }
+            })
+    );
+
+
+    UIElement elm = pathbar;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = uipathbar;
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = uipathbar;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(pathbar, false);
+
+    return widget;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/text.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,86 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/text.h"
+#include "toolkit.h"
+
+#include "../ui/container.h"
+
+struct UiPathTextField {
+    winrt::Microsoft::UI::Xaml::Controls::Grid grid = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::StackPanel buttons = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::TextBox textbox = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::TextBlock filler = { nullptr };
+
+    ~UiPathTextField();
+
+    UiPathElm* current_path = nullptr;
+    size_t current_path_nelms = 0;
+
+    UiObject* obj;
+
+    ui_pathelm_func getpathelm;
+    void* getpathelmdata;
+    
+    ui_callback onactivate;
+    void* onactivatedata;
+
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropdata;
+};
+
+char* ui_wtext_get(UiText *text, std::wstring &value);
+std::wstring ui_wtext_set(UiText *text, const char* value);
+
+char* ui_wstring_get(UiString* str, std::wstring& value);
+std::wstring ui_wstring_set(UiString* str, const char* value);
+
+extern "C" char* ui_textarea_get(UiText *text);
+extern "C" void  ui_textarea_set(UiText *text, const char *newvalue);
+extern "C" char* ui_textarea_getsubstr(UiText*, int, int);
+extern "C" void  ui_textarea_insert(UiText*, int, char*);
+extern "C" void  ui_textarea_setposition(UiText*,int);
+extern "C" int   ui_textarea_position(UiText*);
+extern "C" void  ui_textarea_selection(UiText*, int*, int*);
+extern "C" int   ui_textarea_length(UiText*);
+extern "C" void  ui_textarea_remove(UiText*, int, int);
+
+extern "C" char* ui_textfield_get(UiString *str);
+extern "C" void  ui_textfield_set(UiString *str, const char *newvalue);
+
+extern "C" char* ui_passwordfield_get(UiString * str);
+extern "C" void  ui_passwordfield_set(UiString * str, const char* newvalue);
+
+extern "C" char* ui_path_textfield_get(UiString * str);
+extern "C" void  ui_path_textfield_set(UiString * str, const char* newvalue);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/toolkit.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,382 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "toolkit.h"
+
+#include <cx/allocator.h>
+#include <cx/mempool.h>
+
+#include "../common/context.h"
+#include "../common/document.h"
+#include "../common/toolbar.h"
+#include "../common/properties.h"
+
+#include "icons.h"
+
+#include "MainWindow.xaml.h"
+
+#include "App.xaml.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace Windows::UI::Core;
+
+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;
+
+
+static UiObject* active_window;
+
+static winrt::Microsoft::UI::Dispatching::DispatcherQueue uiDispatcherQueue = { nullptr };
+
+void ui_app_run_startup() {
+	uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();
+	
+	if (startup_func) {
+		startup_func(NULL, startup_data);
+	}
+}
+
+class App : public ApplicationT<App, IXamlMetadataProvider> {
+public:
+	void OnLaunched(LaunchActivatedEventArgs const&) {
+		Resources().MergedDictionaries().Append(XamlControlsResources());
+		if (startup_func) {
+			startup_func(NULL, startup_data);
+		}
+
+		//auto window = make<winui::implementation::MainWindow>();
+		//window.Activate();
+	}
+	IXamlType GetXamlType(TypeName const& type) {
+		return provider.GetXamlType(type);
+	}
+	IXamlType GetXamlType(hstring const& fullname) {
+		return provider.GetXamlType(fullname);
+	}
+	com_array<XmlnsDefinition> GetXmlnsDefinitions() {
+		return provider.GetXmlnsDefinitions();
+	}
+private:
+	XamlControlsXamlMetaDataProvider provider;
+};
+
+UiWidget::UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm) : uielement(elm) {}
+
+extern "C" void destroy_ui_window_wrapper(void* ptr) {
+	UiWindow* win = (UiWindow*)ptr;
+	delete win;
+}
+
+extern "C" void destroy_ui_widget_wrapper(void* ptr) {
+	UiWidget* widget = (UiWidget*)ptr;
+	delete widget;
+}
+
+extern "C" void destroy_ui_container_wrapper(void* ptr) {
+	UiContainer* ctn = (UiContainer*)ptr;
+	delete ctn;
+}
+
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win) {
+	cxMempoolRegister(ctx->mp, win, destroy_ui_window_wrapper);
+}
+
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget) {
+	cxMempoolRegister(ctx->mp, widget, destroy_ui_widget_wrapper);
+}
+
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container) {
+	cxMempoolRegister(ctx->mp, container, destroy_ui_container_wrapper);
+}
+
+
+UiEvent ui_create_int_event(UiObject* obj, int64_t i) {
+	UiEvent evt;
+	evt.obj = obj;
+	evt.window = obj->window;
+	evt.document = obj->ctx->document;
+	evt.eventdata = nullptr;
+	evt.intval = i;
+	return evt;
+}
+
+
+#include <MddBootstrap.h>
+
+void ui_appsdk_bootstrap(void) {
+	const UINT32 majorMinorVersion{ 0x00010002 };
+	PCWSTR versionTag{ L"" };
+	const PACKAGE_VERSION minVersion{};
+
+	const HRESULT hr = MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion);
+	if (FAILED(hr)) {
+		exit(102);
+	}
+}
+
+void ui_init(const char* appname, int argc, char** argv) {
+	application_name = appname;
+
+	//ui_appsdk_bootstrap();
+
+	uic_init_global_context();
+	uic_docmgr_init();
+        uic_menu_init();
+	uic_toolbar_init();
+	
+	uic_load_app_properties();
+}
+
+const char* ui_appname() {
+	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();
+	//Application::Start([](auto&&) {make<App>(); });
+
+	::winrt::Microsoft::UI::Xaml::Application::Start(
+		[](auto&&)
+		{
+			::winrt::make<::winrt::winui::implementation::App>();
+		});
+		*/
+	{
+		void (WINAPI * pfnXamlCheckProcessRequirements)();
+		auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll");
+		if (module)
+		{
+			pfnXamlCheckProcessRequirements = reinterpret_cast<decltype(pfnXamlCheckProcessRequirements)>(GetProcAddress(module, "XamlCheckProcessRequirements"));
+			if (pfnXamlCheckProcessRequirements)
+			{
+				(*pfnXamlCheckProcessRequirements)();
+			}
+
+			::FreeLibrary(module);
+		}
+	}
+
+	winrt::init_apartment(winrt::apartment_type::single_threaded);
+	::winrt::Microsoft::UI::Xaml::Application::Start(
+		[](auto&&)
+		{
+			::winrt::make<::winrt::winui::implementation::App>();
+		});
+}
+
+class UiWin {
+public:
+	Window window;
+};
+
+void ui_show(UiObject* obj) {
+	if (obj->wobj) {
+		obj->wobj->window.Activate();
+	} else if(obj->widget && obj->widget->Show) {
+		obj->widget->Show();
+	}
+}
+
+void ui_close(UiObject* obj) {
+	if (obj->wobj) {
+		obj->wobj->window.Close();
+	}
+}
+
+static void ui_job_thread(UiJob* job) {
+	if (!job->job_func(job->job_data) && job->finish_callback) {
+		bool isQueued = uiDispatcherQueue.TryEnqueue([job]()
+		{
+			UiEvent event;
+			event.obj = job->obj;
+			event.window = job->obj->window;
+			event.document = job->obj->ctx->document;
+			event.intval = 0;
+			event.eventdata = NULL;
+			job->finish_callback(&event, job->finish_data);
+			delete job;
+		});
+		if (!isQueued) {
+			// TODO: error or try again?
+			exit(-1);
+		}
+	}
+	else {
+		delete job;
+	}
+}
+
+UIEXPORT void ui_job(UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {
+	UiJob* job = new UiJob;
+	job->obj = obj;
+	job->job_func = tf;
+	job->job_data = td;
+	job->finish_callback = f;
+	job->finish_data = fd;
+
+	std::thread jobThread(ui_job_thread, job);
+	jobThread.detach();
+}
+
+UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td) {
+	bool isQueued = uiDispatcherQueue.TryEnqueue([tf, td]()
+	{
+		(void)tf(td);
+	});
+	if (!isQueued) {
+		// TODO: error or try again?
+		exit(-1);
+	}
+}
+
+static UiJob kill_job; // &kill_job indicates to stop the thread
+
+static void ui_threadpool_run(UiThreadpool* pool) {
+	for (;;) {
+		UiJob* job = pool->GetJob();
+		if (job == &kill_job) {
+			return;
+		}
+		else if (job) {
+			ui_job_thread(job);
+		}
+	}
+}
+
+UiThreadpool::UiThreadpool(int nthreads) {
+	for (int i = 0; i < nthreads; i++) {
+		std::thread thread(ui_threadpool_run, this);
+		thread.detach();
+	}
+}
+
+void UiThreadpool::EnqueueJob(UiJob* job)
+{
+	std::unique_lock<std::mutex> lock(mutex);
+	queue.push(job);
+	lock.unlock();
+	condition.notify_one();
+}
+
+UiJob* UiThreadpool::GetJob() {
+	std::unique_lock<std::mutex> lock(mutex);
+
+	UiJob* job = nullptr;
+	while (!job) {
+		if (queue.empty()) {
+			condition.wait(lock);
+			continue;
+		}
+		else
+		{
+			job = queue.front();
+			queue.pop();
+		}
+	}
+
+	return job;
+}
+
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads) {
+	return new UiThreadpool(nthreads);
+}
+
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool) {
+	// TODO
+}
+
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {
+	UiJob* job = new UiJob;
+	job->obj = obj;
+	job->job_func = tf;
+	job->job_data = td;
+	job->finish_callback = f;
+	job->finish_data = fd;
+	pool->EnqueueJob(job);
+}
+
+
+
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups) {
+	if(!groups) {
+		return;
+	}
+	size_t ngroups = uic_group_array_size(groups);
+	ui_set_widget_ngroups(ctx, widget, groups, ngroups);
+}
+
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups) {
+	if(ngroups > 0) {
+		uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);
+		ui_set_enabled(widget, FALSE);
+	}
+}
+
+
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled) {
+	Control ctrl = widget->uielement.as<Control>();
+	if (ctrl) {
+		ctrl.IsEnabled(enabled);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/toolkit.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,72 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/toolkit.h"
+
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+typedef struct UiJob {
+    UiObject* obj;
+    ui_threadfunc job_func;
+    void* job_data;
+    ui_callback   finish_callback;
+    void* finish_data;
+} UiJob;
+
+struct UiThreadpool
+{
+	std::queue<UiJob*> queue;
+	std::mutex mutex;
+	std::condition_variable condition;
+
+	UiThreadpool(int nthreads);
+
+	void EnqueueJob(UiJob* job);
+
+	UiJob* GetJob();
+};
+
+typedef void(*ui_eventfunc)(void*, void*);
+
+void ui_app_run_startup();
+
+extern "C" void destroy_ui_window_wrapper(void* ptr);
+extern "C" void destroy_ui_widget_wrapper(void* ptr);
+
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win);
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget);
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container);
+
+UiEvent ui_create_int_event(UiObject* obj, int64_t i);
+
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups);
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/util.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,46 @@
+#include "pch.h"
+
+#include "util.h"
+
+#include <stdlib.h>
+
+
+wchar_t* str2wstr(const char* str, int* newlen) {
+    size_t len = strlen(str);
+
+    return str2wstr_len(str, len, newlen);
+}
+
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen) {
+    wchar_t* wstr = (wchar_t*)calloc(len + 1, sizeof(wchar_t));
+    int wlen = MultiByteToWideChar(
+        CP_UTF8,
+        0,
+        str,
+        len,
+        wstr,
+        len + 1
+    );
+    if (newlen) {
+        *newlen = wlen;
+    }
+    wstr[wlen] = 0;
+
+    return wstr;
+}
+
+char* wchar2utf8(const wchar_t* wstr, size_t wlen) {
+    size_t maxlen = wlen * 4;
+    char* ret = (char*)malloc(maxlen + 1);
+    int ret_len = WideCharToMultiByte(
+        CP_UTF8,
+        0,
+        wstr,
+        wlen,
+        ret,
+        maxlen,
+        NULL,
+        NULL);
+    ret[ret_len] = 0;
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/util.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <stdlib.h>
+
+wchar_t* str2wstr(const char* str, int* newlen);
+
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen);
+
+char* wchar2utf8(const wchar_t* wstr, size_t wlen);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/window.cpp	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,637 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+
+#include "window.h"
+
+#include "appmenu.h"
+#include "commandbar.h"
+#include "container.h"
+#include "util.h"
+#include "button.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <stdlib.h>
+
+#include <cx/mempool.h>
+
+#include "MainWindow.xaml.h"
+
+
+#include <Windows.h>
+#include <shobjidl.h>
+#include <iostream>
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::Controls::Primitives;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Storage::Pickers;
+
+UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}
+
+UiObject* ui_window(const char* title, void* window_data) {
+	UiObject* obj = ui_simple_window(title, window_data);
+
+	/*
+	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;
+
+		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 = 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);
+
+
+		// 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);
+		}
+
+		toolbar_grid.VerticalAlignment(VerticalAlignment::Top);
+		obj->container->Add(toolbar_grid, false);
+	}
+
+	return obj;
+}
+
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) {
+	CxMempool* mp = cxBasicMempoolCreate(256);
+	UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
+
+	obj->ctx = uic_context(obj, mp);
+	obj->window = window_data;
+
+	Window window = Window();
+	//Window window = make<winui::implementation::MainWindow>();
+
+	winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };
+	Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);
+
+	window.ExtendsContentIntoTitleBar(true);
+
+	Grid grid = Grid();
+	window.Content(grid);
+
+	StackPanel titleBar = StackPanel();
+	Thickness titleBarPadding = { 10, 5, 5, 10 };
+	titleBar.Padding(titleBarPadding);
+	titleBar.Orientation(Orientation::Horizontal);
+	TextBlock titleLabel = TextBlock();
+	titleBar.Children().Append(titleLabel);
+
+	if (title) {
+		wchar_t* wtitle = str2wstr(title, nullptr);
+		window.Title(wtitle);
+		titleLabel.Text(hstring(wtitle));
+		free(wtitle);
+	}
+
+	window.SetTitleBar(titleBar);
+
+	obj->wobj = new UiWindow(window);
+	ui_context_add_window_destructor(obj->ctx, obj->wobj);
+
+	window.Closed([obj](IInspectable const& sender, WindowEventArgs) {
+		if (obj->ctx->close_callback) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.document = obj->ctx->document;
+			evt.window = obj->window;
+			evt.eventdata = NULL;
+			evt.intval = 0;
+			obj->ctx->close_callback(&evt, obj->ctx->close_data);
+		} else {
+			ui_context_destroy(obj->ctx);
+		}
+		});
+
+	obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);
+
+	titleBar.VerticalAlignment(VerticalAlignment::Top);
+	obj->container->Add(titleBar, false);
+
+	obj->window = window_data;
+
+	return obj;
+}
+
+static void dialog_button_add_callback(ContentDialog dialog, Button button, int num, UiObject *obj, ui_callback onclick, void *onclickdata) {
+	button.Click([dialog, num, obj, onclick, onclickdata](IInspectable const& sender, RoutedEventArgs) {
+		if (onclick) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.window = obj->window;
+			evt.document = obj->ctx->document;
+			evt.eventdata = nullptr;
+			evt.intval = num;
+			onclick(&evt, onclickdata);
+		}
+		dialog.Hide();
+	});
+}
+
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
+	UiWindow *window = parent->wobj;
+	if (!window) {
+		return NULL;
+	}
+	
+	CxMempool* mp = cxBasicMempoolCreate(256);
+	UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
+	
+	obj->ctx = uic_context(obj, mp);
+	
+	ContentDialog dialog = ContentDialog();
+	UIElement elm = dialog;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(obj->ctx, widget);
+	obj->widget = widget;
+
+	if (args.title) {
+		wchar_t* wtitle = str2wstr(args.title, nullptr);
+		dialog.Title(box_value(wtitle));
+		free(wtitle);
+	}
+
+	
+	Grid dialogContent = Grid();
+	GridLength gl;
+
+	// content row
+	gl.Value = 1;
+	gl.GridUnitType = GridUnitType::Star;
+	RowDefinition rowdef0 = RowDefinition();
+	rowdef0.Height(gl);
+	dialogContent.RowDefinitions().Append(rowdef0);
+
+	// button row
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+	RowDefinition rowdef1 = RowDefinition();
+	rowdef1.Height(gl);
+	dialogContent.RowDefinitions().Append(rowdef1);
+
+	// coldef
+	gl.Value = 1;
+	gl.GridUnitType = GridUnitType::Star;
+	ColumnDefinition coldef = ColumnDefinition();
+	coldef.Width(gl);
+	dialogContent.ColumnDefinitions().Append(coldef);
+
+	// content
+	Grid grid = Grid();
+	grid.SetRow(grid, 0);
+	grid.SetColumn(grid, 0);
+	dialogContent.Children().Append(grid);
+	obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);
+
+	// buttons
+	Grid buttons = Grid();
+	Thickness btnsMargin = { (double)0, (double)10, (double)0, (double)0 };
+	buttons.Margin(btnsMargin);
+
+	RowDefinition btnrowdef = RowDefinition();
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+	btnrowdef.Height(gl);
+	buttons.RowDefinitions().Append(btnrowdef);
+
+	gl.Value = 1;
+	gl.GridUnitType = GridUnitType::Auto;
+	int c = 0;
+	if (args.lbutton1) {
+		ColumnDefinition bcoldef = ColumnDefinition();
+		bcoldef.Width(gl);
+		buttons.ColumnDefinitions().Append(bcoldef);
+
+		Button btn = Button();
+		ui_set_button_label(btn, args.lbutton1, NULL, NULL, UI_LABEL_TEXT);
+		Thickness margin = { (double)5, (double)5, (double)5, (double)5 };
+		btn.Margin(margin);
+		btn.HorizontalAlignment(HorizontalAlignment::Stretch);
+		dialog_button_add_callback(dialog, btn, 1, obj, args.onclick, args.onclickdata);
+
+		buttons.SetRow(btn, 0);
+		buttons.SetColumn(btn, c++);
+		buttons.Children().Append(btn);
+	}
+	if (args.lbutton2) {
+		ColumnDefinition bcoldef = ColumnDefinition();
+		bcoldef.Width(gl);
+		buttons.ColumnDefinitions().Append(bcoldef);
+
+		Button btn = Button();
+		ui_set_button_label(btn, args.lbutton2, NULL, NULL, UI_LABEL_TEXT);
+		Thickness margin = { (double)5, (double)5, (double)5, (double)5 };
+		btn.Margin(margin);
+		btn.HorizontalAlignment(HorizontalAlignment::Stretch);
+		dialog_button_add_callback(dialog, btn, 2, obj, args.onclick, args.onclickdata);
+
+		buttons.SetRow(btn, 0);
+		buttons.SetColumn(btn, c++);
+		buttons.Children().Append(btn);
+	}
+	if (args.rbutton3) {
+		ColumnDefinition bcoldef = ColumnDefinition();
+		bcoldef.Width(gl);
+		buttons.ColumnDefinitions().Append(bcoldef);
+
+		Button btn = Button();
+		ui_set_button_label(btn, args.rbutton3, NULL, NULL, UI_LABEL_TEXT);
+		Thickness margin = { (double)5, (double)5, (double)5, (double)5 };
+		btn.Margin(margin);
+		btn.HorizontalAlignment(HorizontalAlignment::Stretch);
+		dialog_button_add_callback(dialog, btn, 3, obj, args.onclick, args.onclickdata);
+
+		buttons.SetRow(btn, 0);
+		buttons.SetColumn(btn, c++);
+		buttons.Children().Append(btn);
+	}
+	if (args.rbutton4) {
+		ColumnDefinition bcoldef = ColumnDefinition();
+		bcoldef.Width(gl);
+		buttons.ColumnDefinitions().Append(bcoldef);
+
+		Button btn = Button();
+		ui_set_button_label(btn, args.rbutton4, NULL, NULL, UI_LABEL_TEXT);
+		Thickness margin = { (double)5, (double)5, (double)5, (double)5 };
+		btn.Margin(margin);
+		btn.HorizontalAlignment(HorizontalAlignment::Stretch);
+		dialog_button_add_callback(dialog, btn, 4, obj, args.onclick, args.onclickdata);
+
+		buttons.SetRow(btn, 0);
+		buttons.SetColumn(btn, c++);
+		buttons.Children().Append(btn);
+	}
+
+	dialogContent.SetRow(buttons, 1);
+	dialogContent.SetColumn(buttons, 0);
+	dialogContent.Children().Append(buttons);
+
+
+	dialog.Content(dialogContent);
+	dialog.XamlRoot(window->window.Content().XamlRoot());
+
+	obj->widget->Show = [dialog]() {
+		dialog.ShowAsync();
+	};
+
+	return obj;
+}
+
+void ui_window_size(UiObject *obj, int width, int height) {
+	UIWINDOW win = obj->wobj;
+	if (win) {
+		winrt::Windows::Graphics::SizeInt32 wsize;
+		wsize.Width = width;
+		wsize.Height = height;
+		win->window.AppWindow().Resize(wsize);
+	}
+}
+
+
+
+
+static Windows::Foundation::IAsyncAction create_dialog_async(UiObject *obj, UiDialogArgs args) {
+	UiObject* current = uic_current_obj(obj);
+	Window parentWindow = current->wobj->window;
+
+	ContentDialog dialog = ContentDialog();
+	dialog.XamlRoot(parentWindow.Content().XamlRoot());
+
+	if (args.title) {
+		wchar_t *str = str2wstr(args.title, nullptr);
+		dialog.Title(winrt::box_value(str));
+		free(str);
+	}
+
+	TextBox textfield{ nullptr };
+	PasswordBox password{ nullptr };
+	if(args.input || args.password) {
+		StackPanel panel = StackPanel();
+		panel.Orientation(Orientation::Vertical);
+		if (args.content) {
+			wchar_t *str = str2wstr(args.content, nullptr);
+			TextBlock label = TextBlock();
+			label.Text(str);
+			panel.Children().Append(label);
+			free(str);
+		}
+
+		Thickness margin = { 0, 5, 0, 0 };
+		if (args.password) {
+			password = PasswordBox();
+			password.Margin(margin);
+			panel.Children().Append(password);
+		} else {
+			textfield = TextBox();
+			textfield.Margin(margin);
+			panel.Children().Append(textfield);
+		}
+		
+		panel.Margin(margin);
+
+		dialog.Content(panel);
+
+	} else {
+		if (args.content) {
+			wchar_t *str = str2wstr(args.content, nullptr);
+			dialog.Content(winrt::box_value(str));
+			free(str);
+		}
+	}
+
+	if (args.button1_label) {
+		wchar_t *str = str2wstr(args.button1_label, nullptr);
+		dialog.PrimaryButtonText(winrt::hstring(str));
+		free(str);
+		dialog.DefaultButton(ContentDialogButton::Primary);
+	}
+	if (args.button2_label) {
+		wchar_t *str = str2wstr(args.button2_label, nullptr);
+		dialog.SecondaryButtonText(winrt::hstring(str));
+		free(str);
+	}
+	if (args.closebutton_label) {
+		wchar_t *str = str2wstr(args.closebutton_label, nullptr);
+		dialog.CloseButtonText(winrt::hstring(str));
+		free(str);
+	}
+
+	ContentDialogResult result = co_await dialog.ShowAsync();
+
+	if (args.result) {
+		UiEvent evt;
+		evt.obj = current;
+		evt.document = current->ctx->document;
+		evt.window = current->window;
+		evt.eventdata = NULL;
+		evt.intval = 0;
+		if (result == ContentDialogResult::Primary) {
+			evt.intval = 1;
+		} else if (result == ContentDialogResult::Secondary) {
+			evt.intval = 2;
+		}
+
+		if (args.password) {
+			std::wstring wstr(password.Password());
+			char *text = wchar2utf8(wstr.c_str(), wstr.length());
+			evt.eventdata = text;
+		} else if (args.input) {
+			std::wstring wstr(textfield.Text());
+			char *text = wchar2utf8(wstr.c_str(), wstr.length());
+			evt.eventdata = text;
+		}
+
+		args.result(&evt, args.resultdata);
+
+		if (evt.eventdata) {
+			free(evt.eventdata);
+		}
+	}
+}
+
+UIEXPORT void ui_dialog_create(UiObject *obj, UiDialogArgs args) {
+	create_dialog_async(obj, args);
+}
+
+
+
+// --------------------------------------- File Dialog ---------------------------------------
+
+static void filedialog_callback(
+	UiObject *obj,
+	ui_callback file_selected_callback,
+	void *cbdata,
+	winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Storage::StorageFile> result)
+{
+	UiFileList flist;
+	flist.nfiles = result.Size();
+	flist.files = new char*[flist.nfiles];
+
+	int i = 0;
+	for (auto const& file : result) {
+		winrt::hstring path = file.Path();
+		flist.files[i++] = wchar2utf8(path.c_str(), path.size());
+	}
+
+	UiEvent evt;
+	evt.obj = obj;
+	evt.document = obj->ctx->document;
+	evt.window = obj->window;
+	evt.eventdata = &flist;
+	evt.intval = 0;
+	file_selected_callback(&evt, cbdata);
+
+	for (int i = 0; i < flist.nfiles;i++) {
+		free(flist.files[i]);
+	}
+	delete[] flist.files;
+}
+
+static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+	FileOpenPicker openFileDialog = FileOpenPicker();
+	auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>()
+	};
+
+	HWND hwnd{ nullptr };
+	winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));
+
+	initializeWithWindow->Initialize(hwnd);
+
+	openFileDialog.FileTypeFilter().Append(L"*");
+
+	if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+		auto files = co_await openFileDialog.PickMultipleFilesAsync();
+		filedialog_callback(obj, file_selected_callback, cbdata, files);
+	} else {
+		auto file = co_await openFileDialog.PickSingleFileAsync();
+		auto files = single_threaded_vector<winrt::Windows::Storage::StorageFile>();
+		files.Append(file);
+		filedialog_callback(obj, file_selected_callback, cbdata, files.GetView());
+	}
+}
+
+static Windows::Foundation::IAsyncAction save_filedialog_async(UiObject *obj, char *name, ui_callback file_selected_callback, void *cbdata) {
+	IFileSaveDialog *saveFileDialog;
+
+	HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast<void**>(&saveFileDialog));
+	if (FAILED(hr))
+	{
+		co_return;
+	}
+
+	if (name) {
+		wchar_t *wname = str2wstr(name, NULL);
+		saveFileDialog->SetFileName(wname);
+		free(wname);
+		free(name);
+	}
+	
+
+	hr = saveFileDialog->Show(NULL);
+	if (SUCCEEDED(hr)) {
+		IShellItem *item;
+		hr = saveFileDialog->GetResult(&item);
+		if (SUCCEEDED(hr)) {
+			PWSTR wpath;
+			hr = item->GetDisplayName(SIGDN_FILESYSPATH, &wpath);
+
+			if (SUCCEEDED(hr)) {
+				char *path = wchar2utf8(wpath, lstrlen(wpath));
+				CoTaskMemFree(wpath);
+
+				UiFileList flist;
+				flist.nfiles = 1;
+				flist.files = new char*[1];
+				flist.files[0] = path;
+
+				UiEvent evt;
+				evt.obj = obj;
+				evt.document = obj->ctx->document;
+				evt.window = obj->window;
+				evt.eventdata = &flist;
+				evt.intval = 0;
+				file_selected_callback(&evt, cbdata);
+
+				free(path);
+				delete[] flist.files;
+			}
+			item->Release();
+		}
+	}
+
+	// cleanup
+	saveFileDialog->Release();
+}
+
+static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) {
+	FolderPicker folderPicker = FolderPicker();
+	auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>()
+	};
+
+	HWND hwnd{ nullptr };
+	winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));
+
+	initializeWithWindow->Initialize(hwnd);
+
+	folderPicker.FileTypeFilter().Append(L"*");
+
+	auto folder = co_await folderPicker.PickSingleFolderAsync();
+	if (folder) {
+		winrt::hstring hpath = folder.Path();
+		char *cpath =  wchar2utf8(hpath.c_str(), hpath.size());
+		
+		UiFileList flist;
+		flist.nfiles = 1;
+		flist.files = &cpath;
+
+		UiEvent evt;
+		evt.obj = obj;
+		evt.document = obj->ctx->document;
+		evt.window = obj->window;
+		evt.eventdata = &flist;
+		evt.intval = 0;
+		file_selected_callback(&evt, cbdata);
+	}
+}
+
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+	if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+		folderdialog_async(obj, file_selected_callback, cbdata);
+	} else {
+		open_filedialog_async(obj, mode, file_selected_callback, cbdata);
+	}
+}
+
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {
+	char *n = name ? _strdup(name) : NULL;
+	save_filedialog_async(obj, n, file_selected_callback, cbdata);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/window.h	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+
+#include "../ui/window.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/winui.vcxproj	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" />
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" />
+  <PropertyGroup Label="Globals">
+    <CppWinRTOptimized>true</CppWinRTOptimized>
+    <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
+    <MinimalCoreWin>true</MinimalCoreWin>
+    <ProjectGuid>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</ProjectGuid>
+    <ProjectName>winui</ProjectName>
+    <RootNamespace>winui</RootNamespace>
+    <!--
+      $(TargetName) should be same as $(RootNamespace) so that the produced binaries (.exe/.pri/etc.)
+      have a name that matches the .winmd
+    -->
+    <TargetName>$(RootNamespace)</TargetName>
+    <DefaultLanguage>de-DE</DefaultLanguage>
+    <MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
+    <AppContainerApplication>false</AppContainerApplication>
+    <AppxPackage>false</AppxPackage>
+    <ApplicationType>Windows Store</ApplicationType>
+    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
+    <UseWinUI>true</UseWinUI>
+    <EnableMsixTooling>true</EnableMsixTooling>
+    <WindowsPackageType>None</WindowsPackageType>
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <DesktopCompatible>true</DesktopCompatible>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
+      <WarningLevel>Level4</WarningLevel>
+      <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
+    <ClCompile>
+      <PreprocessorDefinitions>_DEBUG;DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdc17</LanguageStandard_C>
+      <RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebugDLL</RuntimeLibrary>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
+    <ClCompile>
+      <PreprocessorDefinitions>DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <SDLCheck Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</SDLCheck>
+      <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Release|x64'">stdc17</LanguageStandard_C>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup Condition="'$(WindowsPackageType)'!='None' and Exists('Package.appxmanifest')">
+    <AppxManifest Include="Package.appxmanifest">
+      <SubType>Designer</SubType>
+    </AppxManifest>
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\common\context.h" />
+    <ClInclude Include="..\common\document.h" />
+    <ClInclude Include="..\common\menu.h" />
+    <ClInclude Include="..\common\object.h" />
+    <ClInclude Include="..\common\properties.h" />
+    <ClInclude Include="..\common\toolbar.h" />
+    <ClInclude Include="..\common\types.h" />
+    <ClInclude Include="..\common\ucx_properties.h" />
+    <ClInclude Include="..\ui\button.h" />
+    <ClInclude Include="..\ui\container.h" />
+    <ClInclude Include="..\ui\display.h" />
+    <ClInclude Include="..\ui\dnd.h" />
+    <ClInclude Include="..\ui\entry.h" />
+    <ClInclude Include="..\ui\graphics.h" />
+    <ClInclude Include="..\ui\icons.h" />
+    <ClInclude Include="..\ui\image.h" />
+    <ClInclude Include="..\ui\menu.h" />
+    <ClInclude Include="..\ui\properties.h" />
+    <ClInclude Include="..\ui\range.h" />
+    <ClInclude Include="..\ui\stock.h" />
+    <ClInclude Include="..\ui\text.h" />
+    <ClInclude Include="..\ui\toolbar.h" />
+    <ClInclude Include="..\ui\toolkit.h" />
+    <ClInclude Include="..\ui\tree.h" />
+    <ClInclude Include="..\ui\ui.h" />
+    <ClInclude Include="..\ui\window.h" />
+    <ClInclude Include="appmenu.h" />
+    <ClInclude Include="button.h" />
+    <ClInclude Include="commandbar.h" />
+    <ClInclude Include="condvar.h" />
+    <ClInclude Include="container.h" />
+    <ClInclude Include="dnd.h" />
+    <ClInclude Include="icons.h" />
+    <ClInclude Include="image.h" />
+    <ClInclude Include="label.h" />
+    <ClInclude Include="list.h" />
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="App.xaml.h">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClInclude>
+    <ClInclude Include="MainWindow.xaml.h">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </ClInclude>
+    <ClInclude Include="stock.h" />
+    <ClInclude Include="table.h" />
+    <ClInclude Include="text.h" />
+    <ClInclude Include="toolkit.h" />
+    <ClInclude Include="util.h" />
+    <ClInclude Include="window.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml" />
+    <Page Include="MainWindow.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\common\context.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\document.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\menu.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\object.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\properties.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\toolbar.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\types.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="..\common\ucx_properties.c">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="appmenu.cpp" />
+    <ClCompile Include="button.cpp" />
+    <ClCompile Include="commandbar.cpp" />
+    <ClCompile Include="condvar.cpp" />
+    <ClCompile Include="container.cpp" />
+    <ClCompile Include="dnd.cpp" />
+    <ClCompile Include="icons.cpp" />
+    <ClCompile Include="image.cpp" />
+    <ClCompile Include="label.cpp" />
+    <ClCompile Include="list.cpp" />
+    <ClCompile Include="pch.cpp">
+      <PrecompiledHeader>Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="App.xaml.cpp">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="MainWindow.xaml.cpp">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+    <ClCompile Include="stock.cpp" />
+    <ClCompile Include="table.cpp" />
+    <ClCompile Include="text.cpp" />
+    <ClCompile Include="toolkit.cpp" />
+    <ClCompile Include="util.cpp" />
+    <ClCompile Include="window.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="App.idl">
+      <SubType>Code</SubType>
+      <DependentUpon>App.xaml</DependentUpon>
+    </Midl>
+    <Midl Include="MainWindow.idl">
+      <SubType>Code</SubType>
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </Midl>
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="readme.txt">
+      <DeploymentContent>false</DeploymentContent>
+    </Text>
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\LockScreenLogo.scale-200.png" />
+    <Image Include="Assets\SplashScreen.scale-200.png" />
+    <Image Include="Assets\Square150x150Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
+    <Image Include="Assets\StoreLogo.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-200.png" />
+  </ItemGroup>
+  <!--
+    Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
+    Tools extension to be activated for this project even if the Windows App SDK Nuget
+    package has not yet been restored.
+  -->
+  <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+    <ProjectCapability Include="Msix" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\make\vs\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <!--
+    Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
+    Explorer "Package and Publish" context menu entry to be enabled for this project even if
+    the Windows App SDK Nuget package has not yet been restored.
+  -->
+  <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+    <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/winui.vcxproj.filters	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Page Include="MainWindow.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="App.idl" />
+    <Midl Include="MainWindow.idl" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="pch.cpp" />
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+    <ClCompile Include="appmenu.cpp" />
+    <ClCompile Include="button.cpp" />
+    <ClCompile Include="commandbar.cpp" />
+    <ClCompile Include="container.cpp" />
+    <ClCompile Include="list.cpp" />
+    <ClCompile Include="table.cpp" />
+    <ClCompile Include="text.cpp" />
+    <ClCompile Include="toolkit.cpp" />
+    <ClCompile Include="util.cpp" />
+    <ClCompile Include="window.cpp" />
+    <ClCompile Include="stock.cpp" />
+    <ClCompile Include="icons.cpp" />
+    <ClCompile Include="label.cpp" />
+    <ClCompile Include="dnd.cpp" />
+    <ClCompile Include="condvar.cpp" />
+    <ClCompile Include="image.cpp" />
+    <ClCompile Include="..\common\context.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\document.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\menu.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\object.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\properties.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\toolbar.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\types.c">
+      <Filter>common</Filter>
+    </ClCompile>
+    <ClCompile Include="..\common\ucx_properties.c">
+      <Filter>common</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="appmenu.h" />
+    <ClInclude Include="button.h" />
+    <ClInclude Include="commandbar.h" />
+    <ClInclude Include="container.h" />
+    <ClInclude Include="list.h" />
+    <ClInclude Include="table.h" />
+    <ClInclude Include="text.h" />
+    <ClInclude Include="toolkit.h" />
+    <ClInclude Include="util.h" />
+    <ClInclude Include="window.h" />
+    <ClInclude Include="..\ui\button.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\container.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\display.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\dnd.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\entry.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\graphics.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\image.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\menu.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\properties.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\range.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\stock.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\text.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\toolbar.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\toolkit.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\tree.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\ui.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\window.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="stock.h" />
+    <ClInclude Include="icons.h" />
+    <ClInclude Include="label.h" />
+    <ClInclude Include="dnd.h" />
+    <ClInclude Include="condvar.h" />
+    <ClInclude Include="image.h" />
+    <ClInclude Include="..\ui\icons.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\context.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\document.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\menu.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\object.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\properties.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\toolbar.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\types.h">
+      <Filter>common</Filter>
+    </ClInclude>
+    <ClInclude Include="..\common\ucx_properties.h">
+      <Filter>common</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\Wide310x150Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LockScreenLogo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+  </ItemGroup>
+  <ItemGroup>
+    <Filter Include="Assets">
+      <UniqueIdentifier>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="public">
+      <UniqueIdentifier>{2b58fe46-d27b-4335-b63c-13ec2204fa24}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="common">
+      <UniqueIdentifier>{35c88d5c-b36f-41b3-86c0-784227845ae9}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="readme.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/winui.vcxproj.user	Sat Jan 04 16:38:48 2025 +0100
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+</Project>
\ No newline at end of file

mercurial