add resource preview window

Sun, 20 Oct 2024 21:24:13 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 20 Oct 2024 21:24:13 +0200
changeset 51
e324291ca9f8
parent 50
9c25e2616bfa
child 52
0c881944fa10

add resource preview window

application/application.h file | annotate | diff | comparison | revisions
application/davcontroller.c file | annotate | diff | comparison | revisions
application/davcontroller.h file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
application/window.h file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/object.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/types.h 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/headerbar.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.h file | annotate | diff | comparison | revisions
ui/gtk/icon.c file | annotate | diff | comparison | revisions
ui/gtk/icon.h file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/image.h file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/objs.mk file | annotate | diff | comparison | revisions
ui/gtk/text.c 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/toolkit.h file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/ui/container.h file | annotate | diff | comparison | revisions
ui/ui/icons.h file | annotate | diff | comparison | revisions
ui/ui/image.h file | annotate | diff | comparison | revisions
ui/ui/properties.h file | annotate | diff | comparison | revisions
ui/ui/text.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
--- a/application/application.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/application/application.h	Sun Oct 20 21:24:13 2024 +0200
@@ -50,6 +50,15 @@
     UiList *repos;
 } DavApp;
 
+
+typedef enum DavResourceViewType {
+    DAV_RESOURCE_VIEW_PROPERTIES = 0,
+    DAV_RESOURCE_VIEW_TEXT,
+    DAV_RESOURCE_VIEW_IMAGE
+} DavResourceViewType;
+
+#define DAV_RESOURCEVIEWER_PREVIEW_MAX_SIZE 0x1000000
+
 /*
  * main window document object
  */
@@ -57,6 +66,8 @@
     UiContext *ctx;
     DavSession *sn;
     UiThreadpool *dav_queue;
+    
+    CxMap *res_open_inprogress;
 
     char *repo_base;
 
@@ -82,6 +93,38 @@
     UiList *resources;
 } DavBrowser;
 
