fix repolist menu button

Sat, 04 Oct 2025 14:52:59 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 04 Oct 2025 14:52:59 +0200
changeset 110
c00e968d018b
parent 109
c3dfcb8f0be7
child 111
81c4f73236a4

fix repolist menu button

.hgignore file | annotate | diff | comparison | revisions
ui/cocoa/BoxContainer.h file | annotate | diff | comparison | revisions
ui/cocoa/BoxContainer.m file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.h file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.m file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.h file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.m file | annotate | diff | comparison | revisions
ui/cocoa/Toolbar.m file | annotate | diff | comparison | revisions
ui/cocoa/container.h file | annotate | diff | comparison | revisions
ui/cocoa/container.m file | annotate | diff | comparison | revisions
ui/cocoa/entry.h file | annotate | diff | comparison | revisions
ui/cocoa/entry.m file | annotate | diff | comparison | revisions
ui/cocoa/objs.mk file | annotate | diff | comparison | revisions
ui/cocoa/text.m file | annotate | diff | comparison | revisions
ui/cocoa/widget.h file | annotate | diff | comparison | revisions
ui/cocoa/widget.m file | annotate | diff | comparison | revisions
ui/cocoa/window.h file | annotate | diff | comparison | revisions
ui/cocoa/window.m file | annotate | diff | comparison | revisions
ui/common/args.c file | annotate | diff | comparison | revisions
ui/common/args.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/document.c file | annotate | diff | comparison | revisions
ui/common/document.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/object.c file | annotate | diff | comparison | revisions
ui/common/properties.c file | annotate | diff | comparison | revisions
ui/common/toolbar.c file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/wrapper.c file | annotate | diff | comparison | revisions
ui/common/wrapper.h file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/button.h 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/entry.c 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/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/range.c file | annotate | diff | comparison | revisions
ui/gtk/text.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/webview.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/motif/Grid.c file | annotate | diff | comparison | revisions
ui/motif/Grid.h file | annotate | diff | comparison | revisions
ui/motif/toolkit.c file | annotate | diff | comparison | revisions
ui/qt/entry.cpp file | annotate | diff | comparison | revisions
ui/qt/toolkit.cpp file | annotate | diff | comparison | revisions
ui/qt/widget.cpp file | annotate | diff | comparison | revisions
ui/ui/container.h file | annotate | diff | comparison | revisions
ui/ui/entry.h file | annotate | diff | comparison | revisions
ui/ui/properties.h file | annotate | diff | comparison | revisions
ui/ui/toolbar.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
ui/ui/widget.h file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
ui/winui/toolkit.cpp file | annotate | diff | comparison | revisions
--- a/.hgignore	Sun Aug 24 15:24:16 2025 +0200
+++ b/.hgignore	Sat Oct 04 14:52:59 2025 +0200
@@ -1,6 +1,7 @@
 relre:^build/.*$
 relre:^config.mk$
 relre:^core$
+relre:^gmon.out$
 relre:^make/vs/.vs/.*
 relre:^make/vs/packages/.*
 relre:^make/vs/.*vcxproj\.user
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/BoxContainer.h	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,39 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "Container.h"
+#import "GridLayout.h"
+
+@interface BoxContainer : GridLayout
+
+@property NSUserInterfaceLayoutOrientation orientation;
+
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing;
+
+@end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/BoxContainer.m	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,33 @@
+
+
+#import "BoxContainer.h"
+
+@implementation BoxContainer
+
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing {
+    self = [super init];
+    _orientation = orientation;
+    self.columnspacing = spacing;
+    self.rowspacing = spacing;
+    
+    return self;
+}
+
+- (void) addView:(NSView*)view {
+    UiLayout layout = self.uilayout;
+    if(_orientation == NSUserInterfaceLayoutOrientationVertical) {
+        layout.hexpand = TRUE;
+        layout.hfill = TRUE;
+    } else {
+        layout.vexpand = TRUE;
+        layout.vfill = TRUE;
+        self.newline = FALSE;
+    }
+    self.uilayout = layout;
+    [super addView:view];
+    if(_orientation == NSUserInterfaceLayoutOrientationVertical) {
+        self.newline = TRUE;
+    }
+}
+
+@end
--- a/ui/cocoa/GridLayout.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/GridLayout.h	Sat Oct 04 14:52:59 2025 +0200
@@ -61,8 +61,6 @@
 @property CxList *children;
 @property NSSize preferredSize;
 
-@property NSButton *test;
-
 @property int x;
 @property int y;
 @property int cols;
--- a/ui/cocoa/GridLayout.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/GridLayout.m	Sat Oct 04 14:52:59 2025 +0200
@@ -41,6 +41,8 @@
     _columnspacing = 0;
     _rowspacing = 0;
     _children = cxArrayListCreateSimple(sizeof(GridElm), 32);
+    _preferredSize.width = -1;
+    _preferredSize.height = -1;
     
     return self;
 }
@@ -80,12 +82,6 @@
             
             NSSize size = elm->view.intrinsicContentSize;
             NSSize size2 = elm->view.fittingSize;
-            NSEdgeInsets alignment = elm->view.alignmentRectInsets;
-            // TODO: remove alignment
-            alignment.left = 0;
-            alignment.right = 0;
-            alignment.top = 0;
-            alignment.bottom = 0;
             if(size.width == NSViewNoIntrinsicMetric) {
                 size.width = size2.width;
             }
@@ -93,14 +89,14 @@
                 size.height = size2.height;
             }
             if(size.width != NSViewNoIntrinsicMetric) {
-                CGFloat width = size.width + alignment.left + alignment.right;
+                CGFloat width = size.width;
                 if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) {
                     cols[elm->x].preferred_size = width;
                 }
                 elm->preferred_width = width;
             }
             if(size.height != NSViewNoIntrinsicMetric) {
-                CGFloat height = size.height + alignment.top + alignment.right;
+                CGFloat height = size.height;
                 //CGFloat height = size.height;
                 if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) {
                     rows[elm->y].preferred_size = height;
@@ -290,10 +286,16 @@
  
 
 - (NSSize)intrinsicContentSize {
+    if(_preferredSize.width == -1) {
+        [self layout];
+    }
     return self.preferredSize;
 }
 
 - (void) addView:(NSView*)view {
+    _preferredSize.width = -1;
+    _preferredSize.height = -1;
+    
     if(_newline) {
         _y++;
         _x = 0;
--- a/ui/cocoa/MainWindow.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/MainWindow.h	Sat Oct 04 14:52:59 2025 +0200
@@ -31,7 +31,9 @@
 
 @interface MainWindow : NSWindow
 
-- (MainWindow*)init:(UiObject*)obj;
+@property (strong) NSView *sidebar;
+
+- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar;
 
 @end
 
--- a/ui/cocoa/MainWindow.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/MainWindow.m	Sat Oct 04 14:52:59 2025 +0200
@@ -29,6 +29,7 @@
 #import "MainWindow.h"
 #import "Container.h"
 #import "GridLayout.h"
+#import "BoxContainer.h"
 #import "../common/object.h"
 #import <objc/runtime.h>
 
@@ -38,7 +39,7 @@
 
 @implementation MainWindow
 
-- (MainWindow*)init:(UiObject*)obj {
+- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar {
     NSRect frame = NSMakeRect(300, 200, 600, 500);
     
     self = [self initWithContentRect:frame
@@ -49,24 +50,52 @@
                              backing:NSBackingStoreBuffered
                                defer:false];
     
+    
     if(uic_toolbar_isenabled()) {
         UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj];
         [self setToolbar:toolbar];
     }
     
+    int top = 4;
+    NSView *content = self.contentView;
+    if(sidebar) {
+        self.styleMask |= NSWindowStyleMaskFullSizeContentView;
+        self.titleVisibility = NSWindowTitleHidden;
+        self.titlebarAppearsTransparent = YES;
+        
+        NSSplitView *splitview = [[NSSplitView alloc]init];
+        splitview.vertical = YES;
+        splitview.dividerStyle = NSSplitViewDividerStyleThin;
+        splitview.translatesAutoresizingMaskIntoConstraints = false;
+        [self.contentView addSubview:splitview];
+        
+        [NSLayoutConstraint activateConstraints:@[
+            [splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0],
+            [splitview.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
+            [splitview.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
+            [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
+        ]];
+        
+        _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
+        [splitview addArrangedSubview:_sidebar];
+        
+        content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
+        [splitview addArrangedSubview:content];
+        
+        top = 34;
+    }
     
     // create a vertical stackview as default container
     BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
     //GridLayout *vbox = [[GridLayout alloc] init];
     vbox.translatesAutoresizingMaskIntoConstraints = false;
-    [self.contentView addSubview:vbox];
+    [content addSubview:vbox];
     [NSLayoutConstraint activateConstraints:@[
-        [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4],
-        [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
-        [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
-        [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
+        [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top],
+        [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
+        [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
+        [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor],
     ]];
-    
     uic_object_push_container(obj, ui_create_container(obj, vbox));
     
     return self;
@@ -75,7 +104,6 @@
 @end
 
 
-
 @implementation MainWindowController
 
 - (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window {
@@ -273,3 +301,27 @@
         index++;
     }
 }
+
+
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) {
+    MainWindow *window = (__bridge MainWindow*)obj->wobj;
+    if(window.sidebar == nil) {
+        return NULL;
+    }
+    NSView *sidebar = window.sidebar;
+    
+    // create a vertical stackview as default container
+    BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing];
+    //GridLayout *vbox = [[GridLayout alloc] init];
+    vbox.translatesAutoresizingMaskIntoConstraints = false;
+    [sidebar addSubview:vbox];
+    [NSLayoutConstraint activateConstraints:@[
+        [vbox.topAnchor constraintEqualToAnchor:sidebar.topAnchor constant:34],
+        [vbox.leadingAnchor constraintEqualToAnchor:sidebar.leadingAnchor],
+        [vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor],
+        [vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor]
+    ]];
+    uic_object_push_container(obj, ui_create_container(obj, vbox));
+    
+    return NULL;
+}
--- a/ui/cocoa/Toolbar.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/Toolbar.m	Sat Oct 04 14:52:59 2025 +0200
@@ -204,6 +204,7 @@
             
         }
         i->obj = (__bridge void*)seg;
+        printf("seg: %p\n", seg);
         i->get = ui_toolbar_seg_toggleitem_get;
         i->set = ui_toolbar_seg_toggleitem_set;
     }
--- a/ui/cocoa/container.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/container.h	Sat Oct 04 14:52:59 2025 +0200
@@ -75,14 +75,6 @@
 
 @end
 
-@interface BoxContainer : NSStackView<Container>
-
-- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing;
-
-@end
-
-
-
 
 
 UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
--- a/ui/cocoa/container.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/container.m	Sat Oct 04 14:52:59 2025 +0200
@@ -28,9 +28,11 @@
 
 #import "Container.h"
 #import "GridLayout.h"
+#import "BoxContainer.h"
 
 /* ------------------------- container classes ------------------------- */
 
+/*
 @implementation BoxContainer
 
 @synthesize label=_label;
@@ -88,7 +90,7 @@
 }
 
 @end
-
+*/
 
 
 /* -------------------- public container functions --------------------- */
@@ -129,7 +131,6 @@
     return (__bridge void*)grid;
 }
 
-
 void ui_container_begin_close(UiObject *obj) {
     UiContainerX *ct = obj->container_end;
     ct->close = 1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/entry.h	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+#import "Container.h"
+
+#import "../ui/entry.h"
+
+@interface UiSpinBox : NSObject<NSTextFieldDelegate>
+
+@property UiObject *obj;
+@property (weak) NSTextField *textfield;
+@property (weak) NSStepper *stepper;
+@property BOOL isInteger;
+@property ui_callback onchange;
+@property void* onchangedata;
+@property UiObserver **observers;
+
+- (UiSpinBox*)init;
+
+- (void)valueChanged;
+- (void)stepperChanged:(id)sender;
+- (void) controlTextDidChange:(NSNotification *) obj;
+
+@end
+
+int64_t ui_spinbutton_getint(UiInteger *i);
+void ui_spinbutton_setint(UiInteger *i, int64_t val);
+
+double ui_spinbutton_getdouble(UiDouble *d);
+void ui_spinbutton_setdouble(UiDouble *d, double val);
+
+double ui_spinbutton_getrangeval(UiRange *r);
+void ui_spinbutton_setrangeval(UiRange *r, double val);
+void ui_spinbutton_setrange(UiRange *r, double min, double max);
+void ui_spinbutton_setextent(UiRange *r, double extent);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/entry.m	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,272 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "entry.h"
+#import <objc/runtime.h>
+
+@implementation UiSpinBox
+
+- (UiSpinBox*)init {
+    return self;
+}
+
+- (void)valueChanged {
+    float value = _stepper.doubleValue;
+    UiEvent e;
+    e.obj = _obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = NULL;
+    e.eventdatatype = 0;
+    e.intval = (int)value;
+    e.set = ui_get_setop();
+    
+    if(_onchange) {
+        _onchange(&e, _onchangedata);
+    }
+    
+    if(_observers) {
+        UiObserver *observer = *_observers;
+        ui_notify_evt(observer, &e);
+    }
+}
+
+- (void)stepperChanged:(id)sender {
+    if(_isInteger) {
+        _textfield.integerValue = _stepper.integerValue;
+    } else {
+        _textfield.doubleValue = _stepper.doubleValue;
+    }
+    [self valueChanged];
+}
+
+- (void) controlTextDidChange:(NSNotification *)obj {
+    if(_isInteger) {
+        _stepper.integerValue = _textfield.integerValue;
+    } else {
+        _stepper.doubleValue = _textfield.doubleValue;
+    }
+    [self valueChanged];
+}
+
+@end
+
+UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) {
+    double min = args->min;
+    double max = args->max != 0 ? args->max : 1000;
+    
+    UiVar *var = NULL;
+    UiVarType vartype = 0;
+    if(args->varname) {
+        var = uic_get_var(obj->ctx, args->varname);
+        if(var) {
+            vartype = var->type;
+        } else {
+            var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE);
+            vartype = UI_VAR_RANGE;
+        }
+    }
+    
+    if(!var) {
+        if(args->intvalue) {
+            var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER);
+            vartype = UI_VAR_INTEGER;
+        } else if(args->doublevalue) {
+            var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE);
+            vartype = UI_VAR_DOUBLE;
+        } else if(args->rangevalue) {
+            var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE);
+            vartype = UI_VAR_RANGE;
+        }
+    }
+    
+    if(vartype == UI_VAR_RANGE) {
+        UiRange *r = var->value;
+        min = r->min;
+        max = r->max;
+    }
+    if(args->step == 0) {
+        args->step = 1;
+    }
+    
+    // create and setup textfield for number input
+    NSTextField *textfield = [[NSTextField alloc] init];
+    textfield.translatesAutoresizingMaskIntoConstraints = NO;
+    
+    if(!args->hfill || args->width > 0) {
+        textfield.translatesAutoresizingMaskIntoConstraints = NO;
+        int width = args->width > 0 ? args->width : 100;
+        [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES];
+    }
+    
+    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+    formatter.numberStyle = NSNumberFormatterDecimalStyle;
+    formatter.allowsFloats = vartype != UI_VAR_INTEGER;
+    formatter.minimumFractionDigits = args->digits;
+    formatter.maximumFractionDigits = args->digits;
+
+    textfield.formatter = formatter;
+
+    // create view containing the textfield and stepper
+    NSView *view = [[NSView alloc]init];
+    view.translatesAutoresizingMaskIntoConstraints = NO;
+    
+    NSStepper *stepper = [[NSStepper alloc] init];
+    stepper.translatesAutoresizingMaskIntoConstraints = NO;
+    
+    [view addSubview:textfield];
+    [view addSubview:stepper];
+
+    [NSLayoutConstraint activateConstraints:@[
+        [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
+        [textfield.topAnchor constraintEqualToAnchor:view.topAnchor],
+        [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
+        
+        [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
+        [stepper.topAnchor constraintEqualToAnchor:view.topAnchor],
+        [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
+        
+        [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor]
+    ]];
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, view, &layout);
+    
+    // create the spinbox object, that handles all textfield and stepper events
+    UiSpinBox *spinbox = [[UiSpinBox alloc]init];
+    spinbox.obj = obj;
+    spinbox.textfield = textfield;
+    spinbox.stepper = stepper;
+    spinbox.onchange = args->onchange;
+    spinbox.onchangedata = args->onchangedata;
+    spinbox.isInteger = vartype == UI_VAR_INTEGER;
+    objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN);
+    
+    stepper.minValue = min;
+    stepper.maxValue = max;
+    stepper.increment = args->step;
+    stepper.target = spinbox;
+    stepper.action = @selector(stepperChanged:);
+    textfield.delegate = spinbox;
+    
+    UiObserver **obs = NULL;
+    if(var) {
+        void *varObj = (__bridge void*)spinbox;
+        switch(vartype) {
+            default: break;
+            case UI_VAR_INTEGER: {
+                UiInteger *i = var->value;
+                i->get = ui_spinbutton_getint;
+                i->set = ui_spinbutton_setint;
+                i->obj = varObj;
+                obs = &i->observers;
+                
+                stepper.integerValue = i->value;
+                textfield.integerValue = i->value;
+                break;
+            }
+            case UI_VAR_DOUBLE: {
+                UiDouble *d = var->value;
+                d->get = ui_spinbutton_getdouble;
+                d->set = ui_spinbutton_setdouble;
+                d->obj = varObj;
+                obs = &d->observers;
+                
+                stepper.doubleValue = d->value;
+                textfield.doubleValue = d->value;
+                break;
+            }
+            case UI_VAR_RANGE: {
+                UiRange *r = var->value;
+                r->get = ui_spinbutton_getrangeval;
+                r->set = ui_spinbutton_setrangeval;
+                r->setrange = ui_spinbutton_setrange;
+                r->setextent = ui_spinbutton_setextent;
+                r->obj = varObj;
+                obs = &r->observers;
+                
+                stepper.doubleValue = r->value;
+                textfield.doubleValue = r->value;
+                break;
+            }
+        }
+    }
+    spinbox.observers = obs;
+    
+    return (__bridge void*)textfield;
+}
+
+int64_t ui_spinbutton_getint(UiInteger *i) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj;
+    i->value = spinbox.stepper.integerValue;
+    return i->value;
+}
+
+void ui_spinbutton_setint(UiInteger *i, int64_t val) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj;
+    i->value = val;
+    spinbox.stepper.integerValue = val;
+    spinbox.textfield.integerValue = val;
+}
+
+double ui_spinbutton_getdouble(UiDouble *d) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj;
+    d->value = spinbox.stepper.doubleValue;
+    return d->value;
+}
+
+void ui_spinbutton_setdouble(UiDouble *d, double val) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj;
+    d->value = val;
+    spinbox.stepper.doubleValue = val;
+    spinbox.textfield.doubleValue = val;
+}
+
+double ui_spinbutton_getrangeval(UiRange *r) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
+    r->value = spinbox.stepper.doubleValue;
+    return r->value;
+}
+
+void ui_spinbutton_setrangeval(UiRange *r, double val) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
+    r->value = val;
+    spinbox.stepper.doubleValue = val;
+    spinbox.textfield.doubleValue = val;
+}
+
+void ui_spinbutton_setrange(UiRange *r, double min, double max) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
+    spinbox.stepper.minValue = min;
+    spinbox.stepper.maxValue = max;
+}
+
+void ui_spinbutton_setextent(UiRange *r, double extent) {
+    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
+    spinbox.stepper.increment = extent;
+}
--- a/ui/cocoa/objs.mk	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/objs.mk	Sat Oct 04 14:52:59 2025 +0200
@@ -32,8 +32,9 @@
 COCOAOBJ = toolkit.o
 COCOAOBJ += AppDelegate.o
 COCOAOBJ += GridLayout.o
