add minimal working download

Mon, 12 Feb 2024 17:32:02 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 12 Feb 2024 17:32:02 +0100
changeset 29
3fc287f06305
parent 28
1ecc1183f046
child 30
762afc7adc63

add minimal working download

application/application.c file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/davcontroller.c file | annotate | diff | comparison | revisions
application/davcontroller.h file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/menu.h file | annotate | diff | comparison | revisions
ui/common/objs.mk file | annotate | diff | comparison | revisions
ui/common/properties.c file | annotate | diff | comparison | revisions
ui/common/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/container.h file | annotate | diff | comparison | revisions
ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
ui/gtk/model.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/tree.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/motif/container.c file | annotate | diff | comparison | revisions
ui/motif/text.c file | annotate | diff | comparison | revisions
ui/motif/text.h file | annotate | diff | comparison | revisions
ui/motif/window.c file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
ui/winui/window.cpp file | annotate | diff | comparison | revisions
--- a/application/application.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/application/application.c	Mon Feb 12 17:32:02 2024 +0100
@@ -88,7 +88,7 @@
 	ui_toolbar_item("NewFolder", .icon = "NewFolder", .onclick = action_mkcol);
 	ui_toolbar_item("NewFile", .icon = "Add", .onclick = action_newfile);
 	ui_toolbar_item("Upload", .label = "Upload", .icon = "Upload", .onclick = action_upload_file);
-	ui_toolbar_item("Download", .icon = "SaveLocal");
+	ui_toolbar_item("Download", .icon = "SaveLocal", .onclick = action_download);
 	ui_toolbar_item("Remove", .icon = "Delete", .onclick = action_delete );
 	ui_toolbar_toggleitem("LocalBrowser", .icon = "DockLeft", .label = "Local Browser");
 	ui_toolbar_toggleitem("PreviewPane", .icon = "DockRight");
@@ -175,6 +175,65 @@
 	
 }
 
+
+
+static void download_location_selected(UiEvent *event, void *data) {
+	DavBrowser *browser = event->document;
+	DavResource *reslist = data;
+	UiFileList *flist = event->eventdata;
+
+	if (flist && flist->nfiles > 0) {
+		davbrowser_download(event->obj, browser, reslist, flist->files[0]);
+	} else {
+		dav_session_destroy(reslist->session);
+	}
+}
+
+void action_download(UiEvent *event, void *data) {
+	DavBrowser *browser = event->document;
+	UiListSelection sel = ui_list_getselection(browser->resources);
+	if (sel.count > 0) {;
+		const char *initialFileName = NULL;
+		if (sel.count == 1) {
+			DavResource *res = ui_list_get(browser->resources, sel.rows[0]);
+			if (res && !res->iscollection) {
+				initialFileName = res->name;
+			}
+		}
+
+		// create a copy of the current session and all selected resources
+		DavSession *sn = dav_session_clone(browser->sn);
+		DavResource *first = NULL;
+		DavResource *last = NULL;
+		for (int i = 0; i < sel.count; i++) {
+			// get selected resource
+			DavResource *res = ui_list_get(browser->resources, sel.rows[i]);
+			// copy resource
+			DavResource *res_copy = dav_resource_new(sn, res->path);
+			res_copy->iscollection = res->iscollection;
+			res_copy->contentlength = res->contentlength;
+			res_copy->lastmodified = res->lastmodified;
+			res_copy->creationdate = res->creationdate;
+
+			// link resources
+			if (!first) {
+				first = res_copy;
+			}
+			if (last) {
+				res_copy->prev = last;
+				last->next = res_copy;
+			}
+			last = res_copy;
+		}
+
+		if (initialFileName) {
+			ui_savefiledialog(event->obj, initialFileName, download_location_selected, first);
+		} else {
+			ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_FOLDER, download_location_selected, first);
+		}
+	}
+}
+
 void action_upload_file(UiEvent *event, void *data) {
 	ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_MULTI, file_selected, NULL);
 }
--- a/application/application.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/application/application.h	Mon Feb 12 17:32:02 2024 +0100
@@ -107,6 +107,8 @@
 
 void action_repo_selected(UiEvent *event, void *data);
 