+/*
+ * resource view window document object
+ */
+typedef struct DavResourceViewer {
+    UiContext *ctx;
+    DavSession *sn;
+    DavResource *current;
+    char *path;
+    DavResourceViewType type;
+    
+    UiInteger *tabview;
+    UiInteger *loading;
+    UiString *message;
+    
+    UiList *properties;
+    UiText *text;
+    UiGeneric *image;
+    
+    int error;
+    char *message_str;
+    
+    CxBuffer *text_content;
+    char *tmp_file;
+} DavResourceViewer;
+
+typedef struct DavPropertyList {
+    char *ns;
+    char *name;
+    char *value_simplified;
+    char *value_full;
+} DavPropertyList;
+
 void application_init(void);
 
 /*
--- a/application/davcontroller.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/application/davcontroller.c	Sun Oct 20 21:24:13 2024 +0200
@@ -34,6 +34,7 @@
 #include "config.h"
 
 #include "system.h"
+#include "common/context.h"
 
 #include <libidav/config.h>
 #include <libidav/utils.h>
@@ -48,6 +49,8 @@
     doc->navstack_pos = 0;
 
     doc->dav_queue = ui_threadpool_create(1);
+    
+    doc->res_open_inprogress = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 4);
 
     doc->path = ui_string_new(ctx, "path");
     doc->resources = ui_list_new(ctx, "reslist");
@@ -198,6 +201,64 @@
     }
 }
 
+void davbrowser_open_resource(UiObject *ui, DavBrowser *browser, DavResource *res) {
+    char *url = util_concat_path(browser->sn->base_url, res->path);
+    void *x = cxMapGet(browser->res_open_inprogress, url);
+    free(url);
+    if(x) {
+        return; // open resource already in progress
+    }
+    
+    DavResourceViewType type = DAV_RESOURCE_VIEW_PROPERTIES;
+    if(res->iscollection) {
+        // default type
+    } else if(res->contenttype) {
+        cxstring contenttype = cx_str(res->contenttype);
+        if(cx_strprefix(contenttype, CX_STR("text/"))) {
+            type = DAV_RESOURCE_VIEW_TEXT;
+        } else if(cx_strprefix(contenttype, CX_STR("image/"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strprefix(contenttype, CX_STR("application/"))) {
+            if(cx_strsuffix(contenttype, CX_STR("json"))) {
+                type = DAV_RESOURCE_VIEW_TEXT;
+            } else if(cx_strsuffix(contenttype, CX_STR("/xml"))) {
+                type = DAV_RESOURCE_VIEW_TEXT;
+            } else if(cx_strsuffix(contenttype, CX_STR("+xml"))) {
+                type = DAV_RESOURCE_VIEW_TEXT;
+            } else if(cx_strsuffix(contenttype, CX_STR("/xml"))) {
+                type = DAV_RESOURCE_VIEW_TEXT;
+            }
+        }
+    } else {
+        cxstring path = cx_str(res->path);
+        if(cx_strsuffix(path, CX_STR(".png"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".jpg"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".jpeg"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".tif"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".tiff"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".webp"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".bmp"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".gif"))) {
+            type = DAV_RESOURCE_VIEW_IMAGE;
+        } else if(cx_strsuffix(path, CX_STR(".txt"))) {
+            type = DAV_RESOURCE_VIEW_TEXT;
+        } else if(cx_strsuffix(path, CX_STR(".md"))) {
+            type = DAV_RESOURCE_VIEW_TEXT;
+        } else if(cx_strsuffix(path, CX_STR(".xml"))) {
+            type = DAV_RESOURCE_VIEW_TEXT;
+        }
+    }
+    
+    dav_resourceviewer_new(browser, res->path, type);
+}
+
 void davbrowser_add2navstack(DavBrowser *browser, const char *base, const char *path) {
     if (browser->navstack_enabled) {
         for (int i = 0; i < browser->navstack_pos; i++) {
@@ -1152,3 +1213,102 @@
 void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name) {
     davbrowser_create_resource(ui, browser, name, FALSE);
 }
+
+
+
+DavResourceViewer* dav_resourceviewer_create(DavSession *sn, const char *path, DavResourceViewType type) {
+    DavResourceViewer *doc = ui_document_new(sizeof(DavResourceViewer));
+    UiContext *ctx = ui_document_context(doc);
+    doc->ctx = ctx;
+    
+    doc->sn = dav_session_clone(sn);
+    doc->path = strdup(path);
+    doc->type = type;
+    
+    doc->tabview = ui_int_new(ctx, "tabview");
+    doc->loading = ui_int_new(ctx, "loading");
+    doc->message = ui_string_new(ctx, "message");
+    
+    doc->text = ui_text_new(ctx, "text");
+    doc->image = ui_generic_new(ctx, "image");
+    
+    doc->properties = ui_list_new(ctx, "properties");
+    
+    return doc;
+}
+
+static char* gen_tmp_download_filename(const char *name) {
+    char *dir = ui_getappdir();
+    
+    unsigned char rd[8];
+    memset(rd, 0, 8);
+    dav_rand_bytes(rd, 8);   
+    char *hex = util_hexstr(rd, 8);
+    cxmutstr tmp = cx_asprintf("%sdownload-%s-%s", dir, name, hex);
+    return tmp.ptr;
+}
+
+static int jobthr_resourceviewer_load(void *data) {
+    DavResourceViewer *doc = data;
+    
+    DavResource *res = dav_resource_new(doc->sn, doc->path);
+    doc->error = dav_load(res);
+    if(!doc->error) {
+        doc->current = res;
+        
+        if(res->contentlength < DAV_RESOURCEVIEWER_PREVIEW_MAX_SIZE) {
+            if(doc->type == DAV_RESOURCE_VIEW_TEXT) {
+                doc->text_content = cxBufferCreate(NULL, res->contentlength, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+                int err = dav_get_content(res, doc->text_content, (dav_write_func)cxBufferWrite);
+                cxBufferPut(doc->text_content, 0);
+                if(err) {
+                    doc->error = err;
+                    doc->message_str = "Cannot load content"; // TODO: better message
+                }
+            } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) {
+                char *tmp = gen_tmp_download_filename(res->name);
+                FILE *f = fopen(tmp, "w");
+                if(f) {
+                    int err = dav_get_content(res, f, (dav_write_func)fwrite);
+                    if(!err) {
+                        doc->tmp_file = tmp;
+                    } else {
+                        free(tmp);
+                    }
+                    fclose(f);
+                } else {
+                    free(tmp);
+                }
+            }
+        } else {
+            // TODO: add file too large message
+        }
+    } else {
+        doc->message_str = "Cannot load properties"; // TODO: better message
+        dav_resource_free(res);
+    }
+    
+    return 0;
+}
+
+static void resourceviewer_load_finished(UiEvent *event, void *data) {
+    DavResourceViewer *doc = data;
+    
+    if(doc->type == DAV_RESOURCE_VIEW_TEXT) {
+        ui_set(doc->text, doc->text_content->space);
+    } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) {
+        ui_image_load_file(doc->image, doc->tmp_file);
+        unlink(doc->tmp_file);
+    }
+    
+    ui_set(doc->tabview, 1);
+}
+
+void dav_resourceviewer_load(UiObject *ui, DavResourceViewer *res) {
+    ui_set(res->loading, 1);
+    ui_set(res->message, "Loading...");
+    ui_set(res->tabview, 0);
+    
+    ui_job(ui, jobthr_resourceviewer_load, res, resourceviewer_load_finished, res);
+}
+
--- a/application/davcontroller.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/application/davcontroller.h	Sun Oct 20 21:24:13 2024 +0200
@@ -40,6 +40,8 @@
 #endif
 
 #define DAVBROWSER_MAX_NAVLIST 128
+    
+
 
 DavBrowser* davbrowser_create(UiObject *toplevel);
 
@@ -51,6 +53,8 @@
 
 void davbrowser_query_url(UiObject *ui, DavBrowser *browser, const char *url);
 
+void davbrowser_open_resource(UiObject *ui, DavBrowser *browser, DavResource *res);
+
 void davbrowser_add2navstack(DavBrowser *browser, const char *base, const char *path);
 
 void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser);
@@ -67,6 +71,11 @@
 void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name);
 void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name);
 
+
+DavResourceViewer* dav_resourceviewer_create(DavSession *sn, const char *path, DavResourceViewType type);
+
+void dav_resourceviewer_load(UiObject *ui, DavResourceViewer *res);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/application/window.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/application/window.c	Sun Oct 20 21:24:13 2024 +0200
@@ -107,6 +107,57 @@
     ui_set(win->progress, on);
 }
 
+
+
+
+
+void dav_resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type) {
+    const char *name = util_resource_name(path);
+    UiObject *win = ui_simple_window(name, NULL);
+    
+    DavResourceViewer *doc = dav_resourceviewer_create(browser->sn, path, type);
+    ui_attach_document(win->ctx, doc);
+    
+    ui_tabview(win, .tabview = UI_TABVIEW_INVISIBLE, .varname = "tabview") {
+        /* loading / message tab */
+        ui_tab(win, NULL) {
+            ui_hbox(win, .margin = 16, .spacing = 10, .fill = UI_OFF) {
+                ui_progressspinner(win, .varname = "loading");
+                ui_label(win, .varname = "message");
+            }
+        }
+        
+        /* preview tab */
+        ui_tab(win, NULL) {
+            ui_tabview0(win) {
+                if(type == DAV_RESOURCE_VIEW_TEXT) {
+                    ui_tab(win, "Content") {
+                        ui_textarea(win, .varname = "text");
+                    }
+                } else if(type == DAV_RESOURCE_VIEW_IMAGE) {
+                    ui_tab(win, "Preview") {
+                        ui_imageviewer(win, .varname = "image");
+                    }
+                }
+                
+                ui_tab(win, "Properties") {
+                    UiModel* model = ui_model(win->ctx, UI_STRING, "Namespace", UI_STRING, "Name", UI_STRING, "Value", -1);
+                    model->getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue;
+                    ui_table(win, .fill = UI_ON, .model = model, .varname = "properties");
+                }
+            }
+        }
+    }
+    
+    dav_resourceviewer_load(win, doc);
+    
+    ui_show(win);
+}
+
+void* resourceviewer_proplist_getvalue(DavPropertyList *property, int col) {
+    return NULL;
+}
+
 static UiPathElm* dav_get_pathelm(const char *full_path, size_t len, size_t *ret_nelm, void* data) {
     cxstring fpath = cx_strn(full_path, len);
     int protocol = 0;
@@ -212,7 +263,7 @@
             if (res->iscollection) {
                 davbrowser_query_path(event->obj, browser, res->path);
             } else {
-                // TODO
+                davbrowser_open_resource(event->obj, browser, res);
             }
         }
     }