+COCOAOBJ += BoxContainer.o
 COCOAOBJ += EventData.o
-COCOAOBJ += UiJob.o
+COCOAOBJ += UiThread.o
 COCOAOBJ += MainWindow.o
 COCOAOBJ += WindowManager.o
 COCOAOBJ += window.o
@@ -42,6 +43,13 @@
 COCOAOBJ += text.o
 COCOAOBJ += menu.o
 COCOAOBJ += Toolbar.o
+COCOAOBJ += ListDelegate.o
+COCOAOBJ += ListDataSource.o
+COCOAOBJ += label.o
+COCOAOBJ += list.o
+COCOAOBJ += widget.o
+COCOAOBJ += image.o
+COCOAOBJ += entry.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/text.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/text.m	Sat Oct 04 14:52:59 2025 +0200
@@ -182,7 +182,14 @@
         textfield = [[NSSecureTextField alloc] init];
     } else {
         textfield = [[NSTextField alloc] init];
-    } 
+    }
+    
+    if(!args->hfill || args->width > 0) {
+        textfield.translatesAutoresizingMaskIntoConstraints = NO;
+        int width = args->width > 0 ? args->width : 100;
+        [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES];
+    }
+    
     
     if(frameless) {
         [textfield setBezeled: NO];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/widget.h	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,31 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+#import "container.h"
+#import "../ui/widget.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/widget.m	Sat Oct 04 14:52:59 2025 +0200
@@ -0,0 +1,59 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "widget.h"
+
+/* genereal widget functions */
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    NSControl *control = (__bridge NSControl*)widget;
+    control.enabled = enabled;
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    // TODO: is this relevant?
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    NSView *view = (__bridge NSView*)widget;
+    view.hidden = !visible;
+}
+
+
+
+/* custom widget */
+
+UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) {
+    UIWIDGET widget = create_widget(obj, args, userdata);
+    
+    NSView *view = (__bridge NSView*)widget;
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, view, &layout);
+    
+    return widget;
+}
--- a/ui/cocoa/window.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/window.h	Sat Oct 04 14:52:59 2025 +0200
@@ -26,4 +26,5 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "toolkit.h"
\ No newline at end of file
+#import "toolkit.h"
+#import "../ui/window.h"
--- a/ui/cocoa/window.m	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/cocoa/window.m	Sat Oct 04 14:52:59 2025 +0200
@@ -42,14 +42,14 @@
 #include <cx/mempool.h>
 
 
-static UiObject* create_window(const char *title, BOOL simple) {
+static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar) {
     CxMempool *mp = cxMempoolCreateSimple(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
     obj->ref = 0;
     
     obj->ctx = uic_context(obj, mp);
     
-    MainWindow *window = [[MainWindow alloc] init:obj];
+    MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar];
     [[WindowManager sharedWindowManager] addWindow:window];
     window.releasedWhenClosed = false;
     
@@ -64,13 +64,169 @@
 }
 
 UiObject* ui_window(const char *title, void *window_data) {
-    UiObject *obj = create_window(title, FALSE);
+    UiObject *obj = create_window(title, FALSE, FALSE);
     obj->window = window_data;
     return obj;
 }
 
 UiObject* ui_simple_window(const char *title, void *window_data) {
-    UiObject *obj = create_window(title, TRUE);
+    UiObject *obj = create_window(title, TRUE, FALSE);
+    obj->window = window_data;
+    return obj;
+}
+
+UiObject* ui_sidebar_window(const char *title, void *window_data) {
+    UiObject *obj = create_window(title, FALSE, TRUE);
     obj->window = window_data;
     return obj;
 }
+
+/* --------------------------------- File Dialogs --------------------------------- */
+
+void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+    if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+        openPanel.allowsMultipleSelection = YES;
+    }
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        openPanel.canChooseFiles = NO;
+        openPanel.canChooseDirectories = YES;
+    }
+    
+    NSWindow *window = (__bridge NSWindow*)obj->wobj;
+    [openPanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) {
+        UiEvent event;
+        event.obj = obj;
+        event.window = obj->window;
+        event.document = obj->ctx->document;
+        event.intval = 0;
+        event.set = 0;
+        
+        UiFileList flist = { NULL, 0 };
+        event.eventdata = &flist;
+        event.eventdatatype = UI_EVENT_DATA_FILE_LIST;
+        
+        if(result == NSModalResponseOK) {
+            NSArray<NSURL *> *urls = [openPanel URLs];
+            flist.files = calloc(urls.count, sizeof(char*));
+            for(NSURL *url in urls) {
+                if([url isFileURL]) {
+                    flist.files[flist.nfiles++] = strdup(url.path.UTF8String);
+                }
+            }
+        }
+        
+        if(file_selected_callback) {
+            file_selected_callback(&event, cbdata);
+        }
+        ui_filelist_free(flist);
+    }];
+}
+
+void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    NSSavePanel *savePanel = [NSSavePanel savePanel];
+    if(name) {
+        NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
+        [savePanel setNameFieldStringValue: nameStr];
+    }
+    
+    NSWindow *window = (__bridge NSWindow*)obj->wobj;
+    [savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) {
+        UiEvent event;
+        event.obj = obj;
+        event.window = obj->window;
+        event.document = obj->ctx->document;
+        event.intval = 0;
+        event.set = 0;
+        
+        UiFileList flist = { NULL, 0 };
+        event.eventdata = &flist;
+        event.eventdatatype = UI_EVENT_DATA_FILE_LIST;
+        
+        if(result == NSModalResponseOK) {
+            NSURL *url = [savePanel URL];
+            if([url isFileURL]) {
+                NSString *path = url.path;
+                flist.files = malloc(sizeof(char*));
+                flist.files[0] = strdup(path.UTF8String);
+                flist.nfiles = 1;
+            }
+            file_selected_callback(NULL, NULL);
+        }
+        if(file_selected_callback) {
+            file_selected_callback(&event, cbdata);
+        }
+        ui_filelist_free(flist);
+    }];
+}
+
+/* ------------------------------------- Dialog ------------------------------------- */
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs *args) {
+    NSAlert *dialog = [[NSAlert alloc] init];
+    
+    if(args->title) {
+        dialog.messageText = [[NSString alloc]initWithUTF8String:args->title];
+    }
+    if(args->content) {
+        dialog.informativeText = [[NSString alloc]initWithUTF8String:args->content];
+    }
+    NSTextField *textfield = nil;
+    if(args->input) {
+        NSRect frame = NSMakeRect(0,0,300,22);
+        textfield = args->password ? [[NSSecureTextField alloc] initWithFrame:frame] : [[NSTextField alloc]initWithFrame:frame];
+        if(args->input_value) {
+            textfield.stringValue = [[NSString alloc]initWithUTF8String:args->input_value];
+        }
+        dialog.accessoryView = textfield;
+    }
+    
+    int b = 0;
+    int b1 = -1;
+    int b2 = -1;
+    if(args->button1_label) {
+        [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button1_label]];
+        b1 = b++;
+    }
+    if(args->button2_label) {
+        [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button2_label]];
+        b2 = b;
+    }
+    if(args->closebutton_label) {
+        [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->closebutton_label]];
+    }
+    
+    ui_callback callback = args->result;
+    void *userdata = args->resultdata;
+    
+    NSWindow *window = (__bridge NSWindow*)parent->wobj;
+    [dialog beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) {
+        UiEvent event;
+        event.obj = parent;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = NULL;
+        event.eventdatatype = 0;
+        event.set = 0;
+        event.intval = 0;
+        
+        long ret = returnCode - NSAlertFirstButtonReturn;
+        if(ret == b1) {
+            event.intval = 1;
+        } else if(ret == b2) {
+            event.intval = 2;
+        }
+        
+        NSString *value = nil;
+        if(textfield) {
+            value = textfield.stringValue;
+            event.eventdata = (void*)value.UTF8String;
+            event.eventdatatype = UI_EVENT_DATA_STRING;
+        }
+        
+        if(callback) {
+            callback(&event, userdata);
+        }
+    }];
+    
+}
--- a/ui/common/args.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/args.c	Sat Oct 04 14:52:59 2025 +0200
@@ -320,13 +320,16 @@
     args->onclickdata = onclickdata;
 }
 
-void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *groups) {
-    // TODO
+void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 void ui_toolbar_item_args_free(UiToolbarItemArgs *args) {
     free((void*)args->label);
     free((void*)args->stockid);
     free((void*)args->icon);
+    free((void*)args->groups);
     free(args);
 }
 
@@ -369,8 +372,10 @@
 }
 
 
-void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *groups) {
-    // TODO
+void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 
@@ -379,6 +384,7 @@
     free((void*)args->stockid);
     free((void*)args->icon);
     free((void*)args->varname);
+    free((void*)args->groups);
     free(args);
 }
 
@@ -424,7 +430,7 @@
 }
 
 void ui_container_args_set_fill(UiContainerArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 void ui_container_args_set_hexpand(UiContainerArgs *args, UiBool value) {
@@ -529,7 +535,7 @@
 
 
 void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -662,7 +668,7 @@
 
 
 void ui_splitpane_args_set_fill(UiSplitPaneArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -735,6 +741,9 @@
     args->initial_position = pos;
 }
 
+void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname) {
+    args->position_property = strdup(propname);
+}
 
 void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname) {
     args->varname = strdup(varname);
@@ -754,6 +763,115 @@
     free((void*)args->name);
     free((void*)args->style_class);
     free((void*)args->varname);
+    free((void*)args->position_property);
+    free(args);
+}
+
+
+/* ---------------------------- UiTabViewArgs ---------------------------- */
+
+UiTabViewArgs* ui_tabview_args_new(void) {
+    UiTabViewArgs *args = malloc(sizeof(UiTabViewArgs));
+    memset(args, 0, sizeof(UiTabViewArgs));
+    return args;
+}
+
+void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill) {
+    args->fill = fill;
+}
+
+void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value) {
+    args->hexpand = value;
+}
+
+
+void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value) {
+    args->vexpand = value;
+}
+
+
+void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value) {
+    args->hfill = value;
+}
+
+
+void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value) {
+    args->vfill = value;
+}
+
+
+void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value) {
+    args->override_defaults = value;
+}
+
+
+void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan) {
+    args->colspan = colspan;
+}
+
+
+void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan) {
+    args->rowspan = rowspan;
+}
+
+
+void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name) {
+    args->name = strdup(name);
+}
+
+
+void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname) {
+    args->style_class = strdup(classname);
+}
+
+
+void ui_tabview_args_set_margin(UiTabViewArgs *args, int value) {
+    args->margin = value;
+}
+
+
+void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value) {
+    args->spacing = value;
+}
+
+
+void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value) {
+    args->columnspacing = value;
+}
+
+
+void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value) {
+    args->rowspacing = value;
+}
+
+void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview) {
+    args->tabview = tabview;
+}
+
+void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb) {
+    args->onchange = cb;
+}
+
+void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata) {
+    args->onchangedata = userdata;
+}
+
+void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname) {
+    args->varname = strdup(varname);
+}
+
+void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value) {
+    args->value = value;
+}
+
+void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer) {
+    args->subcontainer = subcontainer;
+}
+
+void ui_tabview_args_free(UiTabViewArgs *args) {
+    free((void*)args->name);
+    free((void*)args->style_class);
+    free((void*)args->varname);
     free(args);
 }
 
@@ -768,7 +886,7 @@
 
 
 void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -834,7 +952,7 @@
 
 
 void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -923,7 +1041,7 @@
 
 
 void ui_progressbar_args_set_fill(UiProgressbarArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1004,7 +1122,7 @@
 }
 
 void ui_progress_spinner_args_set_fill(UiProgressbarSpinnerArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 void ui_progress_spinner_args_set_hexpand(UiProgressbarSpinnerArgs *args, UiBool value) {
@@ -1069,7 +1187,7 @@
 
 
 void ui_button_args_set_fill(UiButtonArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1145,8 +1263,10 @@
     args->onclickdata = onclickdata;
 }
 
-void ui_button_args_set_groups(UiButtonArgs *args, int *groups){
-    // TODO
+void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_button_args_free(UiButtonArgs *args) {
@@ -1171,7 +1291,7 @@
 
 
 void ui_toggle_args_set_fill(UiToggleArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1259,8 +1379,10 @@
     args->enable_group = group;
 }
 
-void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups){
-    // TODO
+void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_toggle_args_free(UiToggleArgs *args) {
@@ -1285,7 +1407,7 @@
 
 
 void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1365,8 +1487,10 @@
     args->value = value;
 }
 
-void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups){
-    // TODO
+void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_linkbutton_args_free(UiLinkButtonArgs *args) {
@@ -1389,7 +1513,7 @@
 }
 
 void ui_list_args_set_fill(UiListArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 void ui_list_args_set_hexpand(UiListArgs *args, UiBool value) {
@@ -1461,6 +1585,14 @@
     args->getvalue2data = userdata;
 }
 
+void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle) {
+    args->getstyle = getstyle;
+}
+
+void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata) {
+    args->getstyledata = userdata;
+}
+
 void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback) {
     args->onactivate = callback;
 }
@@ -1501,6 +1633,14 @@
     args->ondropdata = userdata;
 }
 
+void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave) {
+    args->onsave = onsave;
+}
+
+void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata) {
+    args->onsavedata = userdata;
+}
+
 void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection) {
     args->multiselection = multiselection;
 }
@@ -1509,8 +1649,10 @@
     args->contextmenu = menubuilder;
 }
 
-void ui_list_args_set_groups(UiListArgs *args, int *groups) {
-    // TODO
+void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_list_args_free(UiListArgs *args) {
@@ -1523,6 +1665,7 @@
         }
         free(args->static_elements);
     }
+    free((void*)args->groups);
     free(args);
 }
 