+void action_download(UiEvent *event, void *data);
+
 void action_upload_file(UiEvent *event, void *data);
 
 void action_delete(UiEvent *event, void *data);
--- a/application/davcontroller.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/application/davcontroller.c	Mon Feb 12 17:32:02 2024 +0100
@@ -666,6 +666,143 @@
 }
 
 
+// ------------------------------------- File Download -------------------------------------
+
+typedef struct DavFileDownload {
+    UiObject *ui;
+    DavBrowser *browser;
+
+    DavSession *sn;
+    DavResource *reslist;
+    char *local_path;
+    DavBool isdirectory;
+
+    UiThreadpool *queue;
+
+    size_t total_bytes;
+    size_t total_files;
+    size_t total_directories;
+    size_t downloaded_bytes;
+    size_t downloaded_files;
+    size_t downloaded_directories;
+
+    UiObject *dialog;
+    UiDouble *progress;
+    UiString *label_top_left;
+    UiString *label_top_right;
+    UiString *label_bottom_left;
+    UiString *label_bottom_right;
+} DavFileDownload;
+
+// dav download file
+typedef struct DDFile {
+    DavFileDownload *download;
+    DavResource *res;
+    char *to;
+} DDFile;
+
+static int qthr_download_resource(void *data) {
+    DDFile *file = data;
+
+    FILE *f = fopen(file->to, "wb");
+    if (!f) {
+        return 0;
+    }
+
+    dav_get_content(file->res, f, (dav_write_func)fwrite);
+
+    fclose(f);
+}
+
+static int jobthr_download_scan(void *data) {
+    DavFileDownload *download = data;
+    DavBrowser *browser = download->browser;
+
+    // check if the specified local location is a directory
+    SYS_STAT s;
+    if (!sys_stat(download->local_path, &s)) {
+        if (S_ISDIR(s.st_mode)) {
+            download->isdirectory = TRUE;
+        }
+    }
+
+    // add selected files to the download queue
+    DavResource *res = download->reslist;
+    while (res) {
+        DDFile *file = malloc(sizeof(DDFile));
+        file->download = download;
+        file->res = res;
+        if (download->isdirectory) {
+            file->to = util_concat_path(download->local_path, res->name);
+        } else {
+            file->to = strdup(download->local_path);
+        }
+
+        ui_threadpool_job(download->queue, download->ui, qthr_download_resource, file, NULL, NULL);
+
+        res = res->next;
+    }
+}
+
+static void uithr_download_scan_finished(UiEvent *event, void *data) {
+
+}
+
+static void download_window_closed(UiEvent *event, void *data) {
+
+}
+
+
+void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path) {
+    DavFileDownload *download = malloc(sizeof(DavFileDownload));
+    memset(download, 0, sizeof(DavFileDownload));
+
+    download->browser = browser;
+    download->sn = reslist->session;
+    download->reslist = reslist;
+    download->local_path = strdup(local_path);
+
+    download->queue = ui_threadpool_create(1);
+
+    // create download progress window
+    cxmutstr wtitle = cx_asprintf("Download to: %s", local_path);
+    UiObject *dialog = ui_simple_window(wtitle.ptr, download);
+    ui_context_closefunc(dialog->ctx, download_window_closed, NULL);
+    free(wtitle.ptr);
+    download->dialog = dialog;
+    ui_window_size(dialog, 550, 120);
+    download->progress = ui_double_new(dialog->ctx, NULL);
+    download->label_top_left = ui_string_new(dialog->ctx, NULL);
+    download->label_top_right = ui_string_new(dialog->ctx, NULL);
+    download->label_bottom_left = ui_string_new(dialog->ctx, NULL);
+    download->label_bottom_right = ui_string_new(dialog->ctx, NULL);
+
+    ui_grid(dialog, .margin = 10, .spacing = 10, .fill = TRUE) {
+        ui_llabel(dialog, .value = download->label_top_left, .hexpand = TRUE);
+        ui_rlabel(dialog, .value = download->label_top_right);
+        ui_newline(dialog);
+
+        ui_progressbar(dialog, .value = download->progress, .colspan = 2, .hexpand = TRUE);
+        ui_newline(dialog);
+
+        ui_llabel(dialog, .value = download->label_bottom_left);
+        ui_rlabel(dialog, .value = download->label_bottom_right);
+        ui_newline(dialog);
+    }
+
+    ui_set(download->label_top_left, "");
+    ui_set(download->label_top_right, "");
+    ui_set(download->label_bottom_left, "");
+    ui_set(download->label_bottom_right, "");
+    ui_set(download->progress, 0);
+
+    ui_show(dialog);
+
+    // start upload and stat threads
+    ui_job(ui, jobthr_download_scan, download, uithr_download_scan_finished, download);
+}
+
+
 // ------------------------------------- Path Operation (DELETE, MKCOL) -------------------------------------
 
 enum DavPathOpType {
@@ -812,8 +949,6 @@
     return 0;
 }
 