--- a/application/window.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/application/window.h	Sun Oct 20 21:24:13 2024 +0200
@@ -38,7 +38,7 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
-
+    
 /*
  * toplevel UiObject* window data 
  */
@@ -55,6 +55,10 @@
 
 void window_progress(MainWindow *win, int on);
 
+
+void dav_resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type);
+void* resourceviewer_proplist_getvalue(DavPropertyList *property, int col);
+
 void action_go_back(UiEvent *event, void *data);
 void action_go_forward(UiEvent *event, void *data);
 
--- a/ui/common/context.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/context.c	Sun Oct 20 21:24:13 2024 +0200
@@ -162,7 +162,7 @@
 
 static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
     UiVar *var = cxMapGet(ctx->vars, key);
-    if(!var) {
+    if(!var && ctx->documents) {
         CxIterator i = cxListIterator(ctx->documents);
         cx_foreach(void *, doc, i) {
             UiContext *subctx = ui_document_context(doc);
@@ -243,6 +243,9 @@
             val = ui_range_new(ctx, NULL);
             break;
         }
+        case UI_VAR_GENERIC: {
+            val = ui_generic_new(ctx, NULL);
+        }
     }
     return val;
 }
@@ -343,6 +346,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;
+        }
     }
 }
 
@@ -355,6 +366,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;
     }
 }
 