@@ -1538,7 +1681,7 @@
 
 
 void ui_sourcelist_args_set_fill(UiSourceListArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1630,12 +1773,16 @@
     args->onbuttonclickdata = userdata;
 }
 
+void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder) {
+    args->contextmenu = menubuilder;
+}
 
 void ui_sourcelist_args_free(UiSourceListArgs *args) {
     free((void*)args->name);
     free((void*)args->style_class);
     free((void*)args->varname);
     free((void*)args->sublists);
+    free((void*)args->groups);
     free(args);
 }
 
@@ -1650,7 +1797,7 @@
 
 
 void ui_textarea_args_set_fill(UiTextAreaArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1715,8 +1862,10 @@
     args->value = value;
 }
 
-void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *groups){
-    // TODO
+void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_textarea_args_free(UiTextAreaArgs *args) {
@@ -1739,7 +1888,7 @@
 
 
 void ui_textfield_args_set_fill(UiTextFieldArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1813,8 +1962,10 @@
     args->value = value;
 }
 
-void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *groups){
-    // TODO
+void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_textfield_args_free(UiTextFieldArgs *args) {
@@ -1826,6 +1977,109 @@
 }
 
 
+/* ------------------------- UiSpinBoxArgs ----------------------------*/
+
+UiSpinBoxArgs* ui_spinbox_args_new(void) {
+    UiSpinBoxArgs *args = malloc(sizeof(UiSpinBoxArgs));
+    memset(args, 0, sizeof(UiSpinBoxArgs));
+    return args;
+}
+
+void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill) {
+    args->fill = fill;
+}
+
+void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value) {
+    args->hexpand = value;
+}
+
+void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value) {
+    args->vexpand = value;
+}
+
+void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value) {
+    args->hfill = value;
+}
+
+void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value) {
+    args->vfill = value;
+}
+
+void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value) {
+    args->override_defaults = value;
+}
+
+void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan) {
+    args->colspan = colspan;
+}
+
+void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan) {
+    args->rowspan = rowspan;
+}
+
+void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name) {
+    args->name = strdup(name);
+}
+
+void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname) {
+    args->style_class = strdup(classname);
+}
+
+void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback) {
+    args->onchange = callback;
+}
+
+void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata) {
+    args->onchangedata = onchangedata;
+}
+
+void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min) {
+    args->min = min;
+}
+
+void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max) {
+    args->max = max;
+}
+
+void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step) {
+    args->step = step;
+}
+
+void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits) {
+    args->digits;
+}
+
+void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname) {
+    args->varname = strdup(varname);
+}
+
+void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value) {
+    args->intvalue = value;
+}
+
+void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value) {
+    args->doublevalue = value;
+}
+
+void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value) {
+    args->rangevalue = value;
+}
+
+void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
+}
+
+void ui_spinbox_args_free(UiSpinBoxArgs *args) {
+    free((void*)args->name);
+    free((void*)args->style_class);
+    free((void*)args->varname);
+    free((void*)args->groups);
+    free(args);
+}
+
+
 /* ------------------------- UiWebviewArgs ----------------------------*/
 
 UiWebviewArgs* ui_webview_args_new(void) {
@@ -1836,7 +2090,7 @@
 
 
 void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill) {
-    args->fill = fill ? UI_ON : UI_OFF;
+    args->fill = fill;
 }
 
 
@@ -1892,8 +2146,10 @@
     args->value = value;
 }
 
-void ui_webview_args_set_groups(UiWebviewArgs *args, int *groups){
-    // TODO
+void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) {
+    args->groups = calloc(numstates+1, sizeof(int));
+    memcpy((void*)args->groups, states, numstates * sizeof(int));
+    ((int*)args->groups)[numstates] = -1;
 }
 
 void ui_webview_args_free(UiWebviewArgs *args) {
--- a/ui/common/args.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/args.h	Sat Oct 04 14:52:59 2025 +0200
@@ -33,6 +33,7 @@
 #include "../ui/container.h"
 #include "../ui/display.h"
 #include "../ui/button.h"
+#include "../ui/entry.h"
 #include "../ui/menu.h"
 #include "../ui/toolbar.h"
 #include "../ui/tree.h"
@@ -108,7 +109,7 @@
 UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon);
 UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback);
 UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata);
-UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *groups);
+UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates);
 UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args);
 
 UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void);
@@ -118,7 +119,7 @@
 UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname);
 UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback);
 UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata);
-UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *groups);
+UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates);
 UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args);
 
 UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void);
@@ -148,7 +149,6 @@
 UIEXPORT void ui_container_args_set_rowspacing(UiContainerArgs *args, int value);
 UIEXPORT void ui_container_args_free(UiContainerArgs *args);
     
-
 UIEXPORT UiFrameArgs* ui_frame_args_new(void);
 UIEXPORT void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill);
 UIEXPORT void ui_frame_args_set_hexpand(UiFrameArgs *args, UiBool value);
@@ -191,11 +191,35 @@
 UIEXPORT void ui_splitpane_args_set_columnspacing(UiSplitPaneArgs *args, int value);
 UIEXPORT void ui_splitpane_args_set_rowspacing(UiSplitPaneArgs *args, int value);
 UIEXPORT void ui_splitpane_args_set_initial_position(UiSplitPaneArgs *args, int pos);
+UIEXPORT void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname);
 UIEXPORT void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname);
 UIEXPORT void ui_splitpane_args_set_value(UiSplitPaneArgs *args, UiInteger *value);
 UIEXPORT void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max);
 UIEXPORT void ui_splitpane_args_free(UiSplitPaneArgs *args);
 
+UIEXPORT UiTabViewArgs* ui_tabview_args_new(void);
+UIEXPORT void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill);
+UIEXPORT void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value);
+UIEXPORT void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value);
+UIEXPORT void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value);
+UIEXPORT void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value);
+UIEXPORT void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value);
+UIEXPORT void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan);
+UIEXPORT void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan);
+UIEXPORT void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name);
+UIEXPORT void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname);
+UIEXPORT void ui_tabview_args_set_margin(UiTabViewArgs *args, int value);
+UIEXPORT void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value);
+UIEXPORT void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value);
+UIEXPORT void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value);
+UIEXPORT void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview);
+UIEXPORT void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb);
+UIEXPORT void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata);
+UIEXPORT void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname);
+UIEXPORT void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value);
+UIEXPORT void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer);
+UIEXPORT void ui_tabview_args_free(UiTabViewArgs *args);
+
 UIEXPORT UiWidgetArgs* ui_widget_args_new(void);
 UIEXPORT void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill);
 UIEXPORT void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value);
@@ -276,7 +300,7 @@
 UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype);
 UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback);
 UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata);
-UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *groups);
+UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates);
 UIEXPORT void ui_button_args_free(UiButtonArgs *args);
 
 UIEXPORT UiToggleArgs* ui_toggle_args_new(void);
@@ -299,7 +323,7 @@
 UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname);
 UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value);
 UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group);
-UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups);
+UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates);
 UIEXPORT void ui_toggle_args_free(UiToggleArgs *args);
 
 UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void);
@@ -321,7 +345,7 @@
 UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata);
 UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value);
 UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type);
-UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups);
+UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates);
 UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args);
 
 UIEXPORT UiListArgs* ui_list_args_new(void);
@@ -342,6 +366,8 @@
 UIEXPORT void ui_list_args_set_getvalue_func(UiListArgs *args, ui_getvaluefunc getvalue);
 UIEXPORT void ui_list_args_set_getvalue_func2(UiListArgs *args, ui_getvaluefunc2 getvalue);
 UIEXPORT void ui_list_args_set_getvalue_data(UiListArgs *args, void *userdata);
+UIEXPORT void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle);
+UIEXPORT void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata);
 UIEXPORT void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback);
 UIEXPORT void ui_list_args_set_onactivatedata(UiListArgs *args, void *userdata);
 UIEXPORT void ui_list_args_set_onselection(UiListArgs *args, ui_callback callback);
@@ -352,9 +378,11 @@
 UIEXPORT void ui_list_args_set_ondragcompletedata(UiListArgs *args, void *userdata);
 UIEXPORT void ui_list_args_set_ondrop(UiListArgs *args, ui_callback callback);
 UIEXPORT void ui_list_args_set_ondropdata(UiListArgs *args, void *userdata);
+UIEXPORT void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave);
+UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata);
 UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection);
 UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder);
-UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *groups);
+UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates);
 UIEXPORT void ui_list_args_free(UiListArgs *args);
 
 UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void);
@@ -377,6 +405,7 @@
 UIEXPORT void ui_sourcelist_args_set_onactivatedata(UiSourceListArgs *args, void *userdata);
 UIEXPORT void ui_sourcelist_args_set_onbuttonclick(UiSourceListArgs *args, ui_callback callback);
 UIEXPORT void ui_sourcelist_args_set_onbuttonclickdata(UiSourceListArgs *args, void *userdata);
+UIEXPORT void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder);
 UIEXPORT void ui_sourcelist_args_free(UiSourceListArgs *args);
 
 UIEXPORT UiTextAreaArgs* ui_textarea_args_new(void);
@@ -394,7 +423,7 @@
 UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata);
 UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname);
 UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value);
-UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *groups);
+UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates);
 UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args);
 
 UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void);
@@ -414,9 +443,33 @@
 UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata);
 UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname);
 UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value);
-UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *groups);
+UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates);
 UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args);
 
+UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void);
+UIEXPORT void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill);
+UIEXPORT void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value);
+UIEXPORT void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value);
+UIEXPORT void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value);
+UIEXPORT void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value);
+UIEXPORT void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value);
+UIEXPORT void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan);
+UIEXPORT void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan);
+UIEXPORT void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name);
+UIEXPORT void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname);
+UIEXPORT void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback);
+UIEXPORT void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata);
+UIEXPORT void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min);
+UIEXPORT void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max);
+UIEXPORT void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step);
+UIEXPORT void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits);
+UIEXPORT void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname);
+UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value);
+UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value);
+UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value);
+UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates);
+UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args);
+
 UIEXPORT UiWebviewArgs* ui_webview_args_new(void);
 UIEXPORT void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill);
 UIEXPORT void ui_webview_args_set_hexpand(UiWebviewArgs *args, UiBool value);
@@ -430,7 +483,7 @@
 UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname);
 UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname);
 UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value);
-UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *groups);
+UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates);
 UIEXPORT void ui_webview_args_free(UiWebviewArgs *args);
 
 #ifdef __cplusplus
--- a/ui/common/context.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/context.c	Sat Oct 04 14:52:59 2025 +0200
@@ -38,6 +38,7 @@
 
 #include "context.h"
 #include "../ui/window.h"
+#include "../ui/widget.h"
 #include "document.h"
 #include "types.h"
 
@@ -102,21 +103,19 @@
     UiContext *doc_ctx = ui_document_context(document);
     doc_ctx->parent = ctx;
     
-    // check if any parent context has an unbound variable with the same name
-    // as any document variable
+    // if a document variable has the same name as a parent variable,
+    // move the bindings to the document
     UiContext *var_ctx = ctx;
     while(var_ctx) {
-        if(var_ctx->vars_unbound &&  cxMapSize(var_ctx->vars_unbound) > 0) {
-            CxMapIterator i = cxMapIterator(var_ctx->vars_unbound);
-            cx_foreach(CxMapEntry*, entry, i) {
-                printf("attach %s\n", entry->key->data);
-                UiVar *var = entry->value;
-                UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
-                if(docvar) {
-                    // bind var to document var
-                    uic_copy_binding(var, docvar, TRUE);
-                    cxIteratorFlagRemoval(i);
-                }
+        CxMapIterator i = cxMapIterator(var_ctx->vars);
+        cx_foreach(CxMapEntry*, entry, i) {
+            printf("attach %.*s\n", (int)entry->key->len, entry->key->data);
+            UiVar *var = entry->value;
+            UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
+            if(docvar) {
+                // bind var to document var
+                uic_copy_binding(var, docvar, TRUE);
+                cxIteratorFlagRemoval(i);
             }
         }
         
@@ -129,10 +128,10 @@
     cx_foreach(CxMapEntry*, entry, mi) {
         UiVar *var = entry->value;
         // var->from && var->from_ctx && var->from_ctx != ctx
+        uic_save_var(var);
         if(var->from) {
-            uic_save_var2(var);
             uic_copy_binding(var, var->from, FALSE);
-            cxMapPut(var->from->from_ctx->vars_unbound, *entry->key, var->from);
+            cxMapPut(var->from->from_ctx->vars, *entry->key, var->from);
             var->from = NULL;
         }
     }
@@ -199,13 +198,6 @@
 }
 
 UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) {
-    if(ctx->vars_unbound) {
-        UiVar *unbound = cxMapGet(ctx->vars_unbound, name);
-        if(unbound) {
-            return unbound;
-        }
-    }
-    
     UiVar *var = uic_get_var(ctx, name);
     if(var) {
         if(var->type == type) {
@@ -224,10 +216,7 @@
 
     cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var);
 
-    if(!ctx->vars_unbound) {
-        ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
-    }
-    cxMapPut(ctx->vars_unbound, name, var);
+    cxMapPut(ctx->vars, name, var);
     
     return var;
 }
@@ -278,7 +267,7 @@
 }
 
 
-UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) {
+UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type) {
     if (value) {
         return uic_create_value_var(current, value);
     }
@@ -383,7 +372,7 @@
     ui_setop_enable(FALSE);
 }
 
-void uic_save_var2(UiVar *var) {
+void uic_save_var(UiVar *var) {
     switch(var->type) {
         case UI_VAR_SPECIAL: break;
         case UI_VAR_INTEGER: uic_int_save(var->value); break;
@@ -542,6 +531,9 @@
 }
 
 void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
+    if(enable == NULL) {
+        enable = (ui_enablefunc)ui_set_enabled;
+    }
     // get groups
     CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
     va_list ap;
@@ -557,6 +549,22 @@
     cxListFree(groups);
 }
 
+void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups) {
+    if(enable == NULL) {
+        enable = (ui_enablefunc)ui_set_enabled;
+    }
+    CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups);
+    for(int i=0;i<ngroups;i++) {
+        cxListAdd(ls, groups+i);
+    }
+    uic_add_group_widget(ctx, widget, enable, ls);
+    cxListFree(ls);
+}
+
+void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates) {
+    ui_widget_set_groups2(ctx, widget, (ui_enablefunc)ui_set_visible, states, nstates);
+}
+
 size_t uic_group_array_size(const int *groups) {
     int i;
     for(i=0;groups[i] >= 0;i++) { }
--- a/ui/common/context.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/context.h	Sat Oct 04 14:52:59 2025 +0200
@@ -67,8 +67,7 @@
     void          *document;
     CxList        *documents;
     
-    CxMap         *vars; // manually created context vars
-    CxMap         *vars_unbound; // unbound vars created by widgets
+    CxMap         *vars;
     
     CxList        *groups; // int list
     CxList        *group_widgets; // UiGroupWidget list
@@ -92,7 +91,6 @@
     void          *close_data;
 };
 
-// UiVar replacement, rename it to UiVar when finished
 struct UiVar {
     void      *value;
     void      *original_value;
@@ -133,10 +131,10 @@
 UiVar* uic_create_value_var(UiContext *ctx, void *value);
 void* uic_create_value(UiContext *ctx, UiVarType type);
 
-UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type);
+UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type);
 
 void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc);
-void uic_save_var2(UiVar *var);
+void uic_save_var(UiVar *var);
 void uic_unbind_var(UiVar *var);
 
 void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value);
--- a/ui/common/document.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/document.c	Sat Oct 04 14:52:59 2025 +0200
@@ -36,22 +36,16 @@
 #include <cx/mempool.h>
 
 
-static CxMap *documents;
 