-
-
 void davbrowser_delete(UiObject *ui, DavBrowser *browser, UiListSelection selection) {
     DavPathOp *op = malloc(sizeof(DavPathOp));
     op->ui = ui;
--- a/application/davcontroller.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/application/davcontroller.h	Mon Feb 12 17:32:02 2024 +0100
@@ -58,6 +58,8 @@
 
 void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files);
 
+void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path);
+
 void davbrowser_delete(UiObject *ui, DavBrowser *browser, UiListSelection selection);
 
 void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name);
--- a/ui/common/menu.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/common/menu.c	Mon Feb 12 17:32:02 2024 +0100
@@ -61,7 +61,7 @@
     return s ? strdup(s) : NULL;
 }
 
-static int* copy_groups(int* groups, size_t *ngroups) {
+static int* copy_groups(const int* groups, size_t *ngroups) {
     *ngroups = 0;
     if (!groups) {
         return NULL;
--- a/ui/common/menu.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/common/menu.h	Mon Feb 12 17:32:02 2024 +0100
@@ -65,7 +65,7 @@
 
 struct UiMenu {
     UiMenuItemI    item;
-    char           *label;
+    const char     *label;
     UiMenuItemI    *items_begin;
     UiMenuItemI    *items_end;
     UiMenu         *parent;
--- a/ui/common/objs.mk	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/common/objs.mk	Mon Feb 12 17:32:02 2024 +0100
@@ -35,6 +35,8 @@
 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
 
 TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
--- a/ui/common/properties.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/common/properties.c	Mon Feb 12 17:32:02 2024 +0100
@@ -57,7 +57,7 @@
 }
 
 char* ui_configfile(char *name) {
-    char *appname = ui_appname();
+    const char *appname = ui_appname();
     if(!appname) {
         return NULL;
     }
--- a/ui/common/toolbar.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/common/toolbar.c	Mon Feb 12 17:32:02 2024 +0100
@@ -37,108 +37,108 @@
 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);
+    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(char* str) {
-	return str ? strdup(str) : NULL;
+static char* nl_strdup(const char* str) {
+    return str ? strdup(str) : NULL;
 }
 
 static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args) {
-	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;
-	return newargs;
+    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;
+    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);
-	cxMapPut(toolbar_items, name, item);
+    UiToolbarItem* item = malloc(sizeof(UiToolbarItem));
+    item->item.type = UI_TOOLBAR_ITEM;
+    item->args = itemargs_copy(args);
+    cxMapPut(toolbar_items, name, item);
 }
 
 
 static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args) {
-	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;
-	return newargs;
+    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;
+    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);
-	cxMapPut(toolbar_items, name, item);
+    UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem));
+    item->item.type = UI_TOOLBAR_TOGGLEITEM;
+    item->args = toggleitemargs_copy(args);
+    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;
+    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;
+    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);
-	}
+    if (!name) {
+        // special appmenu
+        ui_appmenu = item;
+    } else {
+        // toplevel menu
+        cxMapPut(toolbar_items, name, item);
+    }
 