@@ -367,6 +379,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;
     }
 }
 
@@ -528,6 +541,10 @@
     cxListAdd(ctx->group_widgets, &gw);
 }
 
+UIEXPORT void *ui_ctx_allocator(UiContext *ctx) {
+    return (void*) ctx ? ctx->allocator : NULL;
+}
+
 void* ui_malloc(UiContext *ctx, size_t size) {
     return ctx ? cxMalloc(ctx->allocator, size) : NULL;
 }
--- a/ui/common/context.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/context.h	Sun Oct 20 21:24:13 2024 +0200
@@ -54,7 +54,8 @@
     UI_VAR_STRING,
     UI_VAR_TEXT,
     UI_VAR_LIST,
-    UI_VAR_RANGE
+    UI_VAR_RANGE,
+    UI_VAR_GENERIC
 };
 
 struct UiContext {
--- a/ui/common/object.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/object.c	Sun Oct 20 21:24:13 2024 +0200
@@ -51,8 +51,10 @@
 
 
 UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
-    UiContext *ctx = toplevel->ctx;
-    
+    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;
--- a/ui/common/object.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/object.h	Sun Oct 20 21:24:13 2024 +0200
@@ -36,6 +36,7 @@
 #endif
 
 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);
 
--- a/ui/common/types.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/types.c	Sun Oct 20 21:24:13 2024 +0200
@@ -281,6 +281,15 @@
     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) {
@@ -389,6 +398,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;
@@ -416,6 +431,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;
@@ -462,6 +482,13 @@
     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) {
--- a/ui/common/types.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/common/types.h	Sun Oct 20 21:24:13 2024 +0200
@@ -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,6 +57,7 @@
 void uic_text_unbind(UiText *t);
 void uic_range_unbind(UiRange *r);
 void uic_list_unbind(UiList *l);
+void uic_generic_unbind(UiGeneric *g);
   
 #ifdef	__cplusplus
 }