-void uic_docmgr_init() {
-    if (!documents) {
-        documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
-    }
-}
 
 void* ui_document_new(size_t size) {
     CxMempool *mp = cxMempoolCreateSimple(256);
     const CxAllocator *a = mp->allocator;
     UiContext *ctx = uic_context(NULL, mp);
     
-    void *document = cxCalloc(a, size, 1);
-    cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
-    return document;
+    UiDoc *document = cxCalloc(a, sizeof(UiDoc) + size, 1);
+    document->ctx = ctx;
+    return &document->doc;
 }
 
 void ui_document_destroy(void *doc) {
@@ -74,7 +68,9 @@
 
 UiContext* ui_document_context(void *doc) {
     if(doc) {
-        return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
+        char *docPtr = doc;
+        UiDoc *document = (UiDoc*)(docPtr - sizeof(void*));
+        return document->ctx;
     } else {
         return NULL;
     }
--- a/ui/common/document.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/document.h	Sat Oct 04 14:52:59 2025 +0200
@@ -36,7 +36,11 @@
 extern "C" {
 #endif
 
-void uic_docmgr_init();
+typedef struct UiDoc {
+    UiContext *ctx;
+    char doc[];
+} UiDoc;
+    
 void uic_document_addvar(void *doc, char *name, int type, size_t vs);
 
 
--- a/ui/common/menu.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/menu.c	Sat Oct 04 14:52:59 2025 +0200
@@ -100,7 +100,7 @@
     menu->item.next = NULL;
     menu->item.type = UI_MENU;
     
-    menu->label        = label;
+    menu->label        = nl_strdup(label);
     menu->items_begin  = NULL;
     menu->items_end    = NULL;
     menu->parent       = NULL;    
@@ -271,6 +271,7 @@
         default: break;
         case UI_MENU: {
             UiMenu *menu = (UiMenu*)item;
+            free(menu->label);
             UiMenuItemI *m = menu->items_begin;
             while(m) {
                 UiMenuItemI *next = m->next;
--- a/ui/common/menu.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/menu.h	Sat Oct 04 14:52:59 2025 +0200
@@ -66,7 +66,7 @@
 
 struct UiMenu {
     UiMenuItemI    item;
-    const char     *label;
+    char           *label;
     UiMenuItemI    *items_begin;
     UiMenuItemI    *items_end;
     UiMenu         *parent;
--- a/ui/common/object.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/object.c	Sat Oct 04 14:52:59 2025 +0200
@@ -136,6 +136,7 @@
     CxMempool *mp = cxMempoolCreateSimple(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
+    obj->ctx->parent = ui_global_context();
     uic_object_created(obj);
     return obj;
 }
--- a/ui/common/properties.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/properties.c	Sat Oct 04 14:52:59 2025 +0200
@@ -73,7 +73,7 @@
 #define UI_ENV_HOME "USERPROFILE"
 #endif
 
-char* ui_configfile(char *name) {
+char* ui_configfile(const char *name) {
     const char *appname = ui_appname();
     if(!appname) {
         return NULL;
@@ -128,6 +128,7 @@
 
 void uic_load_app_properties() {
     application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128);
+    application_properties->collection.simple_destructor = free;
     
     if(!ui_appname()) {
         // applications without name cannot load app properties
@@ -202,7 +203,11 @@
 }
 
 void ui_set_property(const char *name, const char *value) {
-    cxMapPut(application_properties, name, (char*)value);
+    if(value) {
+        cxMapPut(application_properties, name, strdup(value));
+    } else {
+        cxMapRemove(application_properties, name);
+    }
 }
 
 const char* ui_set_default_property(const char *name, const char *value) {
@@ -241,12 +246,14 @@
 
 #ifndef UI_COCOA
 
-void ui_locales_dir(char *path) {
-    locales_dir = path;
+void ui_locales_dir(const char *path) {
+    free(locales_dir);
+    locales_dir = path ? strdup(path) : NULL;
 }
 
-void ui_pixmaps_dir(char *path) {
-    pixmaps_dir = path;
+void ui_pixmaps_dir(const char *path) {
+    free(pixmaps_dir);
+    pixmaps_dir = path ? strdup(path) : NULL;
 }
 
 char* uic_get_image_path(const char *imgfilename) {
@@ -257,7 +264,7 @@
     }
 }
 
-void ui_load_lang(char *locale) {
+void ui_load_lang(const char *locale) {
     if(!locale) {
         locale = "en_EN";
     }
@@ -319,12 +326,12 @@
     return 0;
 }
 
-char* uistr(char *name) {
+char* uistr(const char *name) {
     char *value = uistr_n(name);
     return value ? value : "missing string";
 }
 
-char* uistr_n(char *name) {
+char* uistr_n(const char *name) {
     if(!language) {
         return NULL;
     }
--- a/ui/common/toolbar.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/toolbar.c	Sat Oct 04 14:52:59 2025 +0200
@@ -29,20 +29,21 @@
 #include "toolbar.h"
 #include "menu.h"
 
+#include <stdio.h>
 #include <string.h>
 
 static CxMap* toolbar_items;
 
-static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right
+static CxList* toolbar_defaults[8]; // 0: left 1: center 2: right
 
 static UiToolbarMenuItem* ui_appmenu;
 
 
 void uic_toolbar_init(void) {
     toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    for(int i=0;i<8;i++) {
+        toolbar_defaults[i] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    }
 }
 
 static char* nl_strdup(const char* str) {
@@ -120,7 +121,7 @@
 }
 
 CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {
-    if (pos >= 0 && pos < 3) {
+    if (pos >= 0 && pos < 8) {
         return toolbar_defaults[pos];
     }
     return NULL;
@@ -128,15 +129,19 @@
 
 void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) {
     char* cp = strdup(name);
-    if (pos >= 0 && pos < 3) {
+    if (pos >= 0 && pos < 8) {
         cxListAdd(toolbar_defaults[pos], cp);
     } else {
-        // TODO: error
+        fprintf(stderr, "Error: illegal toolbar position: %d\n", (int)pos);
     }
 }
 
 UiBool uic_toolbar_isenabled(void) {
-    return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0;
+    size_t size = 0;
+    for(int i=0;i<8;i++) {
+        size += cxListSize(toolbar_defaults[i]);
+    }
+    return size > 0;
 }
 
 UiToolbarItemI* uic_toolbar_get_item(const char* name) {
--- a/ui/common/types.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/types.c	Sat Oct 04 14:52:59 2025 +0200
@@ -220,6 +220,7 @@
     va_end(ap);
     
     size_t len = cxListSize(cols);
+    info->alloc = len;
     info->columns = len;
     info->types = ui_calloc(ctx, len, sizeof(UiModelType));
     info->titles = ui_calloc(ctx, len, sizeof(char*));
@@ -229,7 +230,7 @@
     CxIterator iter = cxListIterator(cols);
     cx_foreach(UiColumn*, c, iter) {
         info->types[i] = c->type;
-        info->titles[i] = c->name;
+        info->titles[i] = ui_strdup(ctx, c->name);
         i++;
     }
     cxListFree(cols);
@@ -237,6 +238,30 @@
     return info;
 }
 
+#define UI_MODEL_DEFAULT_ALLOC_SIZE 16
+
+UiModel* ui_model_new(UiContext *ctx) {
+    UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel));
+    info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE;
+    info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType));
+    info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*));
+    info->columnsize = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(int));
+    return info;
+}
+
+void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) {
+    if(model->columns >= model->alloc) {
+        model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE;
+        model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType));
+        model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*));
+        model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int));
+    }
+    model->types[model->columns] = type;
+    model->titles[model->columns] = ui_strdup(ctx, title);
+    model->columnsize[model->columns] = width;
+    model->columns++;
+}
+
 UiModel* ui_model_copy(UiContext *ctx, UiModel* model) {
     const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
 
@@ -258,6 +283,9 @@
 
 void ui_model_free(UiContext *ctx, UiModel *mi) {
     const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+    for(int i=0;i<mi->columns;i++) {
+        ui_free(ctx, mi->titles[i]);
+    }
     cxFree(a, mi->types);
     cxFree(a, mi->titles);
     cxFree(a, mi->columnsize);
@@ -389,7 +417,7 @@
         return s->get ? s->get(s) : s->value.ptr;
     }
     else {
-        return 0;
+        return NULL;
     }
 }
 
@@ -419,6 +447,69 @@
         return s->get ? s->get(s) : s->value.ptr;
     }
     else {
+        return NULL;
+    }
+}
+
+void ui_range_set(UiRange *r, double value) {
+    if (r) {
+        if (r->set) {
+            r->set(r, value);
+        } else {
+            r->value = value;
+        }
+    }
+}
+
+void ui_range_set_range(UiRange *r, double min, double max) {
+    if (r) {
+        if (r->setrange) {
+            r->setrange(r, min, max);
+        } else {
+            r->min = min;
+            r->max = max;
+        }
+    }
+}
+
+void ui_range_set_extent(UiRange *r, double extent) {
+    if (r) {
+        if (r->setextent) {
+            r->setextent(r, extent);
+        } else {
+            r->extent = extent;
+        }
+    }
+}
+
+double ui_range_get(UiRange *r) {
+    if (r) {
+        return r->get ? r->get(r) : r->value;
+    } else {
+        return 0;
+    }
+}
+
+double ui_range_get_min(UiRange *r) {
+    if (r) {
+        return r->min;
+    } else {
+        return 0;
+    }
+}
+
+double ui_range_get_max(UiRange *r) {
+    if (r) {
+        return r->max;
+    } else {
+        return 0;
+    }
+}
+
+double ui_range_get_extent(UiRange *r) {
+    if (r) {
+        return r->extent;
+    } else {
         return 0;
     }
 }
@@ -580,6 +671,8 @@
 
 void uic_list_unbind(UiList *l) {
     l->update = NULL;
+    l->getselection = NULL;
+    l->setselection = NULL;
     l->obj = NULL;
 }
 
@@ -599,10 +692,12 @@
 }
 
 UIEXPORT void ui_list_setselection(UiList *list, int index) {
-    if (list->setselection && index >= 0) {
-        UiListSelection sel;
-        sel.count = 1;
-        sel.rows = &index;
+    if (list->setselection) {
+        UiListSelection sel = { 0, NULL };
+        if(index >= 0) {
+            sel.count = 1;
+            sel.rows = &index;
+        }
         list->setselection(list, sel);
     }
 }
--- a/ui/common/wrapper.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/wrapper.c	Sat Oct 04 14:52:59 2025 +0200
@@ -236,6 +236,15 @@
     return sel->rows;
 }
 
+UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num) {
+    UiListSelection sel;
+    sel.rows = indices;
+    sel.count = num;
+    if(list->setselection) {
+        list->setselection(list, sel);
+    }
+}
+
 void ui_list_selection_free(UiListSelection *sel) {
     ui_listselection_free(*sel);
     free(sel);
@@ -253,3 +262,59 @@
     }
     return NULL;
 }
+
+/* ---------------------------- UiTextStyle ---------------------------- */
+
+void ui_textstyle_set_bold(UiTextStyle *style, UiBool set) {
+    if(set) {
+        style->text_style |= UI_TEXT_STYLE_BOLD;
+    } else {
+        style->text_style &= ~UI_TEXT_STYLE_BOLD;
+    }
+}
+
+void ui_textstyle_set_underline(UiTextStyle *style, UiBool set) {
+    if(set) {
+        style->text_style |= UI_TEXT_STYLE_UNDERLINE;
+    } else {
+        style->text_style &= ~UI_TEXT_STYLE_UNDERLINE;
+    }
+}
+
+void ui_textstyle_set_italic(UiTextStyle *style, UiBool set) {
+    if(set) {
+        style->text_style |= UI_TEXT_STYLE_ITALIC;
+    } else {
+        style->text_style &= ~UI_TEXT_STYLE_ITALIC;
+    }
+}
+
+void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b) {
+    style->fg_set = TRUE;
+    style->fg.red = r;
+    style->fg.green = g;
+    style->fg.blue = b;
+}
+
+void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable) {
+    style->fg_set = enable;
+}
+
+
+/* ---------------------------- UiCellValue ---------------------------- */
+
+UiBool ui_cell_value_is_string(UiCellValue *value) {
+   return value->type == UI_STRING_EDITABLE;
+}
+
+UiBool ui_cell_value_is_int(UiCellValue *value) {
+    return FALSE; // TODO
+}
+
+const char* ui_cell_value_get_string(UiCellValue *value) {
+    return value->string;
+}
+
+int64_t ui_cell_value_get_int(UiCellValue *value) {
+    return value->i;
+}
--- a/ui/common/wrapper.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/common/wrapper.h	Sat Oct 04 14:52:59 2025 +0200
@@ -77,11 +77,23 @@
 UIEXPORT UiListSelection* ui_list_get_selection_allocated(UiList *list);
 UIEXPORT int ui_list_selection_get_count(UiListSelection *sel);
 UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel);
+UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num);
 UIEXPORT void ui_list_selection_free(UiListSelection *sel);
 
 UIEXPORT int ui_filelist_count(UiFileList *flist);
 UIEXPORT char* ui_filelist_get(UiFileList *flist, int index);
 
+UIEXPORT void ui_textstyle_set_bold(UiTextStyle *style, UiBool set);
+UIEXPORT void ui_textstyle_set_underline(UiTextStyle *style, UiBool set);
+UIEXPORT void ui_textstyle_set_italic(UiTextStyle *style, UiBool set);
+UIEXPORT void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b);
+UIEXPORT void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable);
+
+UIEXPORT UiBool ui_cell_value_is_string(UiCellValue *value);
+UIEXPORT UiBool ui_cell_value_is_int(UiCellValue *value);
+UIEXPORT const char* ui_cell_value_get_string(UiCellValue *value);
+UIEXPORT int64_t ui_cell_value_get_int(UiCellValue *value);
+
 
 #ifdef __cplusplus
 }
--- a/ui/gtk/button.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/button.c	Sat Oct 04 14:52:59 2025 +0200
@@ -382,13 +382,98 @@
 }
 #endif
 
+
+#if GTK_MAJOR_VERSION >= 3
+
+static void switch_changed(
+        GObject *gobject,
+        GParamSpec *pspec,
+        UiVarEventData *event)
+{
+    GtkSwitch *sw = GTK_SWITCH (gobject);
+    gboolean active = gtk_switch_get_active (sw);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.document = e.obj->ctx->document;
+    e.window = e.obj->window;
+    e.eventdata = NULL;
+    e.eventdatatype = 0;
+    e.set = ui_get_setop();
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    if(event->var) {
+        UiInteger *i = event->var->value;
+        ui_notify_evt(i->observers, &e);
+    }
+}
+
 UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) {
-#ifdef UI_GTK3
-    return NULL; // TODO
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *widget = gtk_switch_new();
+    ui_set_name_and_style(widget, args->name, args->style_class);
+    ui_set_widget_groups(obj->ctx, widget, args->groups);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = widget;
+        value->get = ui_switch_get;
+        value->set = ui_switch_set;
+        
+        if(value->value) {
+            gtk_switch_set_active(GTK_SWITCH(widget), TRUE);
+        }
+        
+        
+    }
+    
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = args->onchange;
+    event->userdata = args->onchangedata;
+    event->var = var;
+    event->observers = NULL;
+    
+    g_signal_connect(
+            widget,
+            "notify::active",
+            G_CALLBACK(switch_changed),
+            event);
+    
+    g_signal_connect(
+            widget,
+            "destroy",
+            G_CALLBACK(ui_destroy_vardata),
+            event);
+    
+    UI_APPLY_LAYOUT2(current, args);
+    current->container->add(current->container, widget);
+    
+    return widget;
+}
+
+int64_t ui_switch_get(UiInteger *value) {
+    GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj);
+    value->value = gtk_switch_get_active(sw);
+    return value->value;
+}
+
+void ui_switch_set(UiInteger *value, int64_t i) {
+    GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj);
+    value->value = i;
+    gtk_switch_set_active(sw, i);
+}
+    
 #else
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) {
     return ui_checkbox_create(obj, args);
+}
+
 #endif
-}
 
 #if GTK_MAJOR_VERSION >= 4
 #define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label)
@@ -760,6 +845,12 @@
                 data);
     }
     gtk_button_set_label(GTK_BUTTON(button), args->label);
+#if GTK_CHECK_VERSION(4, 0, 0)
+    gtk_button_set_can_shrink(GTK_BUTTON(button), TRUE);
+#elif GTK_MAJOR_VERSION == 3
+    GtkWidget *child = gtk_bin_get_child(GTK_BIN(button));
+    gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END);
+#endif
     g_object_set_data(G_OBJECT(button), "ui_linkbutton", data);
     g_signal_connect(
             button,
@@ -806,6 +897,11 @@
     if(s->value.free) {
         s->value.free(s->value.ptr);
     }
+#if GTK_MAJOR_VERSION == 3
+    UiLinkButton *data = s->obj;
+    GtkWidget *child = gtk_bin_get_child(GTK_BIN(data->widget));
+    gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END);
+#endif
 }
 
 
--- a/ui/gtk/button.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/button.h	Sat Oct 04 14:52:59 2025 +0200
@@ -96,6 +96,9 @@
 
 void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event);
 
+int64_t ui_switch_get(UiInteger *value);
+void ui_switch_set(UiInteger *value, int64_t i);
+
 int64_t ui_radiobutton_get(UiInteger *value);
 void ui_radiobutton_set(UiInteger *value, int64_t i);
 
--- a/ui/gtk/container.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/container.c	Sat Oct 04 14:52:59 2025 +0200
@@ -28,6 +28,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <limits.h>
 
 #include "container.h"