-	uic_add_menu_to_stack(&item->menu);
+    uic_add_menu_to_stack(&item->menu);
 }
 
 
 CxMap* uic_get_toolbar_items(void) {
-	return toolbar_items;
+    return toolbar_items;
 }
 
 CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {
-	if (pos >= 0 && pos < 3) {
-		return toolbar_defaults[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
-	}
+    char* cp = strdup(name);
+    if (pos >= 0 && pos < 3) {
+        cxListAdd(toolbar_defaults[pos], cp);
+    } else {
+        // TODO: error
+    }
 }
 
 UiBool uic_toolbar_isenabled(void) {
-	return toolbar_defaults[0]->size + toolbar_defaults[1]->size + toolbar_defaults[2]->size > 0;
+    return toolbar_defaults[0]->size + toolbar_defaults[1]->size + toolbar_defaults[2]->size > 0;
 }
 
 UiToolbarItemI* uic_toolbar_get_item(const char* name) {
-	return cxMapGet(toolbar_items, name);
+    return cxMapGet(toolbar_items, name);
 }
 
 UiToolbarMenuItem* uic_get_appmenu(void) {
-	return ui_appmenu;
+    return ui_appmenu;
 }
--- a/ui/gtk/button.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/button.c	Mon Feb 12 17:32:02 2024 +0100
@@ -35,7 +35,7 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+UIWIDGET ui_button_deprecated(UiObject *obj, char *label, ui_callback f, void *data) {
     GtkWidget *button = gtk_button_new_with_label(label);
     
     if(f) {
@@ -132,7 +132,7 @@
     return button;
 }
 
-UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) {
+UIWIDGET ui_checkbox_deprecated(UiObject *obj, char *label, UiInteger *value) {
     UiVar *var = NULL;
     if(value) {
         var = malloc(sizeof(UiVar));
@@ -190,7 +190,7 @@
     return rbutton;
 }
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+UIWIDGET ui_radiobutton_deprecated(UiObject *obj, char *label, UiInteger *rgroup) {
     UiVar *var = NULL;
     if(rgroup) {
         var = malloc(sizeof(UiVar));
--- a/ui/gtk/container.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/container.c	Mon Feb 12 17:32:02 2024 +0100
@@ -67,22 +67,6 @@
 #endif
 }
 
-/* -------------------- Frame Container (deprecated) -------------------- */
-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) {
-    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
 
 
 /* -------------------- Box Container -------------------- */
@@ -243,13 +227,6 @@
 }
 
 
-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 *ret = box;
@@ -272,11 +249,13 @@
     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 *vbox = ui_gtk_vbox_new(args.spacing);   
+    GtkWidget *widget = args.margin > 0 ? box_set_margin(vbox, args.margin) : vbox;
     ct->add(ct, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, vbox);
@@ -286,41 +265,33 @@
     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);
-    uic_obj_add(obj, newobj);
-    
-    return widget;
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, UI_CONTAINER_VBOX);
 }
 
-UIWIDGET ui_grid(UiObject *obj) {
-    return ui_grid_sp(obj, 0, 0, 0);
+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) {
+
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
     UiContainer *ct = uic_get_current_container(obj);
     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);
+    gtk_grid_set_column_spacing(GTK_GRID(grid), args.columnspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), args.rowspacing);
 #if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
-    gtk_widget_set_margin_start(grid, margin);
-    gtk_widget_set_margin_end(grid, margin);
+    gtk_widget_set_margin_start(grid, args.margin);
+    gtk_widget_set_margin_end(grid, args.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);
+    gtk_widget_set_margin_top(grid, args.margin);
+    gtk_widget_set_margin_bottom(grid, args.margin);
     
     widget = grid;
 #elif defined(UI_GTK2)
@@ -347,38 +318,6 @@
     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);