--- a/ui/gtk/button.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/button.c	Sun Oct 20 21:24:13 2024 +0200
@@ -401,8 +401,8 @@
                 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;
 }
--- a/ui/gtk/container.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/container.c	Sun Oct 20 21:24:13 2024 +0200
@@ -32,6 +32,7 @@
 
 #include "container.h"
 #include "toolkit.h"
+#include "headerbar.h"
 
 #include "../common/context.h"
 #include "../common/object.h"
@@ -228,10 +229,12 @@
 }
 
 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;
@@ -330,10 +333,431 @@
 }
 
 
-void ui_select_tab(UIWIDGET tabview, int tab) {
+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");
+}
+
+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 = create_grid(data->columnspacing, data->rowspacing);
+            newobj->container = ui_grid_container(newobj, sub);
+            break;
+        }
+    }
+    newobj->widget = sub;
+    GtkWidget *widget = box_set_margin(sub, data->margin);
+    
+    data->add_tab(data->widget, tab_index, name, widget);
+    
+    return newobj;
+}
+
+
+/* -------------------- 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
+
 /* -------------------- Splitpane -------------------- */
 
 static GtkWidget* create_paned(UiOrientation orientation) {
--- a/ui/gtk/container.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/container.h	Sun Oct 20 21:24:13 2024 +0200
@@ -33,6 +33,8 @@
 #include "../ui/container.h"
 #include <string.h>
 
+#include <cx/allocator.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -92,6 +94,7 @@
 #endif
 } UiGridContainer;
 