@@ -37,6 +38,8 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
+#include "../ui/properties.h"
+
 
 void ui_container_begin_close(UiObject *obj) {
     UiContainer *ct = uic_get_current_container(obj);
@@ -193,14 +196,14 @@
     if(!ct->layout.override_defaults) {
         if(grid->def_hexpand) {
             hexpand = TRUE;
-            hfill = TRUE;
-        } else if(grid->def_hfill) {
+        }
+        if(grid->def_hfill) {
             hfill = TRUE;
         }
         if(grid->def_vexpand) {
             vexpand = TRUE;
-            vfill = TRUE;
-        } else if(grid->def_vfill) {
+        }
+        if(grid->def_vfill) {
             vfill = TRUE;
         }
     }
@@ -208,19 +211,21 @@
     UiBool fill = ct->layout.fill;
     if(ct->layout.hexpand) {
         hexpand = TRUE;
-        hfill = TRUE;
-    } else if(ct->layout.hfill) {
+    }
+    if(ct->layout.hfill) {
         hfill = TRUE;
     }
     if(ct->layout.vexpand) {
         vexpand = TRUE;
-        vfill = TRUE;
-    } else if(ct->layout.vfill) {
+    }
+    if(ct->layout.vfill) {
         vfill = TRUE;
     }
     if(fill) {
         hfill = TRUE;
         vfill = TRUE;
+        hexpand = TRUE;
+        vexpand = TRUE;
     }
     
     if(!hfill) {
@@ -1048,6 +1053,35 @@
 }
 #endif
 
+/* ------------------------ Split Window Panels ------------------------ */
+
+static UIWIDGET splitwindow_panel(UiObject *obj, GtkWidget *pbox, UiSidebarArgs *args) {
+    if(!pbox) {
+        fprintf(stderr, "Error: window is not a splitview window\n");
+        return NULL;
+    }
+    
+    GtkWidget *box = ui_gtk_vbox_new(args->spacing);
+    ui_set_name_and_style(box, args->name, args->style_class);
+    ui_box_set_margin(box, args->margin);
+    BOX_ADD_EXPAND(pbox, box);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+
+UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) {
+    return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_left_panel"), args);
+}
+
+UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) {
+    return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_right_panel"), args);
+}
+
+
 /* -------------------- Splitpane -------------------- */
 
 static GtkWidget* create_paned(UiOrientation orientation) {
@@ -1065,7 +1099,13 @@
     return NULL;
 }
 
-
+static void save_pane_pos(GtkWidget *widget, char *property_name) {
+    int pos = gtk_paned_get_position(GTK_PANED(widget));
+    char buf[32];
+    snprintf(buf, 32, "%d", pos);
+    ui_set_property(property_name, buf);
+    free(property_name);
+}
 
 static UIWIDGET splitpane_create(UiObject *obj, UiOrientation orientation, UiSplitPaneArgs *args) {
     UiObject* current = uic_current_obj(obj);
@@ -1077,12 +1117,40 @@
     
     int max = args->max_panes == 0 ? 2 : args->max_panes;
     
+    if(args->position_property) {
+        const char *pos_str = ui_get_property(args->position_property);
+        if(pos_str) {
+            char *end;
+            long pos = strtol(pos_str, &end, 10);
+            if(*end == '\0') {
+                args->initial_position = (int)pos;
+            }
+        }
+        
+        g_signal_connect(
+                pane0,
+                "destroy",
+                G_CALLBACK(save_pane_pos),
+                strdup(args->position_property));
+    }
+    
     UiObject *newobj = uic_object_new(obj, pane0);
     newobj->container = ui_splitpane_container(obj, pane0, orientation, max, args->initial_position);
     uic_obj_add(obj, newobj);
     
     g_object_set_data(G_OBJECT(pane0), "ui_splitpane", newobj->container);
     
+    UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiSplitPaneContainer *s = (UiSplitPaneContainer*)newobj->container;
+        UiInteger *i = var->value;
+        s->initial_position = i->value;
+        
+        i->obj = s;
+        i->get = ui_splitpane_get;
+        i->set = ui_splitpane_set;
+    }
+    
     return pane0;
 }
 
@@ -1138,6 +1206,18 @@
     }
 }
 
+int64_t ui_splitpane_get(UiInteger *i) {
+    UiSplitPaneContainer *s = i->obj;
+    i->value = gtk_paned_get_position(GTK_PANED(s->container.widget));
+    return i->value;
+}
+
+void ui_splitpane_set(UiInteger *i, int64_t value) {
+    UiSplitPaneContainer *s = i->obj;
+    i->value = value;
+    gtk_paned_set_position(GTK_PANED(s->container.widget), (int)value);
+}
+
 UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible) {
     UiSplitPaneContainer *ct = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane");
     if(!ct) {
--- a/ui/gtk/container.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/container.h	Sat Oct 04 14:52:59 2025 +0200
@@ -197,6 +197,8 @@
 UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init);
 void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget);
 
+int64_t ui_splitpane_get(UiInteger *i);
+void ui_splitpane_set(UiInteger *i, int64_t value);
 
 UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
 
--- a/ui/gtk/entry.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/entry.c	Sat Oct 04 14:52:59 2025 +0200
@@ -35,28 +35,38 @@
 #include "entry.h"
 
 
-UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args) {
-    double min = 0;
-    double max = 1000;
+UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) {
+    double min = args->min;
+    double max = args->max != 0 ? args->max : 1000;
     
     UiObject* current = uic_current_obj(obj);
     
     UiVar *var = NULL;
+    UiVarType vartype = 0;
     if(args->varname) {
         var = uic_get_var(obj->ctx, args->varname);
+        if(var) {
+            vartype = var->type;
+        } else {
+            var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, args->varname, UI_VAR_RANGE);
+            vartype = UI_VAR_RANGE;
+        }
     }
     
     if(!var) {
         if(args->intvalue) {
             var = uic_widget_var(obj->ctx, current->ctx, args->intvalue, NULL, UI_VAR_INTEGER);
+            vartype = UI_VAR_INTEGER;
         } else if(args->doublevalue) {
             var = uic_widget_var(obj->ctx, current->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE);
+            vartype = UI_VAR_DOUBLE;
         } else if(args->rangevalue) {
             var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, NULL, UI_VAR_RANGE);
+            vartype = UI_VAR_RANGE;
         }
     }
     
-    if(var && var->type == UI_VAR_RANGE) {
+    if(vartype == UI_VAR_RANGE) {
         UiRange *r = var->value;
         min = r->min;
         max = r->max;
@@ -72,11 +82,16 @@
     GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args->step);
     ui_set_name_and_style(spin, args->name, args->style_class);
     ui_set_widget_groups(obj->ctx, spin, args->groups);
+    
+    if(args->width > 0) {
+        gtk_widget_set_size_request(spin, args->width, -1);
+    }
+    
     gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args->digits);
     UiObserver **obs = NULL;
     if(var) {
         double value = 0;
-        switch(var->type) {
+        switch(vartype) {
             default: break;
             case UI_VAR_INTEGER: {
                 UiInteger *i = var->value;
--- a/ui/gtk/headerbar.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/headerbar.c	Sat Oct 04 14:52:59 2025 +0200
@@ -31,21 +31,70 @@
 #include "button.h"
 #include "menu.h"
 
+#include "../ui/properties.h"
+
 #if GTK_CHECK_VERSION(3, 10, 0)
     
-void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) {
+void ui_fill_headerbar(UiObject *obj, GtkWidget *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_headerbar) {
+    CxList *sidebar_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_LEFT);
+    CxList *sidebar_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_RIGHT);
+    
     CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
     CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
     CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
     
-    ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT);
-    ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER);
-    
+    CxList *rightpanel_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_LEFT);
+    CxList *rightpanel_center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_CENTER);
+    CxList *rightpanel_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_RIGHT);
+
     UiToolbarMenuItem *appmenu = uic_get_appmenu();
-    if(appmenu) {
-        ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
+    const char *appmenu_pos_str = ui_get_property("ui.gtk.window.appmenu.position");
+    int appmenu_pos = UI_TOOLBAR_RIGHT;
+    if(sidebar_headerbar) {
+        appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT;
+    } else if(right_headerbar) {
+        appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT;
+    }
+    if(appmenu_pos_str) {
+        if(!strcmp(appmenu_pos_str, "sidebar") && sidebar_headerbar) {
+            appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT;
+        } else if(!strcmp(appmenu_pos_str, "main")) {
+            appmenu_pos = UI_TOOLBAR_RIGHT;
+        } else if(!strcmp(appmenu_pos_str, "rightpanel") && right_headerbar) {
+            appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT;
+        }
+    }
+    
+    // main toolbar
+    ui_headerbar_add_items(obj, main_headerbar, left_defaults, UI_TOOLBAR_LEFT);
+    ui_headerbar_add_items(obj, main_headerbar, center_defaults, UI_TOOLBAR_CENTER);
+    
+    if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHT) {
+        ui_add_headerbar_menu(main_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
     }
-    ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT);
+    ui_headerbar_add_items(obj, main_headerbar, right_defaults, UI_TOOLBAR_RIGHT);
+    
+    // sidebar
+    if(sidebar_headerbar) {
+        // ui_headerbar_add_items pos parameter uses only UI_TOOLBAR_LEFT, UI_TOOLBAR_CENTER, UI_TOOLBAR_RIGHT
+        ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_left_defaults, UI_TOOLBAR_LEFT);
+
+        if(appmenu && appmenu_pos == UI_TOOLBAR_SIDEBAR_RIGHT) {
+            ui_add_headerbar_menu(sidebar_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
+        }
+        ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_right_defaults, UI_TOOLBAR_RIGHT);
+    }
+    
+    // right panel
+    if(right_headerbar) {
+        ui_headerbar_add_items(obj, right_headerbar, rightpanel_left_defaults, UI_TOOLBAR_LEFT);
+        ui_headerbar_add_items(obj, right_headerbar, rightpanel_center_defaults, UI_TOOLBAR_CENTER);
+
+        if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHTPANEL_RIGHT) {
+            ui_add_headerbar_menu(right_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
+        }
+        ui_headerbar_add_items(obj, right_headerbar, rightpanel_right_defaults, UI_TOOLBAR_RIGHT);
+    }
 }
 
 static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) {
--- a/ui/gtk/headerbar.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/headerbar.h	Sat Oct 04 14:52:59 2025 +0200
@@ -58,7 +58,7 @@
 #endif
 #endif
     
-void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar);
+void ui_fill_headerbar(UiObject *obj, GtkWidget *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_headerbar);
 
 void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
 
--- a/ui/gtk/list.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/list.c	Sat Oct 04 14:52:59 2025 +0200
@@ -92,6 +92,11 @@
     tableview->ondropdata = args->ondropdata;
     tableview->selection.count = 0;
     tableview->selection.rows = NULL;
+    tableview->current_row = -1;
+    tableview->getstyle = args->getstyle;
+    tableview->getstyledata = args->getstyledata;
+    tableview->onsave = args->onsave;
+    tableview->onsavedata = args->onsavedata;
     
     if(args->getvalue2) {
         tableview->getvalue = args->getvalue2;
@@ -102,7 +107,7 @@
     } else {
         tableview->getvalue = null_getvalue;
     }
-    
+      
     return tableview;
 }
 
@@ -140,6 +145,58 @@
 
 /* END GObject wrapper for generic pointers */
 
+typedef struct UiCellEntry {
+    GtkEntry *entry;
+    UiListView *listview;
+    char *previous_value;
+    int row;
+    int col;
+} UiCellEntry;
+
+static void cell_save_value(UiCellEntry *data, int restore) {
+    if(data->listview && data->listview->onsave) {
+        UiVar *var = data->listview->var;
+        UiList *list = var ? var->value : NULL;
+        const char *str = ENTRY_GET_TEXT(data->entry);
+        UiCellValue value;
+        value.string = str;
+        value.type = UI_STRING_EDITABLE;
+        if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) {
+            free(data->previous_value);
+            data->previous_value = strdup(str);
+        } else if(restore) {
+            ENTRY_SET_TEXT(data->entry, data->previous_value);
+        }
+    }
+}
+
+static void cell_entry_leave_focus(
+        GtkEventControllerFocus *self,
+        UiCellEntry *data)
+{
+    // TODO: use a different singal to track focus
+    //       we only want to call cell_save_value, when another entry is selected,
+    //       not when the window loses focus or something like that
+    cell_save_value(data, TRUE);
+}
+
+static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) {
+    free(data->previous_value);
+    free(data);
+}
+
+static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) {
+    const char *text = ENTRY_GET_TEXT(w);
+    cell_save_value(data, FALSE);
+}
+
+static void cell_entry_activate(
+        GtkEntry *self,
+        UiCellEntry *data)
+{
+    cell_save_value(data, TRUE);
+}
+
 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
     UiModel *model = col->listview->model;
@@ -156,6 +213,38 @@
     } else if(type == UI_ICON) {
         GtkWidget *image = gtk_image_new();
         gtk_list_item_set_child(item, image);
+    } else if(type == UI_STRING_EDITABLE) {
+        GtkWidget *textfield = gtk_entry_new();
+        gtk_widget_add_css_class(textfield, "ui-table-entry");
+        gtk_list_item_set_child(item, textfield);
+        
+        UiCellEntry *entry_data = malloc(sizeof(UiCellEntry));
+        entry_data->entry = GTK_ENTRY(textfield);
+        entry_data->listview = NULL;
+        entry_data->previous_value = NULL;
+        entry_data->col = 0;
+        entry_data->row = 0;
+        g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data);
+        
+        g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(cell_entry_destroy),
+                entry_data);
+        g_signal_connect(
+                textfield,
+                "activate",
+                G_CALLBACK(cell_entry_activate),
+                entry_data);
+        g_signal_connect(
+                textfield,
+                "unmap",
+                G_CALLBACK(cell_entry_unmap),
+                entry_data);
+        
+        GtkEventController *focus_controller = gtk_event_controller_focus_new();
+        g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data);
+        gtk_widget_add_controller(textfield, focus_controller);
     } else {
         GtkWidget *label = gtk_label_new(NULL);
         gtk_label_set_xalign(GTK_LABEL(label), 0);
@@ -163,19 +252,83 @@
     }
 }
 
-static void column_factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+PangoAttrList* textstyle2pangoattributes(UiTextStyle style) {
+    PangoAttrList *attr = pango_attr_list_new();
+    
+    if(style.text_style & UI_TEXT_STYLE_BOLD) {
+        pango_attr_list_insert(attr, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+    }
+    if(style.text_style & UI_TEXT_STYLE_ITALIC) {
+        pango_attr_list_insert(attr, pango_attr_style_new(PANGO_STYLE_ITALIC));
+    }
+    if(style.text_style & UI_TEXT_STYLE_UNDERLINE) {
+        pango_attr_list_insert(attr, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
+    }
+    
+    // foreground color, convert from 8bit to 16bit
+    guint16 r = (guint16)style.fg.red   * 257;
+    guint16 g = (guint16)style.fg.green * 257;
+    guint16 b = (guint16)style.fg.blue  * 257;
+    pango_attr_list_insert(attr, pango_attr_foreground_new(r, g, b));
+    
+    return attr;
+}
+
+static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
     UiList *list = col->listview->var ? col->listview->var->value : NULL;
     UiListView *listview = col->listview;
-    
+     
     ObjWrapper *obj = gtk_list_item_get_item(item);
     UiModel *model = col->listview->model;
     UiModelType type = model->types[col->model_column];
     
+    // cache the GtkListItem
+    CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int));
+    UiRowItems *row = cxMapGet(listview->bound_rows, row_key);
+    if(row) {
+        if(row->items[col->model_column] == NULL) {
+            row->bound++;
+        }
+    } else {
+        row = calloc(1, sizeof(UiRowItems) + listview->numcolumns * sizeof(GtkListItem*));
+        cxMapPut(listview->bound_rows, row_key, row);
+        row->bound = 1;
+    }
+    row->items[col->model_column] = item;
+    
     UiBool freevalue = FALSE;
     void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue);
     GtkWidget *child = gtk_list_item_get_child(item);
     
+    PangoAttrList *attributes = NULL;
+    UiTextStyle style = { 0, 0 };
+    if(listview->getstyle) { 
+        // query current row style, if it wasn't already queried
+        if(obj->i != listview->current_row) {
+            listview->current_row = obj->i;
+            listview->row_style = (UiTextStyle){ 0, 0 };
+            listview->apply_row_style = listview->getstyle(list, obj->data, obj->i, -1, listview->getstyledata, &listview->row_style);
+            style = listview->row_style;
+            if(listview->apply_row_style) {
+                pango_attr_list_unref(listview->current_row_attributes);
+                listview->current_row_attributes = textstyle2pangoattributes(style);
+            }
+        }
+        
+        int style_col = col->data_column;
+        if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) {
+            style_col++; // col->data_column is the icon, we need the next col for the label
+        }
+        
+        // get the column style
+        if(listview->getstyle(list, obj->data, obj->i, style_col, listview->getstyledata, &style)) {
+            attributes = textstyle2pangoattributes(style);
+        } else if(listview->apply_row_style) {
+            attributes = listview->current_row_attributes;
+        }
+    }    
+    
     switch(type) {
         case UI_STRING_FREE: {
             freevalue = TRUE;
@@ -185,6 +338,7 @@
             if(freevalue) {
                 free(data);
             }
+            gtk_label_set_attributes(GTK_LABEL(child), attributes);
             break;
         }
         case UI_INTEGER: {
@@ -192,6 +346,7 @@
             char buf[32];
             snprintf(buf, 32, "%d", (int)intvalue);
             gtk_label_set_label(GTK_LABEL(child), buf);
+            gtk_label_set_attributes(GTK_LABEL(child), attributes);
             break;
         }
         case UI_ICON: {
@@ -217,15 +372,55 @@
             }
             if(data2 && label) {
                 gtk_label_set_label(GTK_LABEL(label), data2);
+                gtk_label_set_attributes(GTK_LABEL(label), attributes);
             }
             if(freevalue) {
                 free(data2);
             }
             break;
         }