-    
-    UiObject *newobj = uic_object_new(obj, 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_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) {
     gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
@@ -401,192 +340,9 @@
     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 = cxCalloc(
-            obj->ctx->allocator,
-            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 = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            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;
-}
 
-void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) {
-    // TODO: remove
-    gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE);
-    
-    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);
-    
-    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;
-    }
-    
-    //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);
-    
-    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;
-}
-
-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->allocator);
-    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;
-}
-
-void ui_tab_set_document(UiContext *ctx, void *document) {
-    // TODO: remove?
-    if(ctx->parent->document) {
-        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
-    }
-    //uic_context_set_document(ctx, document);
-    //uic_context_set_document(ctx->parent, document);
-    //ctx->parent->document = document;
-}
-
-void ui_tab_detach_document(UiContext *ctx) {
-    // TODO: remove?
-    //uic_context_detach_document(ctx->parent);
-}
 
 
 /*
@@ -611,16 +367,21 @@
     ct->layout.vexpand = expand;
 }
 
-void ui_layout_width(UiObject *obj, int width) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.width = width;
-}
-
 void ui_layout_gridwidth(UiObject *obj, int width) {
     UiContainer *ct = uic_get_current_container(obj);
     ct->layout.gridwidth = width;
 }
 
+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;
--- a/ui/gtk/container.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/container.h	Mon Feb 12 17:32:02 2024 +0100
@@ -62,6 +62,8 @@
     UiBool       vexpand;
     int          width;
     int          gridwidth;
+    int          colspan;
+    int          rowspan;
 };
 
 struct UiContainer {
@@ -108,6 +110,8 @@
 UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
 void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
+
 UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
 void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
--- a/ui/gtk/dnd.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/dnd.c	Mon Feb 12 17:32:02 2024 +0100
@@ -55,6 +55,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 +100,4 @@
     }
     return NULL;
 }
+*/
--- a/ui/gtk/image.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/image.c	Mon Feb 12 17:32:02 2024 +0100
@@ -75,9 +75,11 @@
     return NULL;
 }
 
+/*
 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);
@@ -99,6 +101,7 @@
     return NULL;
 }
 
+/*
 UiImage* ui_image(const char *filename) {
     return ui_named_image(filename, NULL);
 }
@@ -129,6 +132,7 @@
     }
     return img;
 }
+*/
 
 void ui_free_image(UiImage *img) {
     g_object_unref(img->pixbuf);
--- a/ui/gtk/menu.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/menu.c	Mon Feb 12 17:32:02 2024 +0100
@@ -45,13 +45,12 @@
 
 static ui_menu_add_f createMenuItem[] = {
     /* UI_MENU                 */ add_menu_widget,
-    /* UI_MENU_SUBMENU         */ add_menu_widget,
     /* UI_MENU_ITEM            */ add_menuitem_widget,
-    /* UI_MENU_STOCK_ITEM      */ add_menuitem_st_widget,
     /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
-    /* UI_MENU_CHECK_ITEM_NV   */ add_checkitemnv_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
     /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
-    /* UI_MENU_ITEM_LIST_NV    */ NULL, // TODO
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
     /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
 };
 
@@ -126,6 +125,7 @@
     }
 }
 
+/*
 void add_menuitem_st_widget(
         GtkWidget *parent,
         int index,
@@ -161,6 +161,7 @@
         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
     }
 }
+*/
 
 void add_menuseparator_widget(
         GtkWidget *parent,
@@ -174,7 +175,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);
     
@@ -198,6 +199,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);
@@ -214,6 +220,7 @@
         // TODO: error
     }
 }
+*/
 
 void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item;
@@ -227,7 +234,10 @@
     ls->menu = GTK_MENU_SHELL(p);
     ls->index = index;
     ls->oldcount = 0;
-    ls->list = il->list;
+    
+    // TODO:
+    //ls->list = il->list;
+    
     ls->callback = il->callback;
     ls->userdata = il->userdata;
     
--- a/ui/gtk/menu.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/menu.h	Mon Feb 12 17:32:02 2024 +0100
@@ -60,6 +60,7 @@
 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);
 
--- a/ui/gtk/model.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/model.c	Mon Feb 12 17:32:02 2024 +0100
@@ -436,6 +436,7 @@
 {
     //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];
@@ -449,6 +450,7 @@
         s.data = selection_data;
         model->model->drop(&e, &s, model->var->value, row);
     }
+    */
     return TRUE;
 }
 
@@ -459,6 +461,7 @@
 {
     //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];
@@ -472,6 +475,7 @@
         s.data = selection_data;
         return model->model->candrop(&e, &s, model->var->value, row);
     }
+    */
     return TRUE;
 }
 
@@ -481,6 +485,7 @@
 {
     //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];
@@ -492,6 +497,7 @@
         e.intval = 0;
         return model->model->candrag(&e, model->var->value, row);
     }
+    */
     return TRUE;
 }
 