+/*
 typedef struct UiPanedContainer {
     UiContainer container;
     GtkWidget *current_pane;
@@ -99,11 +102,35 @@
     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;
+
 GtkWidget* ui_gtk_vbox_new(int spacing);
 GtkWidget* ui_gtk_hbox_new(int spacing);
 
@@ -129,10 +156,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/headerbar.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/headerbar.c	Sun Oct 20 21:24:13 2024 +0200
@@ -31,25 +31,7 @@
 #include "button.h"
 #include "menu.h"
 
-#if GTK_MAJOR_VERSION >= 3
-
-#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
+#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);
--- a/ui/gtk/headerbar.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/headerbar.h	Sun Oct 20 21:24:13 2024 +0200
@@ -38,7 +38,25 @@
 extern "C" {
 #endif
 
-#if GTK_MAJOR_VERSION >= 3
+#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);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/icon.c	Sun Oct 20 21:24:13 2024 +0200
@@ -0,0 +1,204 @@
+/*
+ * 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);
+    }
+#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	Sun Oct 20 21:24:13 2024 +0200
@@ -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 Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/image.c	Sun Oct 20 21:24:13 2024 +0200
@@ -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,170 +26,103 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <cx/map.h>
-
-#include "toolkit.h"
 #include "image.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
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
 
-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);
+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);
+    
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *eventbox = gtk_event_box_new();
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
+    gtk_container_add(GTK_CONTAINER(eventbox), image);
 #else
-    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
 #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;
+    
+    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);
+        }
     }
     
-    UiIcon *icon = malloc(sizeof(UiIcon));
-    icon->info = NULL;
-    icon->pixbuf = pixbuf;
-    return icon;
+    return scrolledwindow;
 }
 
-void ui_icon_free(UiIcon* icon) {
-    if(icon->info) {
-        g_object_unref(icon->info);
-    }
-    if(icon->pixbuf) {
-        g_object_unref(icon->pixbuf);
-    }
-    free(icon);
+void* ui_imageviewer_get(UiGeneric *g) {
+    return g->value;
 }
 
-UiIcon* ui_foldericon(size_t size) {
-    return ui_icon("folder", size);
-}
-
-UiIcon* ui_fileicon(size_t size) {
-    return ui_icon("file", size);
-}
-
-UiIcon* ui_icon_unscaled(const char *name, int size) {
-    return get_icon(name, size, 1);
+const char* ui_imageviewer_get_type(UiGeneric *g) {
+    
 }
 
-#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;
-        icon->pixbuf = gdk_pixbuf_new_from_file(g_file_get_path(file), &error);
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
+    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
+        return 1;
     }
-    return icon->pixbuf;
-}
+    
+    // TODO: do we need to free the previous value here?
+    
+    g->value = value;
+    g->type = type;
+    GdkPixbuf *pixbuf = value;
+    
+    if(pixbuf) {
+        int width, height;
+#if GTK_CHECK_VERSION(4, 12, 0)
+        GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
+        gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
+        width = gdk_texture_get_width(texture);
+        height = gdk_texture_get_height(texture);
 #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;
-}
+        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;
-}
-*/
-
-/*
-UiImage* ui_image(const char *filename) {
-    return ui_named_image(filename, NULL);
+    
+    return 0;
 }
 
-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) {
-        cxMapPut(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 Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/image.h	Sun Oct 20 21:24:13 2024 +0200
@@ -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,41 +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 {
-#if GTK_MAJOR_VERSION >= 4
-    GtkIconPaintable *info;
-#else
-    GtkIconInfo *info;
-#endif
-    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);
 
-struct UiImage {
-    GdkPixbuf *pixbuf;
-};
-
-void ui_image_init(void);
-
-GdkPixbuf* ui_get_image(const char *name);
-
-GdkPixbuf* ui_icon_pixbuf(UiIcon *icon);
-
-#ifdef	__cplusplus
+#ifdef __cplusplus
 }
 #endif
 
-#endif	/* IMAGE_H */
+#endif /* IMAGE_H */
 
--- a/ui/gtk/list.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/list.c	Sun Oct 20 21:24:13 2024 +0200
@@ -36,7 +36,7 @@
 #include "container.h"
 
 #include "list.h"
-#include "image.h"
+#include "icon.h"
 
 
 void* ui_strmodel_getvalue(void *elm, int column) {
--- a/ui/gtk/objs.mk	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/objs.mk	Sun Oct 20 21:24:13 2024 +0200
@@ -40,6 +40,7 @@
 GTKOBJ += text.o
 GTKOBJ += list.o
 GTKOBJ += image.o
+GTKOBJ += icon.o
 GTKOBJ += graphics.o
 GTKOBJ += range.o
 GTKOBJ += entry.o
--- a/ui/gtk/text.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/text.c	Sun Oct 20 21:24:13 2024 +0200
@@ -62,8 +62,13 @@
     }
 }
 
-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);
+    
     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
     g_signal_connect(
             text_area,
@@ -90,17 +95,17 @@
     SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
     
     // font and padding
-    PangoFontDescription *font;
-    font = pango_font_description_from_string("Monospace");
+    //PangoFontDescription *font;
+    //font = pango_font_description_from_string("Monospace");
     //gtk_widget_modify_font(text_area, font); // TODO
-    pango_font_description_free(font);
+    //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;
@@ -160,25 +165,6 @@
     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 SCROLLEDWINDOW_GET_CHILD(textarea);
 }
@@ -571,8 +557,8 @@
         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;
--- a/ui/gtk/toolbar.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/toolbar.c	Sun Oct 20 21:24:13 2024 +0200
@@ -33,7 +33,7 @@
 #include "toolbar.h"
 #include "menu.h"
 #include "button.h"