+        case UI_STRING_EDITABLE: {
+            UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data");
+            if(entry) {
+                entry->listview = col->listview;
+                entry->row = obj->i;
+                entry->col = col->data_column;
+                entry->previous_value = strdup(data);
+            }
+            ENTRY_SET_TEXT(child, data);
+            break;
+        }
+    }
+    
+    if(attributes != listview->current_row_attributes) {
+        pango_attr_list_unref(attributes);
     }
 }
 
+static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) {
+    ObjWrapper *obj = gtk_list_item_get_item(item);
+    UiListView *listview = col->listview;
+    CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int));
+    UiRowItems *row = cxMapGet(listview->bound_rows, row_key);
+    if(row) {
+        row->items[col->model_column] = NULL;
+        row->bound--;
+        if(row->bound == 0) {
+            cxMapRemove(listview->bound_rows, row_key);
+        }
+    } // else: should not happen
+    
+    GtkWidget *child = gtk_list_item_get_child(item);
+    UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data");
+    if(entry) {
+        cell_save_value(entry, FALSE);
+        entry->listview = NULL;
+        free(entry->previous_value);
+        entry->previous_value = NULL;
+    }
+}
+    
+
 static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) {
     GtkSelectionModel *selection_model;
     if(multiselection) {
@@ -253,10 +448,14 @@
         listview->getvalue = str_getvalue;
     }
     
+    listview->numcolumns = 1;
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
     listview->columns->model_column = 0;
+    
+    listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128);
+    listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
      
     GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
     g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
@@ -339,11 +538,15 @@
         listview->getvalue = str_getvalue;
     }
     
+    listview->numcolumns = 1;
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
     listview->columns->model_column = 0;
     
+    listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128);
+    listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
+    
     GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
     g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
     g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns);
@@ -432,6 +635,10 @@
     int columns = model ? model->columns : 0;
     
     tableview->columns = calloc(columns, sizeof(UiColData));
+    tableview->numcolumns = columns;
+    
+    tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128);
+    tableview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
     
     int addi = 0;
     for(int i=0;i<columns;i++) {
@@ -631,21 +838,20 @@
     } else {
         void *value = list->get(list, i);
         if(value) {
-            ObjWrapper *obj = obj_wrapper_new(value, i);
-            UiListSelection sel = list->getselection(list);
-            // TODO: if index i is selected, the selection is lost
-            // is it possible to update the item without removing it?
-            // workaround: save selection and reapply it
-            int count = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
-            if(count <= i) {
-                g_list_store_splice(view->liststore, i, 0, (void **)&obj, 1);
-            } else {
-                g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1);
+            ObjWrapper *obj = g_list_model_get_item(G_LIST_MODEL(view->liststore), i);
+            if(obj) {
+                obj->data = value;
             }
-            if(sel.count > 0) {
-                list->setselection(list, sel);
+            
+            CxHashKey row_key = cx_hash_key(&i, sizeof(int));
+            UiRowItems *row = cxMapGet(view->bound_rows, row_key);
+            if(row) {
+                for(int c=0;c<view->numcolumns;c++) {
+                    if(row->items[c] != NULL) {
+                        column_factory_bind(NULL, row->items[c], &view->columns[c]);
+                    }
+                }
             }
-            ui_listselection_free(sel);
         }
     }
 }
@@ -709,14 +915,40 @@
 
 static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) {
     UiModel *model = listview->model;
+    ui_getstylefunc getstyle = listview->getstyle;
+    
+    // get the row style
+    UiBool style_set = FALSE;
+    UiTextStyle style = { 0, 0 };
+    if(getstyle) {
+        style_set = getstyle(list, elm, row, -1, listview->getstyledata, &style);
+    }
+    
     // set column values
     int c = 0;
     for(int i=0;i<model->columns;i++,c++) {
         UiBool freevalue = FALSE;
         void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
+        
+        UiModelType type = model->types[i];
+        
+        if(getstyle) {
+            // in case the column is icon+text, only get a style for the text column
+            int style_col = c;
+            if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) {
+                style_col++;
+            }
+            
+            // Get the individual column style 
+            // The column style overrides the row style, however if no column style
+            // is provided, we stick with the row style
+            if(getstyle(list, elm, row, style_col, listview->getstyledata, &style)) {
+                style_set = TRUE;
+            }
+        }
 
         GValue value = G_VALUE_INIT;
-        switch(model->types[i]) {
+        switch(type) {
             case UI_STRING_FREE: {
                 freevalue = TRUE;
             } 
@@ -789,13 +1021,64 @@
         }
 
         gtk_list_store_set_value(store, iter, c, &value);
+        
+        if(style_set) {
+            int soff = listview->style_offset + i*6;
+            
+            GValue style_set_value = G_VALUE_INIT;
+            g_value_init(&style_set_value, G_TYPE_BOOLEAN);
+            g_value_set_boolean(&style_set_value, TRUE);
+            gtk_list_store_set_value(store, iter, soff, &style_set_value);
+            
+            GValue style_weight_value = G_VALUE_INIT;
+            g_value_init(&style_weight_value, G_TYPE_INT);
+            if(style.text_style & UI_TEXT_STYLE_BOLD) {
+                g_value_set_int(&style_weight_value, 600);
+            } else {
+                g_value_set_int(&style_weight_value, 400);
+            }
+            gtk_list_store_set_value(store, iter, soff + 1, &style_weight_value);
+            
+            GValue style_underline_value = G_VALUE_INIT;
+            g_value_init(&style_underline_value, G_TYPE_INT);
+            if(style.text_style & UI_TEXT_STYLE_UNDERLINE) {
+                g_value_set_int(&style_underline_value, PANGO_UNDERLINE_SINGLE);
+            } else {
+                g_value_set_int(&style_underline_value, PANGO_UNDERLINE_NONE);
+            }
+            gtk_list_store_set_value(store, iter, soff + 2, &style_underline_value);
+            
+            GValue style_italic_value = G_VALUE_INIT;
+            g_value_init(&style_italic_value, G_TYPE_INT);
+            if(style.text_style & UI_TEXT_STYLE_ITALIC) {
+                g_value_set_int(&style_italic_value, PANGO_STYLE_ITALIC);
+            } else {
+                g_value_set_int(&style_italic_value, PANGO_STYLE_NORMAL);
+            }
+            gtk_list_store_set_value(store, iter, soff + 3, &style_italic_value);
+            
+            GValue style_fgset_value = G_VALUE_INIT;
+            g_value_init(&style_fgset_value, G_TYPE_BOOLEAN);
+            g_value_set_boolean(&style_fgset_value, style.fg_set);
+            gtk_list_store_set_value(store, iter, soff + 4, &style_fgset_value);
+            
+            if(style.fg_set) {
+                char buf[8];
+                snprintf(buf, 8, "#%02X%02X%02X", (int)style.fg.red, (int)style.fg.green, (int)style.fg.blue);
+                
+                GValue style_fg_value = G_VALUE_INIT;
+                g_value_init(&style_fg_value, G_TYPE_STRING);
+                g_value_set_string(&style_fg_value, buf);
+                gtk_list_store_set_value(store, iter, soff + 5, &style_fg_value);
+            }
+        }
     }
 }
 
 static GtkListStore* create_list_store(UiListView *listview, UiList *list) {
     UiModel *model = listview->model;
     int columns = model->columns;
-    GType types[2*columns];
+    GType *types = calloc(columns*8, sizeof(GType));
     int c = 0;
     for(int i=0;i<columns;i++,c++) {
         switch(model->types[i]) {
@@ -810,8 +1093,18 @@
             }
         }
     }
+    int s = 0;
+    for(int i=0;i<columns;i++) {
+        types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // *-set
+        types[listview->style_offset+s] = G_TYPE_INT; s++;     // weight
+        types[listview->style_offset+s] = G_TYPE_INT; s++;     // underline
+        types[listview->style_offset+s] = G_TYPE_INT; s++;     // style
+        types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // foreground-set
+        types[listview->style_offset+s] = G_TYPE_STRING; s++;  // foreground
+    }
     
-    GtkListStore *store = gtk_list_store_newv(c, types);
+    GtkListStore *store = gtk_list_store_newv(c+s, types);
+    free(types);
     
     if(list) {
         void *elm = list->first(list);
@@ -857,6 +1150,7 @@
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
     
     UiListView *listview = create_listview(obj, args);
+    listview->style_offset = 1;
     if(!args->getvalue && !args->getvalue2) {
         listview->getvalue = str_getvalue;
     }
@@ -957,10 +1251,22 @@
     UiModel *model = args->model;
     int columns = model ? model->columns : 0;
     
+    // find the last data column index
     int addi = 0;
-    for(int i=0;i<columns;i++) {
+    int style_offset = 0;
+    int i = 0;
+    for(;i<columns;i++) {
+        if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) {
+            addi++;
+        }
+    }
+    style_offset = i+addi;
+    
+    // create columns and init cell renderers
+    addi = 0;
+    for(i=0;i<columns;i++) {
         GtkTreeViewColumn *column = NULL;
-        if(model->types[i] == UI_ICON_TEXT) {
+        if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) {
             column = gtk_tree_view_column_new();
             gtk_tree_view_column_set_title(column, model->titles[i]);
             
@@ -971,8 +1277,21 @@
             gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
             
             
-            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
-            gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", addi + i);
+            gtk_tree_view_column_add_attribute(column, textrenderer, "text", addi + i+1);
+            
+            if(args->getstyle) {
+                int soff = style_offset + i*6;
+                gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff);
+                
+                gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5);
+            }
             
             addi++;
         } else if (model->types[i] == UI_ICON) {
@@ -984,13 +1303,26 @@
                 i + addi,
                 NULL);
         } else {
-            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+            GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
             column = gtk_tree_view_column_new_with_attributes(
                 model->titles[i],
-                renderer,
+                textrenderer,
                 "text",
                 i + addi,
                 NULL);
+            
+            if(args->getstyle) {
+                int soff = style_offset + i*6;
+                gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff);
+                
+                gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4);
+                gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5);
+            }
         }
         
         int colsz = model->columnsize[i];
@@ -1019,6 +1351,8 @@
     // add TreeView as observer to the UiList to update the TreeView if the
     // data changes
     UiListView *tableview = create_listview(obj, args);
+    tableview->widget = view;
+    tableview->style_offset = style_offset;
     g_signal_connect(
                 view,
                 "destroy",
@@ -1154,6 +1488,7 @@
     
     UiListView *listview = create_listview(obj, args);
     listview->widget = combobox;
+    listview->style_offset = 1;
     listview->model = ui_model(obj->ctx, UI_STRING, "", -1);
     g_signal_connect(
                 combobox,
@@ -1676,6 +2011,8 @@
     }
 #if GTK_CHECK_VERSION(4, 10, 0)
     free(v->columns);
+    pango_attr_list_unref(v->current_row_attributes);
+    cxMapFree(v->bound_rows);
 #endif
     free(v->selection.rows);
     free(v);
@@ -1947,7 +2284,7 @@
     }
 }
 
-static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
+static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
     GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
     if(item->icon) {
         GtkWidget *icon = ICON_IMAGE(item->icon);
@@ -1959,7 +2296,6 @@
     if(item->badge) {
         
     }
-    GtkWidget *row = gtk_list_box_row_new();
     LISTBOX_ROW_SET_CHILD(row, hbox);
     
     // signals 
@@ -1987,7 +2323,7 @@
     if(item->badge) {
         GtkWidget *badge = gtk_label_new(item->badge);
         WIDGET_ADD_CSS_CLASS(badge, "ui-badge");
-#if GTK_CHECK_VERSION(4, 0, 0)
+#if GTK_CHECK_VERSION(3, 14, 0)
         gtk_widget_set_valign(badge, GTK_ALIGN_CENTER);
         BOX_ADD(hbox, badge);
 #else
@@ -2010,8 +2346,36 @@
                 event
                 );
     }
+}
+
+static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) {
+    GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index);
+    if(!row) {
+        return;
+    }
+    UiList *list = sublist->var->value;
+    if(!list) {
+        return;
+    }
     
-    return row;
+    void *elm = list->get(list, index);
+    UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
+    if(listbox->getvalue) {
+        listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata);
+    } else {
+        item.label = strdup(elm);
+    }
+    
+    LISTBOX_ROW_REMOVE_CHILD(row);
+    
+    listbox_fill_row(listbox, GTK_WIDGET(row), sublist, &item, index);
+    
+     // cleanup
+    free(item.label);
+    free(item.icon);
+    free(item.button_label);
+    free(item.button_icon);
+    free(item.badge);
 }
 
 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) {
@@ -2058,7 +2422,8 @@
         }
         
         // create listbox item
-        GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index);
+        GtkWidget *row = gtk_list_box_row_new();
+        listbox_fill_row(listbox, row, sublist, &item, (int)index);
         if(index == 0) {
             // first row in the sublist, set ui_listbox data to the row
             // which is then used by the headerfunc
@@ -2092,14 +2457,17 @@
 
 void ui_listbox_list_update(UiList *list, int i) {
     UiListBoxSubList *sublist = list->obj;
-    ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos);
-    size_t pos = 0;
-    CxIterator it = cxListIterator(sublist->listbox->sublists);
-    cx_foreach(UiListBoxSubList *, ls, it) {
-        ls->startpos = pos;
-        pos += sublist->numitems;
+    if(i < 0) {
+        ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos);
+        size_t pos = 0;
+        CxIterator it = cxListIterator(sublist->listbox->sublists);
+        cx_foreach(UiListBoxSubList *, ls, it) {
+            ls->startpos = pos;
+            pos += ls->numitems;
+        }
+    } else {
+        update_sublist_item(sublist->listbox, sublist, i);
     }
-    
 }
 
 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
--- a/ui/gtk/list.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/list.h	Sat Oct 04 14:52:59 2025 +0200
@@ -40,6 +40,13 @@
     
 typedef struct UiColData UiColData;
 
+#if GTK_CHECK_VERSION(4, 10, 0)
+typedef struct UiRowItems {
+    int bound;
+    GtkListItem *items[];
+} UiRowItems;
+#endif
+
 typedef struct UiListView {
     UiObject          *obj;
     GtkWidget         *widget;
@@ -47,12 +54,22 @@
     UiModel           *model;
     ui_getvaluefunc2  getvalue;
     void              *getvaluedata;
+    ui_getstylefunc   getstyle;
+    void              *getstyledata;
     char              **elements;
     size_t            nelm;
+    int               current_row;
+    UiTextStyle       row_style;
+    UiBool            apply_row_style;
 #if GTK_CHECK_VERSION(4, 10, 0)
+    CxMap             *bound_rows;
     GListStore        *liststore;
     GtkSelectionModel *selectionmodel;
     UiColData         *columns;
+    int               numcolumns;
+    PangoAttrList     *current_row_attributes;
+#else
+    int               style_offset;
 #endif
     ui_callback       onactivate;
     void              *onactivatedata;
@@ -64,6 +81,8 @@
     void              *ondragcompletedata;
     ui_callback       ondrop;
     void              *ondropdata;
+    ui_list_savefunc  onsave;
+    void              *onsavedata;
     UiListSelection   selection;
     
 } UiListView;
--- a/ui/gtk/menu.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/menu.c	Sat Oct 04 14:52:59 2025 +0200
@@ -576,8 +576,12 @@
     ls->oldcount = 0;
     ls->getvalue = il->getvalue;
     