@@ -502,6 +508,7 @@
 {
     //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];
@@ -515,6 +522,7 @@
         s.data = selection_data;
         model->model->data_get(&e, &s, model->var->value, row);
     }
+    */
     return TRUE;
 }
 
@@ -524,6 +532,7 @@
 {
     //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];
@@ -535,5 +544,6 @@
         e.intval = 0;
         model->model->data_delete(&e, model->var->value, row);
     }
+    */
     return TRUE;
 }
--- a/ui/gtk/text.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/text.c	Mon Feb 12 17:32:02 2024 +0100
@@ -193,7 +193,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);
@@ -632,7 +632,7 @@
     }
 }
 
-UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+UIWIDGET ui_textfield_deprecated(UiObject *obj, UiString *value) {
     return create_textfield(obj, 0, FALSE, FALSE, value);
 }
 
@@ -648,7 +648,7 @@
     return create_textfield_nv(obj, width, FALSE, FALSE, varname);
 }
 
-UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+UIWIDGET ui_frameless_textfield_deprecated(UiObject *obj, UiString *value) {
     return create_textfield(obj, 0, TRUE, FALSE, value);
 }
 
@@ -656,7 +656,7 @@
     return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
 }
 
-UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+UIWIDGET ui_passwordfield_deprecated(UiObject *obj, UiString *value) {
     return create_textfield(obj, 0, FALSE, TRUE, value);
 }
 
@@ -681,7 +681,7 @@
     return str->value.ptr;
 }
 
-void ui_textfield_set(UiString *str, char *value) {
+void ui_textfield_set(UiString *str, const char *value) {
     gtk_entry_set_text(str->obj, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
--- a/ui/gtk/text.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/text.h	Mon Feb 12 17:32:02 2024 +0100
@@ -76,7 +76,7 @@
 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);
@@ -108,7 +108,7 @@
 void ui_textfield_changed(GtkEditable *editable, 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);
 
 #ifdef	__cplusplus
 }
--- a/ui/gtk/toolbar.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/toolbar.c	Mon Feb 12 17:32:02 2024 +0100
@@ -34,7 +34,7 @@
 #include "button.h"
 #include "image.h"
 #include "tree.h"
-#include <cx/basic_mempool.h>
+#include <cx/mempool.h>
 #include <cx/hash_map.h>
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
@@ -221,11 +221,6 @@
 }
 
 
-void ui_toolbar_add_default(char *name) {
-    char *s = strdup(name);
-    cxListAdd(defaults, s);
-}
-
 GtkWidget* ui_create_toolbar(UiObject *obj) {
     if(!defaults) {
         return NULL;
--- a/ui/gtk/toolkit.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/toolkit.c	Mon Feb 12 17:32:02 2024 +0100
@@ -48,7 +48,7 @@
 static GtkApplication *app;
 #endif
 
-static char *application_name;
+static const char *application_name;
 
 static ui_callback   startup_func;
 static void          *startup_data;
@@ -64,14 +64,16 @@
 
 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) {
     uic_init_global_context();
     
     gtk_init(&argc, &argv);
     application_name = appname;
     
     uic_docmgr_init();
-    ui_toolbar_init();
+    ui_toolbar_init(); // TODO: remove
+    
+    uic_toolbar_init();
     
     // init custom types
     ui_list_init();
@@ -86,7 +88,7 @@
 #endif
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
--- a/ui/gtk/tree.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/tree.c	Mon Feb 12 17:32:02 2024 +0100
@@ -43,9 +43,7 @@
 }
 
 
-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
@@ -119,7 +117,7 @@
     return scroll_area;
 }
 
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+UIWIDGET ui_listview_deprecated(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;
@@ -270,7 +268,7 @@
     return scroll_area;
 }
 
-UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
+UIWIDGET ui_table_deprecated(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
     UiVar *var = malloc(sizeof(UiVar));
     var->value = list;
     var->type = UI_VAR_SPECIAL;
@@ -329,10 +327,14 @@
     int nelm;
     char **targets = targets2array(target0, ap, &nelm);
     va_end(ap);
-    ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+    
+    // 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(
@@ -344,6 +346,7 @@
     free(t);
 }
 