-#include "image.h"
+#include "icon.h"
 #include "list.h"
 #include <cx/mempool.h>
 #include <cx/hash_map.h>
--- a/ui/gtk/toolkit.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/toolkit.c	Sun Oct 20 21:24:13 2024 +0200
@@ -33,7 +33,7 @@
 
 #include "toolkit.h"
 #include "toolbar.h"
-#include "image.h"
+#include "icon.h"
 #include "../common/document.h"
 #include "../common/properties.h"
 #include "../common/menu.h"
--- a/ui/gtk/toolkit.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/toolkit.h	Sun Oct 20 21:24:13 2024 +0200
@@ -59,6 +59,8 @@
 #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_box_append(GTK_BOX(box), child)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(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()
@@ -70,7 +72,9 @@
 #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_box_pack_end(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_box_pack_end(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_end(GTK_BOX(box), child, TRUE, FALSE, 0)
 #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)
--- a/ui/gtk/window.c	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/gtk/window.c	Sun Oct 20 21:24:13 2024 +0200
@@ -122,6 +122,7 @@
 
     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);
@@ -167,7 +168,7 @@
     obj->container = ui_box_container(obj, content_box);
     */
     GtkWidget *content_box = ui_gtk_vbox_new(0);
-    BOX_ADD(GTK_BOX(vbox), content_box);
+    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
     obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
     
     nwindows++;
--- a/ui/ui/container.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/container.h	Sun Oct 20 21:24:13 2024 +0200
@@ -50,6 +50,12 @@
     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;
@@ -97,6 +103,9 @@
     UiTabViewType tabview;
 
     UiSubContainerType subcontainer;
+    
+    UiInteger *value;
+    const char* varname;
 
     int margin;
     int spacing;
@@ -107,6 +116,22 @@
     UiBool isexpanded;
 } UiTabViewArgs;
 
+typedef struct UiHeaderbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    UiBool showtitle;
+    UiBool showwindowbuttons;
+    
+    UiHeaderbarAlternative alternative;
+    int alt_spacing;
+} UiHeaderbarArgs;
+
 
 
 #define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
@@ -118,6 +143,7 @@
 #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_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))
@@ -126,31 +152,42 @@
 #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_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
 
+#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))
+
 UIEXPORT void ui_end(UiObject *obj);
     
 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);
-UIEXPORT UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs 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);
 
-UIEXPORT void ui_tab_create(UiObject* obj, const char* title);
-
-UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj);
+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_sidebar(UiObject *obj);
+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_hsplitpane(UiObject *obj, int max);
-UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max);
+UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj); // TODO
+
+UIEXPORT UIWIDGET ui_sidebar(UiObject *obj); // TODO
 
-UIEXPORT UIWIDGET ui_tabview_deprecated(UiObject *obj);
+UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
+UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
 
-UIEXPORT void ui_select_tab(UIWIDGET tabview, int tab);
 
 // box container layout functions
 UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
@@ -163,9 +200,8 @@
 UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows);
 UIEXPORT void ui_newline(UiObject *obj);
 
-
+// TODO
 UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
-
 UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
 
 
--- a/ui/ui/icons.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/icons.h	Sun Oct 20 21:24:13 2024 +0200
@@ -70,6 +70,15 @@
 #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
--- a/ui/ui/image.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/image.h	Sun Oct 20 21:24:13 2024 +0200
@@ -35,24 +35,28 @@
 extern "C" {
 #endif
 
-	/*
-UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
-UIEXPORT UiIcon* ui_imageicon(const char* file);
-UIEXPORT void ui_icon_free(UiIcon* icon);
-*/
+#define UI_IMAGE_OBJECT_TYPE "image"
+    
+typedef struct UiImageViewerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
 
-/*
-UiIcon* ui_icon(const char *name, int size);
-UiIcon* ui_icon_unscaled(const char *name, int size);
-void ui_free_icon(UiIcon *icon);
+    UiBool scrollarea;
+    UiBool autoscale;
+    UiGeneric *value;
+    const char *varname;
+} UiImageViewerArgs;
+    
+#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
+    
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args);
 
-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);
-*/
-
+int ui_image_load_file(UiGeneric *obj, const char *path);
 
 #ifdef __cplusplus
 }