-    //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
-    UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
+    GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    snprintf(ls->action, 32, "win.%s", item->id);
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
     ls->var = var;
     if(var) {
         UiList *list = var->value;
@@ -604,10 +608,6 @@
     ls->callback = il->callback;
     ls->userdata = il->userdata;
     
-    GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
-    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
-    snprintf(ls->action, 32, "win.%s", item->id);
-    
     
     UiEventData *event = malloc(sizeof(UiEventData));
     event->obj = obj;
@@ -650,6 +650,10 @@
     UiVar *var = event->customdata;
     UiList *list = var->value;
     
+    if(!event->callback) {
+        return;
+    }
+    
     UiEvent evt;
     evt.obj = event->obj;
     evt.window = event->obj->window;
--- a/ui/gtk/range.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/range.c	Sat Oct 04 14:52:59 2025 +0200
@@ -126,7 +126,7 @@
 #else
     gtk_adjustment_set_page_size(a, extent);
 #endif
-#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318
+#if !GTK_CHECK_VERSION(3, 18, 0)
     gtk_adjustment_changed(a);
 #endif
     range->extent = extent;
--- a/ui/gtk/text.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/text.c	Sat Oct 04 14:52:59 2025 +0200
@@ -616,10 +616,11 @@
                 uitext);
     
     if(args->width > 0) {
-        // TODO: gtk4
-#if GTK_MAJOR_VERSION <= 3
-        gtk_entry_set_width_chars(GTK_ENTRY(textfield), args->width);
-#endif
+        // An early implementation used gtk_entry_set_width_chars,
+        // but that is not available on gtk4 and other toolkits
+        // also don't have this. Setting the width in pixels can
+        // be implemented on all platforms
+        gtk_widget_set_size_request(textfield, args->width, -1);
     }
     if(frameless) {
         // TODO: gtk2legacy workaroud
--- a/ui/gtk/toolkit.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/toolkit.c	Sat Oct 04 14:52:59 2025 +0200
@@ -79,7 +79,6 @@
 #endif
     
     ui_css_init();
-    uic_docmgr_init();
     uic_menu_init();
     uic_toolbar_init();
     ui_image_init();
@@ -323,15 +322,15 @@
     ui_destroy_boundvar(NULL, var);
 }
 
+// TODO: move to common
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+    uic_save_var(var);
     uic_unbind_var(var);
     
+    // UI_VAR_SPECIAL: anonymous value variable, that is not registered
+    //                 in ctx->vars
     if(var->type == UI_VAR_SPECIAL) {
         ui_free(var->from_ctx, var);
-    } else {
-        ui_free(var->from_ctx, var);
-        // TODO: free or unbound
-        //uic_remove_bound_var(ctx, var);
     }
 }
 
@@ -404,6 +403,14 @@
 "  margin-left: 4px;"
 "  margin-right: 4px;"
 "}\n"
+".ui-nopadding {"
+"  padding: 0;"
+"}\n"
+".ui-table-entry {"
+"  border: none;"
+"  box-shadow: none;"
+"  background: transparent;"
+"}\n"
 ;
 
 #elif GTK_MAJOR_VERSION == 3
@@ -446,6 +453,9 @@
 "  margin-left: 4px;"
 "  margin-right: 4px;"
 "}\n"
+".ui-nopadding {"
+"  padding: 0;"
+"}\n"
 ;
 #endif
 
--- a/ui/gtk/toolkit.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/toolkit.h	Sat Oct 04 14:52:59 2025 +0200
@@ -74,6 +74,7 @@
 #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon)
 #define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row)
 #define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child)
+#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), NULL)
 #define PANED_SET_CHILD1(paned, child) gtk_paned_set_start_child(GTK_PANED(paned), child)
 #define PANED_SET_CHILD2(paned, child) gtk_paned_set_end_child(GTK_PANED(paned), child)
 #else
@@ -96,6 +97,7 @@
 #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON)
 #define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row)
 #define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child)
+#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_container_remove(GTK_CONTAINER(row), gtk_bin_get_child(GTK_BIN(row)))
 #define PANED_SET_CHILD1(paned, child) gtk_paned_pack1(GTK_PANED(paned), child, TRUE, TRUE)
 #define PANED_SET_CHILD2(paned, child) gtk_paned_pack2(GTK_PANED(paned), child, TRUE, TRUE)
 #endif
--- a/ui/gtk/webview.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/webview.c	Sat Oct 04 14:52:59 2025 +0200
@@ -103,7 +103,7 @@
 }
 
 void ui_webview_load_url(UiGeneric *g, const char *url) {
-    WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL };
+    WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL, .javascript = TRUE, .zoom = 1 };
     g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE);
 }
 
@@ -129,6 +129,8 @@
     data.mimetype = (char*)mimetype;
     data.encoding = (char*)encoding;
     data.type = WEBVIEW_CONTENT_CONTENT;
+    data.javascript = FALSE;
+    data.zoom = 1;
     g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE);
 }
 
--- a/ui/gtk/window.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/gtk/window.c	Sat Oct 04 14:52:59 2025 +0200
@@ -49,6 +49,9 @@
 static int window_default_width = 650;
 static int window_default_height = 550;
 
+static int splitview_window_default_pos = -1;
+static UiBool splitview_window_use_prop = TRUE;
+
 static gboolean ui_window_destroy(void *data)  {
     UiObject *obj = data;
     uic_object_destroy(obj);
@@ -77,6 +80,36 @@
 }
 
 static gboolean ui_window_close_request(UiObject *obj) {
+    if(obj->widget) {
+        void *appwindow = g_object_get_data(G_OBJECT(obj->widget), "ui.appwindow");
+        if(appwindow) {
+            int width = 0;
+            int height = 0;
+#if GTK_CHECK_VERSION(4, 10, 0)
+            graphene_rect_t bounds;
+            if(gtk_widget_compute_bounds(obj->widget, obj->widget, &bounds)) {
+                width = bounds.size.width;
+                height = bounds.size.height;
+            }
+#elif GTK_CHECK_VERSION(4, 0, 0)
+            GtkAllocation alloc;
+            gtk_widget_get_allocation(GTK_WIDGET(obj->widget), &alloc);
+            width = alloc.width;
+            height = alloc.height;
+#else
+            gtk_window_get_size(GTK_WINDOW(obj->widget), &width, &height);
+#endif
+            if(width > 0 && height > 0) {
+                char width_str[32];
+                char height_str[32];
+                snprintf(width_str, 32, "%d", width);
+                snprintf(height_str, 32, "%d", height);
+                ui_set_property("ui.window.width", width_str);
+                ui_set_property("ui.window.height", height_str);
+            }
+        }
+    }
+    
     uic_context_prepare_close(obj->ctx);
     obj->ref--;
     if(obj->ref > 0) {
@@ -101,7 +134,14 @@
 }
 #endif
 
-static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) {
+static void save_window_splitview_pos(GtkWidget *widget, void *unused) {
+    int pos = gtk_paned_get_position(GTK_PANED(widget));
+    char buf[32];
+    snprintf(buf, 32, "%d", pos);
+    ui_set_property("ui.window.splitview.pos", buf);
+}
+
+static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) {
     UiObject *obj = uic_object_new_toplevel();
    
 #ifdef UI_LIBADWAITA
@@ -122,19 +162,28 @@
         gtk_window_set_title(GTK_WINDOW(obj->widget), title);
     }
     
-    const char *width = ui_get_property("ui.window.width");
-    const char *height = ui_get_property("ui.window.height");
-    if(width && height) {
-        gtk_window_set_default_size(
+    if(!simple) {
+        g_object_set_data(G_OBJECT(obj->widget), "ui.appwindow", obj);
+    }
+    
+    int window_width = window_default_width;
+    int window_height = window_default_height;
+    if(!simple) {
+        const char *width = ui_get_property("ui.window.width");
+        const char *height = ui_get_property("ui.window.height");
+        if(width && height) {
+            int w = atoi(width);
+            int h = atoi(height);
+            if(w > 0 && h > 0) {
+                window_width = w;
+                window_height = h;
+            }
+        }
+    }
+    gtk_window_set_default_size(
                 GTK_WINDOW(obj->widget),
-                atoi(width),
-                atoi(height));
-    } else {
-        gtk_window_set_default_size(
-                GTK_WINDOW(obj->widget),
-                window_default_width + sidebar*250,
-                window_default_height);
-    }
+                window_width,
+                window_height);
     
     obj->destroy = ui_window_widget_destroy;
     g_signal_connect(
@@ -161,50 +210,95 @@
     GtkWidget *toolbar_view = adw_toolbar_view_new();
     adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
     
-    GtkWidget *content_box = ui_gtk_vbox_new(0);
-    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    GtkWidget *headerbar_sidebar = NULL;
+    GtkWidget *headerbar_main = adw_header_bar_new();
+    GtkWidget *headerbar_right = NULL;
     
-    GtkWidget *sidebar_headerbar = NULL; 
+    GtkWidget *content = toolbar_view;
+    if(splitview) {
+        content = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        g_signal_connect(
+                content,
+                "destroy",
+                G_CALLBACK(save_window_splitview_pos),
+                NULL);
+        
+        const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos");
+        int pos = splitview_window_default_pos;
+        if(pos < 0) {
+            pos = window_width / 2;
+        }
+        if(splitview_pos_str && splitview_window_use_prop) {
+            int splitview_pos = atoi(splitview_pos_str);
+            if(splitview_pos > 0) {
+                pos = splitview_pos;
+            }
+        }
+        gtk_paned_set_position(GTK_PANED(content), pos);
+        
+        GtkWidget *right_panel = adw_toolbar_view_new();
+        GtkWidget *right_vbox = ui_gtk_vbox_new(0);
+        adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(right_panel), right_vbox);
+        
+        headerbar_right = adw_header_bar_new();
+        adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_right), FALSE);
+        adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(right_panel), headerbar_right);
+        
+        adw_header_bar_set_show_end_title_buttons(ADW_HEADER_BAR(headerbar_main), FALSE);
+        
+        gtk_paned_set_start_child(GTK_PANED(content), toolbar_view);
+        gtk_paned_set_end_child(GTK_PANED(content), right_panel);
+        
+        g_object_set_data(G_OBJECT(obj->widget), "ui_window_splitview", content);
+        g_object_set_data(G_OBJECT(obj->widget), "ui_left_panel", vbox);
+        g_object_set_data(G_OBJECT(obj->widget), "ui_right_panel", right_vbox);
+    }
+    
+    GtkWidget *content_box = vbox;
+    
     if(sidebar) {
         GtkWidget *splitview = adw_overlay_split_view_new();
         adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview);
         
         GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new();
         adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view);
-        sidebar_headerbar = adw_header_bar_new();
-        adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar);
+        headerbar_sidebar = adw_header_bar_new();
+        adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE);
+        adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), headerbar_sidebar);
         
-        adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view);
+        adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), content);
         
         g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view);
     } else {
-        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), content);
     }
     
-    GtkWidget *headerbar = adw_header_bar_new();
-    
     const char *show_title = ui_get_property("ui.gtk.window.showtitle");
     if(show_title) {
         if(!strcmp(show_title, "main") && sidebar) {
-            adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE);
         } else if(!strcmp(show_title, "sidebar")) {
-            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE);
         } else if(!strcmp(show_title, "false")) {
-            adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
-            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE);
         } else {
             fprintf(stderr, "Unknown value '%s' for property ui.gtk.window.showtitle\n", show_title);
-            adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE);
         }
     } else {
-        adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+        adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE);
+        if(sidebar) {
+            adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE);
+        }
     }
     
-    adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
-    g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar);
+    adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar_main);
+    g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar_main);
     
     if(!simple) {
-        ui_fill_headerbar(obj, headerbar);
+        ui_fill_headerbar(obj, headerbar_sidebar, headerbar_main, headerbar_right);
     }
 #elif GTK_MAJOR_VERSION >= 4
     GtkWidget *content_box = ui_gtk_vbox_new(0);
@@ -286,15 +380,19 @@
 
 
 UiObject* ui_window(const char *title, void *window_data) {
-    return create_window(title, window_data, FALSE, FALSE);
+    return create_window(title, window_data, FALSE, FALSE, FALSE);
 }
 
 UiObject *ui_sidebar_window(const char *title, void *window_data) {
-    return create_window(title, window_data, TRUE, FALSE);
+    return create_window(title, window_data, TRUE, FALSE, FALSE);
+}
+
+UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) {
+    return create_window(title, NULL, sidebar, TRUE, FALSE);
 }
 
 UiObject* ui_simple_window(const char *title, void *window_data) {
-    return create_window(title, window_data, FALSE, TRUE);
+    return create_window(title, window_data, FALSE, FALSE, TRUE);
 }
 
 void ui_window_size(UiObject *obj, int width, int height) {
@@ -304,6 +402,38 @@
                 height);
 }
 
+void ui_window_default_size(int width, int height) {
+    window_default_width = width;
+    window_default_height = height;
+}
+
+void ui_splitview_window_set_pos(UiObject *obj, int pos) {
+    GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview");
+    if(splitview) {
+        gtk_paned_set_position(GTK_PANED(splitview), pos);
+    } else {
+        fprintf(stderr, "Error: window has no splitview\n");
+    }
+}
+
+int ui_splitview_window_get_pos(UiObject *obj) {
+    GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview");
+    if(splitview) {
+        return gtk_paned_get_position(GTK_PANED(splitview));
+    } else {
+        fprintf(stderr, "Error: window has no splitview\n");
+    }
+    return 0;
+}
+
+void ui_splitview_window_set_default_pos(int pos) {
+    splitview_window_default_pos = pos;
+}
+
+void ui_splitview_window_use_property(UiBool enable) {
+    splitview_window_use_prop = enable;
+}
+
 #ifdef UI_LIBADWAITA
 
 static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) {
@@ -693,18 +823,6 @@
                 G_CALLBACK(ui_destroy_userdata),
                 event);
     
-    
-    UiEvent evt;
-    evt.obj = obj;
-    evt.document = evt.obj->ctx->document;
-    evt.window = evt.obj->window;
-    evt.intval = 0;
-    
-    UiFileList flist;
-    flist.files = NULL;
-    flist.nfiles = 0;
-    evt.eventdata = &flist;
-    
     gtk_widget_show(dialog);
 }
 #endif
--- a/ui/motif/Grid.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/motif/Grid.c	Sat Oct 04 14:52:59 2025 +0200
@@ -299,31 +299,32 @@
 WidgetClass gridClass = (WidgetClass)&gridClassRec;
 
 
-void grid_class_initialize(Widget request, Widget new, ArgList args, Cardinal *num_args) {
+void grid_class_initialize(void) {
     
 }
 void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) {
-    MyWidget mn = (MyWidget)new;
+    Grid mn = (Grid)new;
     
     mn->mywidget.max_col = 0;
     mn->mywidget.max_row = 0;
     
 }
-void grid_realize(MyWidget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) {
-    XtMakeResizeRequest((Widget)w, 400, 400, NULL, NULL);
+void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) {
+    Grid grid = (Grid)w;
+    XtMakeResizeRequest(w, 400, 400, NULL, NULL);
     (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); 
-    grid_place_children(w);
+    grid_place_children(grid);
 }
 
 
-void grid_destroy(MyWidget widget) {
+void grid_destroy(Grid widget) {
     
 }
-void grid_resize(MyWidget widget) {
+void grid_resize(Grid widget) {
     grid_place_children(widget);
 }
 
-void grid_expose(MyWidget widget, XEvent *event, Region region) {
+void grid_expose(Grid widget, XEvent *event, Region region) {
     
 }
 
@@ -336,11 +337,11 @@
     
 }
 
-void grid_getfocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) {
     
 }
 
-void grid_loosefocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) {
     
 }
 
@@ -358,7 +359,7 @@
         widget->core.height = request->height;
         constraints->grid.pref_height = request->height;
     }
-    grid_place_children((MyWidget)XtParent(widget));
+    grid_place_children((Grid)XtParent(widget));
     return XtGeometryYes;
 }
 
@@ -368,7 +369,7 @@
 
 Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
     GridConstraintRec *constraints = neww->core.constraints;
-    MyWidget grid = (MyWidget)XtParent(neww);
+    Grid grid = (Grid)XtParent(neww);
     if(constraints->grid.x > grid->mywidget.max_col) {
         grid->mywidget.max_col = constraints->grid.x;
     }
@@ -387,7 +388,7 @@
 {
     GridConstraintRec *constraints = neww->core.constraints;
     
-    MyWidget grid = (MyWidget)XtParent(neww);
+    Grid grid = (Grid)XtParent(neww);
     if(constraints->grid.x > grid->mywidget.max_col) {
         grid->mywidget.max_col = constraints->grid.x;
     }
@@ -398,7 +399,7 @@
     constraints->grid.pref_height = neww->core.height;
 }
 
-void grid_place_children(MyWidget w) {
+void grid_place_children(Grid w) {
     int ncols = w->mywidget.max_col+1;
     int nrows = w->mywidget.max_row+1;
     GridDef *cols = calloc(ncols, sizeof(GridDef));
--- a/ui/motif/Grid.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/motif/Grid.h	Sat Oct 04 14:52:59 2025 +0200
@@ -125,23 +125,23 @@
     GridContraintPart grid;
 } GridConstraintRec;
 
-typedef GridRec* MyWidget;
+typedef GridRec* Grid;
 
 extern WidgetClass gridClass;
 
-void grid_class_initialize();
-void grid_initialize();
-void grid_realize();
-void grid_destroy();
-void grid_resize();
-void grid_expose();
-Boolean grid_set_values();
+void grid_class_initialize(void);
+void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args);
+void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes);
+void grid_destroy(Grid widget);
+void grid_resize(Grid widget);
+void grid_expose(Grid widget, XEvent *event, Region region);
+Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
 Boolean grid_acceptfocus(Widget , Time*);
 