+
 void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
     va_list ap;
     va_start(ap, target0);
@@ -363,7 +366,8 @@
             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);
@@ -464,11 +468,7 @@
 
 /* --------------------------- 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) {
+UIWIDGET ui_combobox_deprecated(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;
--- a/ui/gtk/window.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/gtk/window.c	Mon Feb 12 17:32:02 2024 +0100
@@ -33,8 +33,10 @@
 #include "../ui/window.h"
 #include "../ui/properties.h"
 #include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
 
-#include <cx/basic_mempool.h>
+#include <cx/mempool.h>
 
 #include "menu.h"
 #include "toolbar.h"
@@ -67,7 +69,7 @@
 #endif
 }
 
-static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+static UiObject* create_window(const char *title, void *window_data, UiBool simple) {
     CxMempool *mp = cxBasicMempoolCreate(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
     
@@ -78,7 +80,7 @@
 #endif
     
     
-    obj->ctx = uic_context(obj, mp->allocator);
+    obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     
     if(title != NULL) {
@@ -110,15 +112,19 @@
     
     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);
+            }
         }
     }
     
@@ -138,11 +144,11 @@
 }
 
 
-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_simplewindow(const char *title, void *window_data) {
     return create_window(title, window_data, TRUE);
 }
 
@@ -179,11 +185,4 @@
     }
 }
 
-char* ui_openfiledialog(UiObject *obj) {
-    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN);
-}
 
-char* ui_savefiledialog(UiObject *obj) {
-    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE);
-}
-
--- a/ui/motif/container.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/motif/container.c	Mon Feb 12 17:32:02 2024 +0100
@@ -674,7 +674,7 @@
     
     UiObject *content = ui_malloc(view->ctx, sizeof(UiObject));
     content->widget = NULL; // initialization for uic_context()
-    content->ctx = uic_context(content, view->ctx->allocator);
+    content->ctx = uic_context(content, view->ctx->mp);
     content->ctx->parent = view->ctx;
     content->ctx->attach_document = uic_context_attach_document;
     content->ctx->detach_document2 = uic_context_detach_document2;
--- a/ui/motif/text.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/motif/text.c	Mon Feb 12 17:32:02 2024 +0100
@@ -119,7 +119,7 @@
     return str;
 }
 
-void ui_textarea_set(UiText *text, char *str) {
+void ui_textarea_set(UiText *text, const char *str) {
     XmTextSetString(text->obj, str);
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
--- a/ui/motif/text.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/motif/text.h	Mon Feb 12 17:32:02 2024 +0100
@@ -64,7 +64,7 @@
 } UiTextArea;
     
 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);
--- a/ui/motif/window.c	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/motif/window.c	Mon Feb 12 17:32:02 2024 +0100
@@ -36,7 +36,7 @@
 #include "../ui/window.h"
 #include "../common/context.h"
 
-#include <cx/basic_mempool.h>
+#include <cx/mempool.h>
 
 static int nwindows = 0;
 
@@ -67,7 +67,7 @@
     CxMempool *mp = cxBasicMempoolCreate(256);
     const CxAllocator *a = mp->allocator;
     UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
-    obj->ctx = uic_context(obj, a);
+    obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     
     Arg args[16];
--- a/ui/ui/toolkit.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/ui/toolkit.h	Mon Feb 12 17:32:02 2024 +0100
@@ -297,7 +297,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*);
--- a/ui/ui/window.h	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/ui/window.h	Mon Feb 12 17:32:02 2024 +0100
@@ -60,7 +60,7 @@
 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, 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);
 
 
 
--- a/ui/winui/window.cpp	Sun Feb 11 15:59:56 2024 +0100
+++ b/ui/winui/window.cpp	Mon Feb 12 17:32:02 2024 +0100
@@ -46,6 +46,10 @@
 #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;
@@ -357,6 +361,59 @@
 	}
 }
 
+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>()
@@ -396,6 +453,7 @@
 	}
 }
 
-UIEXPORT void ui_savefiledialog(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) {
+	char *n = name ? _strdup(name) : NULL;
+	save_filedialog_async(obj, n, file_selected_callback, cbdata);
 }

mercurial