--- a/ui/ui/properties.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/properties.h	Sun Oct 20 21:24:13 2024 +0200
@@ -36,9 +36,6 @@
 #endif
 
 typedef struct CxMap UiProperties;
-    
-char* ui_getappdir();
-char* ui_configfile(char *name);
 
 char* ui_get_property(char *name);
 void  ui_set_property(char *name, char *value);
--- a/ui/ui/text.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/text.h	Sun Oct 20 21:24:13 2024 +0200
@@ -35,6 +35,22 @@
 extern "C" {
 #endif
 
+typedef struct UiTextAreaArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
+
+    UiText *value;
+    const char *varname;
+    ui_callback onchange;
+    void *onchangedata;
+} UiTextAreaArgs;
+    
 typedef struct UiTextFieldArgs {
     UiTri fill;
     UiBool hexpand;
@@ -46,19 +62,19 @@
     const char *style_class;
 
     UiString* value;
-    const char* varname;
+    const char *varname;
     ui_callback onchange;
-    void* onchangedata;
+    void *onchangedata;
 } UiTextFieldArgs;
 
 typedef struct UiPathElmRet {
-    char* name;
+    char *name;
     size_t name_len;
-    char* path;
+    char *path;
     size_t path_len;
 } UiPathElm;
 
-typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void* data);
+typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void *data);
 
 
 
@@ -72,24 +88,25 @@
     const char *style_class;
 
     UiString *value;
-    const char* varname;
+    const char *varname;
 
     ui_pathelm_func getpathelm;
-    void* getpathelmdata;
+    void *getpathelmdata;
 
     ui_callback onactivate;
-    void* onactivatedata;
+    void *onactivatedata;
     
     ui_callback ondragstart;
-    void* ondragstartdata;
+    void *ondragstartdata;
     ui_callback ondragcomplete;
-    void* ondragcompletedata;
+    void *ondragcompletedata;
     ui_callback ondrop;
-    void* ondropsdata;
+    void *ondropsdata;
 } UiPathTextFieldArgs;
 
-UIWIDGET ui_textarea_deprecated(UiObject *obj, UiText *value);
-UIWIDGET ui_textarea_nv_deprecated(UiObject *obj, char *varname);
+#define ui_textarea(obj, ...) ui_textarea_create(obj, (UiTextAreaArgs) { __VA_ARGS__ })
+
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args);
 
 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
 
--- a/ui/ui/toolkit.h	Sun Oct 06 18:43:06 2024 +0200
+++ b/ui/ui/toolkit.h	Sun Oct 20 21:24:13 2024 +0200
@@ -163,6 +163,7 @@
 typedef struct UiText       UiText;
 typedef struct UiList       UiList;
 typedef struct UiRange      UiRange;
+typedef struct UiGeneric    UiGeneric;
 
 typedef struct UiStr        UiStr;
 
@@ -324,6 +325,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
@@ -435,6 +447,7 @@
 UIEXPORT void ui_unset_group(UiContext *ctx, int group);
 UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups);
     
+UIEXPORT void *ui_ctx_allocator(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);
@@ -447,6 +460,7 @@
 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, \
@@ -469,6 +483,12 @@
 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);
@@ -509,18 +529,14 @@
 
 UIEXPORT void ui_listselection_free(UiListSelection selection);
 
-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);
-
 
 UIEXPORT UiStr ui_str(char *cstr);
 UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v));
 
+
+UIEXPORT char* ui_getappdir();
+UIEXPORT char* ui_configfile(char *name);
+
 #ifdef	__cplusplus
 }
 #endif

mercurial