-void grid_place_children(MyWidget w);
+void grid_place_children(Grid w);
 
-void grid_getfocus();
-void grid_loosefocus();
+void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam);
+void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam);
 
 void grid_constraint_init(
     Widget	request,
--- a/ui/motif/toolkit.c	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/motif/toolkit.c	Sat Oct 04 14:52:59 2025 +0200
@@ -100,7 +100,6 @@
     
     display =  XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv);
     
-    uic_docmgr_init();
     uic_menu_init();
     uic_toolbar_init();
     uic_load_app_properties();
--- a/ui/qt/entry.cpp	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/qt/entry.cpp	Sat Oct 04 14:52:59 2025 +0200
@@ -36,14 +36,19 @@
 
 
 
-UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args) {
+UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) {
     UiContainerPrivate *ctn = ui_obj_container(obj);
     UI_APPLY_LAYOUT(ctn->layout, args);
     
+    double min = args->min;
+    double max = args->max != 0 ? args->max : 100000;
+    
     bool use_double = false;
     UiVar *var = NULL;
+    UiVarType vartype = UI_VAR_SPECIAL;
     if(args->varname) {
         var = uic_get_var(obj->ctx, args->varname);
+        vartype = var->type;
         if(var->type == UI_VAR_DOUBLE) {
             use_double = true;
         } else if(var->type == UI_VAR_RANGE) {
@@ -57,11 +62,14 @@
     if(!var) {
         if(args->intvalue) {
             var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER);
+            vartype = UI_VAR_INTEGER;
         } else if(args->doublevalue) {
             var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE);
+            vartype = UI_VAR_DOUBLE;
             use_double = true;
         } else if(args->rangevalue) {
             var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE);
+            vartype = UI_VAR_RANGE;
             use_double = true;
         } else {
             if(args->digits > 0) {
@@ -70,6 +78,12 @@
         }
     }
     
+    if(vartype == UI_VAR_RANGE) {
+        UiRange *r = (UiRange*)var->value;
+        min = r->min;
+        max = r->max;
+    }
+    
     QAbstractSpinBox *widget = nullptr;
     if(use_double) {
         QDoubleSpinBox *spinbox = new QDoubleSpinBox();
@@ -77,17 +91,21 @@
         if(args->step != 0) {
             spinbox->setSingleStep(args->step);
         }
+        spinbox->setMinimum(min);
+        spinbox->setMaximum(max);
         widget = spinbox;
     } else {
         QSpinBox *spinbox = new QSpinBox();
         if(args->step != 0) {
             spinbox->setSingleStep(args->step);
         }
+        spinbox->setMinimum((int)min);
+        spinbox->setMaximum((int)max);
         widget = spinbox;
     }
     
     if(var) {
-        if(var->type == UI_VAR_INTEGER) {
+        if(vartype == UI_VAR_INTEGER) {
             UiInteger *value = (UiInteger*)var->value;
             value->obj = widget;
             if(value->value != 0) {
@@ -96,7 +114,7 @@
             }
             value->get = ui_spinbox_int_get;
             value->set = ui_spinbox_int_set;
-        } else if(var->type == UI_VAR_DOUBLE) {
+        } else if(vartype == UI_VAR_DOUBLE) {
             UiDouble *value = (UiDouble*)var->value;
             value->obj = widget;
             if(value->value != 0) {
@@ -105,7 +123,7 @@
             }
             value->get = ui_spinbox_double_get;
             value->set = ui_spinbox_double_set;
-        } else if(var->type == UI_VAR_RANGE) {
+        } else if(vartype == UI_VAR_RANGE) {
             UiRange *value = (UiRange*)var->value;
             value->obj = widget;
             QDoubleSpinBox *spinbox = (QDoubleSpinBox*)widget;
--- a/ui/qt/toolkit.cpp	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/qt/toolkit.cpp	Sat Oct 04 14:52:59 2025 +0200
@@ -62,7 +62,6 @@
     app_argv = argv;
     application = new QApplication(app_argc, app_argv);
     
-    uic_docmgr_init();
     uic_menu_init();
     uic_toolbar_init();
     
--- a/ui/qt/widget.cpp	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/qt/widget.cpp	Sat Oct 04 14:52:59 2025 +0200
@@ -52,6 +52,14 @@
     return separator;
 }
 
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    widget->setEnabled(enabled);
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    widget->setVisible(visible);
+}
+
 void ui_widget_set_size(UIWIDGET w, int width, int height) {
     w->resize(width >= 0 ? width : w->width(), height >= 0 ? height : w->height());
 }
--- a/ui/ui/container.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/container.h	Sat Oct 04 14:52:59 2025 +0200
@@ -128,9 +128,6 @@
     int spacing;
     int columnspacing;
     int rowspacing;
-
-    const char* label;
-    UiBool isexpanded;
 } UiTabViewArgs;
 
 typedef struct UiHeaderbarArgs {
@@ -177,6 +174,7 @@
     int rowspacing;
     
     int initial_position;
+    const char *position_property;
     UiInteger *value;
     const char* varname;
     int max_panes;
@@ -246,6 +244,8 @@
 #define ui_tabview(obj, ...) for(ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, &(UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_sidebar(obj, ...) for(ui_sidebar_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_left_panel(obj, ...) for(ui_left_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_right_panel(ob, ...) for(ui_right_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 
 #define ui_vbox0(obj) for(ui_vbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_hbox0(obj) for(ui_hbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
@@ -256,6 +256,9 @@
 #define ui_tabview0(obj) for(ui_tabview_create(obj, &(UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_headerbar0(obj) for(ui_headerbar_create(obj, &(UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_sidebar0(obj) for(ui_sidebar_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+
 
 #define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
@@ -301,6 +304,8 @@
 UIEXPORT void ui_headerbar_end_create(UiObject *obj);
 
 UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args);
+UIEXPORT UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args);
+UIEXPORT UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args);
 
 UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args);
 
--- a/ui/ui/entry.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/entry.h	Sat Oct 04 14:52:59 2025 +0200
@@ -36,7 +36,7 @@
 #endif
 
 
-typedef struct UiSpinnerArgs {
+typedef struct UiSpinBoxArgs {
     UiBool fill;
     UiBool hexpand;
     UiBool vexpand;
@@ -45,11 +45,14 @@
     UiBool override_defaults;
     int colspan;
     int rowspan;
+    int width;
     const char *name;
     const char *style_class;
 
     double step;
     int digits;
+    double min;
+    double max;
     UiInteger *intvalue;
     UiDouble* doublevalue;
     UiRange *rangevalue;
@@ -58,13 +61,13 @@
     void* onchangedata;
     
     const int *groups;
-} UiSpinnerArgs;
+} UiSpinBoxArgs;
 
 
     
-UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args);
+UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args);
 
-#define ui_spinner(obj, ...) ui_spinner_create(obj, &(UiSpinnerArgs){ __VA_ARGS__ } )
+#define ui_spinbox(obj, ...) ui_spinbox_create(obj, &(UiSpinBoxArgs){ __VA_ARGS__ } )
 
 void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
 void ui_spinner_setdigits(UIWIDGET spinner, int digits);
--- a/ui/ui/properties.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/properties.h	Sat Oct 04 14:52:59 2025 +0200
@@ -41,14 +41,14 @@
 
 int ui_properties_store(void);
 
-void ui_locales_dir(char *path);
-void ui_pixmaps_dir(char *path);
+void ui_locales_dir(const char *path);
+void ui_pixmaps_dir(const char *path);
 
-void ui_load_lang(char *locale);
+void ui_load_lang(const char *locale);
 void ui_load_lang_def(char *locale, char *default_locale);
     
-char* uistr(char *name);
-char* uistr_n(char *name);
+char* uistr(const char *name);
+char* uistr_n(const char *name);
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/toolbar.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/toolbar.h	Sat Oct 04 14:52:59 2025 +0200
@@ -37,38 +37,43 @@
 #endif
 
 typedef struct UiToolbarItemArgs {
-	const char* label;
-	const char* stockid;
-	const char* icon;
+    const char* label;
+    const char* stockid;
+    const char* icon;
 
-	ui_callback onclick;
-	void* onclickdata;
-        
-        const int *groups;
+    ui_callback onclick;
+    void* onclickdata;
+
+    const int *groups;
 } UiToolbarItemArgs;
 
 typedef struct UiToolbarToggleItemArgs {
-	const char* label;
-	const char* stockid;
-	const char* icon;
+    const char* label;
+    const char* stockid;
+    const char* icon;
 
-	const char* varname;
-	ui_callback onchange;
-	void* onchangedata;
-        
-        const int *groups;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+
+    const int *groups;
 } UiToolbarToggleItemArgs;
 
 typedef struct UiToolbarMenuArgs {
-	const char* label;
-	const char* stockid;
-	const char* icon;
+    const char* label;
+    const char* stockid;
+    const char* icon;
 } UiToolbarMenuArgs;
 
 enum UiToolbarPos {
-	UI_TOOLBAR_LEFT = 0,
-	UI_TOOLBAR_CENTER,
-	UI_TOOLBAR_RIGHT
+    UI_TOOLBAR_LEFT = 0,
+    UI_TOOLBAR_CENTER,
+    UI_TOOLBAR_RIGHT,
+    UI_TOOLBAR_SIDEBAR_LEFT,
+    UI_TOOLBAR_SIDEBAR_RIGHT,
+    UI_TOOLBAR_RIGHTPANEL_LEFT,
+    UI_TOOLBAR_RIGHTPANEL_CENTER,
+    UI_TOOLBAR_RIGHTPANEL_RIGHT
 };
 
 #define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, &(UiToolbarItemArgs){ __VA_ARGS__ } )
--- a/ui/ui/toolkit.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/toolkit.h	Sat Oct 04 14:52:59 2025 +0200
@@ -200,6 +200,9 @@
 
 typedef struct UiListSelection UiListSelection;
 
+typedef struct UiTextStyle  UiTextStyle;
+typedef struct UiColor      UiColor;
+
 /* begin opaque types */
 typedef struct UiContext     UiContext;
 typedef struct UiContainer   UiContainer;
@@ -251,6 +254,7 @@
 
 typedef void*(*ui_getvaluefunc)(void *elm, int col);
 typedef void*(*ui_getvaluefunc2)(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult);
+typedef UiBool(*ui_getstylefunc)(UiList *list, void *elm, int row, int col, void *userdata, UiTextStyle *style);
 
 typedef int(*ui_threadfunc)(void*);
 
@@ -502,6 +506,23 @@
     UI_EVENT_DATA_FILE_LIST
 };
 
+#define UI_COLOR(r, g, b) (UiColor){r, g, b}
+struct UiColor {
+    uint8_t red;
+    uint8_t green;
+    uint8_t blue;
+};
+
+#define UI_TEXT_STYLE_BOLD      1
+#define UI_TEXT_STYLE_ITALIC    2
+#define UI_TEXT_STYLE_UNDERLINE 4
+
+struct UiTextStyle {
+    uint32_t text_style;
+    UiColor  fg;
+    UiBool   fg_set;
+};
+
 
 UIEXPORT void ui_init(const char *appname, int argc, char **argv);
 UIEXPORT const char* ui_appname();
@@ -547,6 +568,8 @@
 UIEXPORT void ui_detach_document(UiContext *ctx, void *document);
 
 UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
+UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups);
+UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates);
 
 UIEXPORT void ui_set_group(UiContext *ctx, int group);
 UIEXPORT void ui_unset_group(UiContext *ctx, int group);
@@ -590,6 +613,13 @@
 UIEXPORT char* ui_string_get(UiString *s);
 UIEXPORT void ui_text_set(UiText *s, const char* value);
 UIEXPORT char* ui_text_get(UiText *s);
+UIEXPORT void ui_range_set(UiRange *r, double value);
+UIEXPORT void ui_range_set_range(UiRange *r, double min, double max);
+UIEXPORT void ui_range_set_extent(UiRange *r, double extent);
+UIEXPORT double ui_range_get(UiRange *r);
+UIEXPORT double ui_range_get_min(UiRange *r);
+UIEXPORT double ui_range_get_max(UiRange *r);
+UIEXPORT double ui_range_get_extent(UiRange *r);
 UIEXPORT void ui_generic_set_image(UiGeneric *g, void *img);
 UIEXPORT void* ui_generic_get_image(UiGeneric *g);
 
@@ -636,11 +666,6 @@
 
 UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove?
 
-// general widget functions
-UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled);
-UIEXPORT void ui_set_show_all(UIWIDGET widget, int value);
-UIEXPORT void ui_set_visible(UIWIDGET widget, int visible);
-
 
 
 UIEXPORT void ui_listselection_free(UiListSelection selection);
@@ -651,7 +676,7 @@
 
 
 UIEXPORT char* ui_getappdir(void);
-UIEXPORT char* ui_configfile(char *name);
+UIEXPORT char* ui_configfile(const char *name);
 
 UIEXPORT UiCondVar* ui_condvar_create(void);
 UIEXPORT void ui_condvar_wait(UiCondVar *var);
--- a/ui/ui/tree.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/tree.h	Sat Oct 04 14:52:59 2025 +0200
@@ -51,9 +51,20 @@
     UI_INTEGER,
     UI_ICON,
     UI_ICON_TEXT,
-    UI_ICON_TEXT_FREE
+    UI_ICON_TEXT_FREE,
+    UI_STRING_EDITABLE
 } UiModelType;
 
+typedef struct UiCellValue {
+    union {
+        const char *string;
+        int64_t i;
+    };
+    UiModelType type;
+} UiCellValue;
+
+typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue *value, void *userdata);
+
 struct UiModel {
     /*
      * number of columns
@@ -61,6 +72,11 @@
     int columns;
     
     /*
+     * current allocation size (internal)
+     */
+    int alloc;
+    
+    /*
      * array of column types
      * array length is the number of columns
      */
@@ -115,6 +131,8 @@
     ui_getvaluefunc getvalue;
     ui_getvaluefunc2 getvalue2;
     void *getvalue2data;
+    ui_getstylefunc getstyle;
+    void *getstyledata;
     ui_callback onactivate;
     void* onactivatedata;
     ui_callback onselection;
@@ -127,6 +145,8 @@
     void* ondropdata;
     UiBool multiselection;
     UiMenuBuilder *contextmenu;
+    ui_list_savefunc onsave;
+    void *onsavedata;
     
     const int *groups;
 };
@@ -247,6 +267,8 @@
  * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1);
  */
 UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
+UIEXPORT UiModel* ui_model_new(UiContext *ctx);
+UIEXPORT void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width);
 UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
 UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
 
--- a/ui/ui/widget.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/widget.h	Sat Oct 04 14:52:59 2025 +0200
@@ -69,6 +69,9 @@
 
 #define ui_separator(obj, ...) ui_separator_create(obj, &(UiWidgetArgs){ __VA_ARGS__ } )
 
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled);
+UIEXPORT void ui_set_visible(UIWIDGET widget, int visible);
+
 UIEXPORT void ui_widget_set_size(UIWIDGET w, int width, int height);
 UIEXPORT void ui_widget_redraw(UIWIDGET w);
 
--- a/ui/ui/window.h	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/ui/window.h	Sat Oct 04 14:52:59 2025 +0200
@@ -74,6 +74,7 @@
 
 UIEXPORT UiObject *ui_window(const char *title, void *window_data);
 UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data);
+UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar);
 UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data);
 UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args);
 
@@ -81,6 +82,12 @@
 #define ui_dialog_window0(parent) ui_dialog_window_create(parent, &(UiDialogWindowArgs){ 0 });
 
 UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
+UIEXPORT void ui_window_default_size(int width, int height);
+
+UIEXPORT void ui_splitview_window_set_pos(UiObject *obj, int pos);
+UIEXPORT int ui_splitview_window_get_pos(UiObject *obj);
+UIEXPORT void ui_splitview_window_set_default_pos(int pos);
+UIEXPORT void ui_splitview_window_use_property(UiBool enable);
 
 #define ui_dialog(parent, ...) ui_dialog_create(parent, &(UiDialogArgs){ __VA_ARGS__ } )
 
--- a/ui/winui/toolkit.cpp	Sun Aug 24 15:24:16 2025 +0200
+++ b/ui/winui/toolkit.cpp	Sat Oct 04 14:52:59 2025 +0200
@@ -165,7 +165,6 @@
 	//ui_appsdk_bootstrap();
 
 	uic_init_global_context();
-	uic_docmgr_init();
         uic_menu_init();
 	uic_toolbar_init();
 	

mercurial