update toolkit default tip

Thu, 12 Dec 2024 20:01:43 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 12 Dec 2024 20:01:43 +0100
changeset 100
d2bd73d28ff1
parent 99
b9767cb5b06b

update toolkit

ui/cocoa/EventData.h file | annotate | diff | comparison | revisions
ui/cocoa/EventData.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/Makefile file | annotate | diff | comparison | revisions
ui/cocoa/UiJob.h file | annotate | diff | comparison | revisions
ui/cocoa/UiJob.m file | annotate | diff | comparison | revisions
ui/cocoa/WindowManager.h file | annotate | diff | comparison | revisions
ui/cocoa/WindowManager.m file | annotate | diff | comparison | revisions
ui/cocoa/appdelegate.h file | annotate | diff | comparison | revisions
ui/cocoa/appdelegate.m file | annotate | diff | comparison | revisions
ui/cocoa/button.h file | annotate | diff | comparison | revisions
ui/cocoa/button.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/objs.mk file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.h file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.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/condvar.c file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/object.h file | annotate | diff | comparison | revisions
ui/common/threadpool.c file | annotate | diff | comparison | revisions
ui/common/threadpool.h file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.h file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/motif/Grid.c file | annotate | diff | comparison | revisions
ui/motif/Grid.h file | annotate | diff | comparison | revisions
ui/motif/button.c file | annotate | diff | comparison | revisions
ui/motif/button.h file | annotate | diff | comparison | revisions
ui/motif/container.c file | annotate | diff | comparison | revisions
ui/motif/container.h file | annotate | diff | comparison | revisions
ui/motif/dnd.c file | annotate | diff | comparison | revisions
ui/motif/graphics.c file | annotate | diff | comparison | revisions
ui/motif/graphics.h file | annotate | diff | comparison | revisions
ui/motif/image.c file | annotate | diff | comparison | revisions
ui/motif/label.c file | annotate | diff | comparison | revisions
ui/motif/list.c file | annotate | diff | comparison | revisions
ui/motif/list.h file | annotate | diff | comparison | revisions
ui/motif/menu.c file | annotate | diff | comparison | revisions
ui/motif/menu.h file | annotate | diff | comparison | revisions
ui/motif/objs.mk file | annotate | diff | comparison | revisions
ui/motif/range.c file | annotate | diff | comparison | revisions
ui/motif/range.h file | annotate | diff | comparison | revisions
ui/motif/stock.c file | annotate | diff | comparison | revisions
ui/motif/stock.h file | annotate | diff | comparison | revisions
ui/motif/text.c file | annotate | diff | comparison | revisions
ui/motif/text.h file | annotate | diff | comparison | revisions
ui/motif/toolbar.c file | annotate | diff | comparison | revisions
ui/motif/toolbar.h file | annotate | diff | comparison | revisions
ui/motif/toolkit.c file | annotate | diff | comparison | revisions
ui/motif/toolkit.h file | annotate | diff | comparison | revisions
ui/motif/window.c file | annotate | diff | comparison | revisions
ui/motif/window.h file | annotate | diff | comparison | revisions
ui/ui/container.h file | annotate | diff | comparison | revisions
ui/ui/text.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/window.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/EventData.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,43 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolkit.h"
+#import "../common/context.h"
+
+@interface EventData : NSObject
+@property UiObject    *obj;
+@property ui_callback callback;
+@property void        *userdata;
+@property void        *data;
+@property int         value;
+
+- (EventData*)init:(ui_callback)cb userdata:(void*)userdata;
+
+- (void)handleEvent:(id)sender;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/EventData.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "EventData.h"
+
+@implementation EventData
+
+- (EventData*)init:(ui_callback)cb userdata:(void*)userdata {
+    _callback = cb;
+    _userdata = userdata;
+    return self;
+}
+
+- (void)handleEvent:(id)sender {
+    if(self.callback) {
+        UiEvent event;
+        event.obj = self.obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = self.data;
+        event.intval = self.value;
+        self.callback(&event, self.userdata);
+    }
+}
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/GridLayout.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,71 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+
+#import "Container.h"
+
+#import <cx/array_list.h>
+
+typedef struct GridElm {
+    NSView *view;
+    int margin;
+    int x;
+    int y;
+    int colspan;
+    int rowspan;
+    BOOL hexpand;
+    BOOL vexpand;
+    BOOL hfill;
+    BOOL vfill;
+} GridElm;
+
+typedef struct GridDef {
+    int size;
+    int pos;
+    int preferred_size;
+    BOOL extend;
+} GridDef;
+
+@interface GridLayout : NSView<Container>
+
+@property int columnspacing;
+@property int rowspacing;
+@property CxList *children;
+@property NSSize preferredSize;
+
+@property NSButton *test;
+
+@property int x;
+@property int y;
+@property int cols;
+@property int rows;
+
+- (GridLayout*)init;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/GridLayout.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,214 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GridLayout.h"
+
+
+
+@implementation GridLayout
+
+@synthesize label=_label;
+@synthesize uilayout=_uilayout;
+@synthesize newline=_newline;
+
+- (GridLayout*)init {
+    self = [super init];
+    _columnspacing = 0;
+    _rowspacing = 0;
+    _children = cxArrayListCreateSimple(sizeof(GridElm), 32);
+    
+    return self;
+}
+
+/*
+- (void) layout {
+    [super layout];
+    
+    NSRect r1 = _test.frame;
+    NSSize s1 = _test.intrinsicContentSize;
+    NSEdgeInsets e1 = _test.alignmentRectInsets;
+    
+    printf("fuck\n");
+}
+ */
+
+
+- (void) layout {
+    int ncols = _cols+1;
+    int nrows = _rows+1;
+    
+    GridDef *coldef = calloc(ncols, sizeof(GridDef));
+    GridDef *rowdef = calloc(nrows, sizeof(GridDef));
+    
+    NSRect viewFrame = self.frame;
+    
+    int colspacing = _columnspacing;
+    int rowspacing = _rowspacing;
+    
+    CxIterator i = cxListIterator(_children);
+    cx_foreach(GridElm *, elm, i) {
+        NSSize size = elm->view.intrinsicContentSize;
+        NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+        if(size.width != NSViewNoIntrinsicMetric) {
+            CGFloat width = size.width + alignment.left + alignment.right;
+            if(width > coldef[elm->x].preferred_size) {
+                coldef[elm->x].preferred_size = width;
+            }
+        }
+        if(size.height != NSViewNoIntrinsicMetric) {
+            CGFloat height = size.height + alignment.top + alignment.right;
+            //CGFloat height = size.height;
+            if(height > rowdef[elm->y].preferred_size) {
+                rowdef[elm->y].preferred_size = height;
+            }
+        }
+        
+        if(elm->hexpand) {
+            coldef[elm->x].extend = TRUE;
+        }
+        if(elm->vexpand) {
+            rowdef[elm->y].extend = TRUE;
+        }
+    }
+    
+    int col_ext = 0;
+    int row_ext = 0;
+    
+    int preferred_width = 0;
+    int preferred_height = 0;
+    for(int j=0;j<ncols;j++) {
+        preferred_width += coldef[j].preferred_size + colspacing;
+        if(coldef[j].extend) {
+            col_ext++;
+        }
+    }
+    for(int j=0;j<nrows;j++) {
+        preferred_height += rowdef[j].preferred_size + rowspacing;
+        if(rowdef[j].extend) {
+            row_ext++;
+        }
+    }
+    
+    _preferredSize.width = preferred_width;
+    _preferredSize.height = preferred_height;
+    
+    
+    int hremaining = viewFrame.size.width - preferred_width;
+    int vremaining = viewFrame.size.height - preferred_height;
+    int hext = hremaining/col_ext;
+    int vext = vremaining/row_ext;
+    
+    for(int j=0;j<ncols;j++) {
+        GridDef *col = &coldef[j];
+        if(col->extend) {
+            col->size = col->preferred_size + hext;
+        } else {
+            col->size = col->preferred_size;
+        }
+    }
+    for(int j=0;j<nrows;j++) {
+        GridDef *row = &rowdef[j];
+        if(row->extend) {
+            row->size = row->preferred_size + vext;
+        } else {
+            row->size = row->preferred_size;
+        }
+    }
+    
+    int pos = 0;
+    for(int j=0;j<ncols;j++) {
+        coldef[j].pos = pos;
+        pos += coldef[j].size + colspacing;
+    }
+    pos = 0;
+    for(int j=0;j<nrows;j++) {
+        rowdef[j].pos = pos;
+        pos += rowdef[j].size + rowspacing;
+    }
+    
+    i = cxListIterator(_children);
+    cx_foreach(GridElm *, elm, i) {
+        //NSSize size = elm->view.intrinsicContentSize;
+        GridDef *col = &coldef[elm->x];
+        GridDef *row = &rowdef[elm->y];
+        
+        NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+        NSRect frame;
+        frame.size.width = col->size;
+        frame.size.height = row->size;
+        frame.origin.x = col->pos - (alignment.left+alignment.right)/2;
+        frame.origin.y = viewFrame.size.height - row->pos - frame.size.height + ((alignment.top+alignment.right)/2);
+        elm->view.frame = frame;
+    }
+    
+    free(coldef);
+    free(rowdef);
+}
+ 
+
+- (NSSize)intrinsicContentSize {
+    return self.preferredSize;
+}
+
+- (void) addView:(NSView*)view fill:(BOOL)fill {
+    if(_newline) {
+        _y++;
+        _x = 0;
+        _newline = FALSE;
+    }
+    
+    GridElm elm;
+    elm.x = _x;
+    elm.y = _y;
+    elm.margin = 0;
+    elm.colspan = _uilayout.colspan;
+    elm.rowspan = _uilayout.rowspan;
+    elm.hfill = _uilayout.hfill;
+    elm.vfill = _uilayout.vfill;
+    elm.hexpand = _uilayout.hexpand;
+    elm.vexpand = _uilayout.vexpand;
+    elm.view = view;
+    cxListAdd(_children, &elm);
+    
+    [self addSubview:view];
+    self.needsLayout = YES;
+    
+    if(_x > _cols) {
+        _cols = _x;
+    }
+    if(_y > _rows) {
+        _rows = _y;
+    }
+    _x++;
+}
+
+- (void) dealloc {
+    cxListDestroy(_children);
+}
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/MainWindow.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,38 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+#import "../ui/window.h"
+
+@interface MainWindow : NSWindow
+
+@property UiObject *uiobj;
+
+- (MainWindow*)init:(UiObject*)obj;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/MainWindow.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,65 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "MainWindow.h"
+#import "Container.h"
+#import "GridLayout.h"
+#import "../common/object.h"
+
+@implementation MainWindow
+
+- (MainWindow*)init:(UiObject*)obj {
+    self.uiobj = obj;
+    NSRect frame = NSMakeRect(300, 200, 600, 500);
+    
+    self = [self initWithContentRect:frame
+                           styleMask:NSWindowStyleMaskTitled |
+            NSWindowStyleMaskResizable |
+            NSWindowStyleMaskClosable |
+            NSWindowStyleMaskMiniaturizable
+                             backing:NSBackingStoreBuffered
+                               defer:false];
+    
+    // create a vertical stackview as default container
+    //BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
+    GridLayout *vbox = [[GridLayout alloc] init];
+    vbox.translatesAutoresizingMaskIntoConstraints = false;
+    [self.contentView addSubview:vbox];
+    [NSLayoutConstraint activateConstraints:@[
+        [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4],
+        [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
+        [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
+        [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
+    ]];
+    
+    uic_object_push_container(obj, ui_create_container(obj, vbox));
+    
+    return self;
+}
+
+@end
--- a/ui/cocoa/Makefile	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/Makefile	Thu Dec 12 20:01:43 2024 +0100
@@ -27,7 +27,7 @@
 #
 
 $(COCOA_OBJPRE)%.o: cocoa/%.m
-	$(CC) -o $@ -c $(CFLAGS) $<
+	$(CC) -o $@ -c -I../ucx -fobjc-arc $(CFLAGS) $(TK_CFLAGS) $<
 
 $(UI_LIB): $(OBJ)
 	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiJob.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,30 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiJob.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,29 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "UiJob.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/WindowManager.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,41 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+
+@interface WindowManager : NSObject<NSWindowDelegate>
+
+@property NSMutableArray<NSWindow*> *windows;
+
++ (WindowManager*) sharedWindowManager;
+
+- (WindowManager*)init;
+
+- (void)addWindow:(NSWindow*)win;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/WindowManager.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,57 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "WindowManager.h"
+
+@implementation WindowManager
+
+static WindowManager *instance = nil;
+
++ (WindowManager*) sharedWindowManager {
+    if(instance == nil) {
+        instance = [[WindowManager alloc] init];
+    }
+    return instance;
+}
+
+- (WindowManager*)init {
+    _windows = [[NSMutableArray alloc] initWithCapacity:16];
+    return self;
+}
+
+- (void)addWindow:(NSWindow*)win {
+    [_windows addObject:win];
+    [win setDelegate:self];
+}
+
+- (void) windowWillClose:(NSNotification *) notification {
+    NSWindow *window = notification.object;
+    [_windows removeObject:window];
+}
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/appdelegate.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,37 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/appdelegate.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "AppDelegate.h"
+
+#import "toolkit.h"
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+    ui_cocoa_onstartup();
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+    ui_cocoa_onexit();
+}
+
+
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
+    return YES;
+}
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/button.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,31 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+
+#import "../ui/button.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/button.m	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "button.h"
+#import "EventData.h"
+#import "Container.h"
+#import <objc/runtime.h>
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+    NSButton *button = [[NSButton alloc] init];
+    if(args.label) {
+        NSString *label = [[NSString alloc] initWithUTF8String:args.label];
+        button.title = label;
+    }
+    
+    if(args.onclick) {
+        EventData *event = [[EventData alloc] init:args.onclick userdata:args.onclickdata];
+        event.obj = obj;
+        button.target = event;
+        button.action = @selector(handleEvent:);
+        objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN);
+    }
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, button, &layout, FALSE);
+    
+    return (__bridge void*)button;
+}
--- a/ui/cocoa/container.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/container.h	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,19 +26,65 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "../ui/toolkit.h"
 #import "toolkit.h"
 
-typedef void(*ui_container_add_f)(UiContainer*, NSView*);
+#import "../ui/container.h"
+
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
 
-struct UiContainer {
-    NSView* widget;
-    void   (*add)(UiContainer*, NSView*);
-    NSRect (*getframe)(UiContainer*);
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiTri        fill;
+    //UiBool       newline;
+    //char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    //int          width;
+    int          colspan;
+    int          rowspan;
 };
 
-UiContainer* ui_window_container(UiObject *obj, NSWindow *window);
+#define UI_INIT_LAYOUT(args) (UiLayout) {\
+    .fill = args.fill, \
+    .hexpand = args.hexpand, \
+    .vexpand = args.vexpand, \
+    .hfill = args.hfill, \
+    .vfill = args.vfill, \
+    .colspan = args.colspan, \
+    .rowspan = args.rowspan }
+
+
+@protocol Container
+
+@property UiLayout uilayout;
+@property const char *label;
+@property UiBool newline;
 
-NSRect ui_container_getframe(UiContainer *ct);
-void ui_container_add(UiContainer *ct, NSView *view);
+- (void) addView:(NSView*)view fill:(BOOL)fill;
+
+@end
+
+@interface BoxContainer : NSStackView<Container>
+
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing;
 
+@end
+
+
+
+
+
+UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
+
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill);
--- a/ui/cocoa/container.m	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/container.m	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,81 +26,143 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <stdio.h>
-#import <stdlib.h>
+#import "Container.h"
+#import "GridLayout.h"
+
+/* ------------------------- container classes ------------------------- */
 
-#import "container.h"
+@implementation BoxContainer
 
+@synthesize label=_label;
+@synthesize uilayout=_uilayout;
+@synthesize newline=_newline;
 
-UiContainer* ui_window_container(UiObject *obj, NSWindow *window) {
-    UiContainer *ct = ucx_mempool_malloc(
-                                         obj->ctx->mempool,
-                                         sizeof(UiContainer));
-    ct->widget = [window contentView];
-    ct->add = ui_container_add;
-    ct->getframe = ui_container_getframe;
-    return ct;
+- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing {
+    self = [super init];
+    _label = NULL;
+    _uilayout = (UiLayout){ 0 };
+    _newline = false;
+    
+    self.distribution = NSStackViewDistributionFillProportionally;
+    self.spacing = spacing;
+    
+    self.orientation = orientation;
+    if(orientation == NSUserInterfaceLayoutOrientationHorizontal) {
+        self.alignment = NSLayoutAttributeHeight;
+    } else {
+        self.alignment = NSLayoutAttributeWidth;
+    }
+    
+    
+    return self;
 }
 
-UIWIDGET ui_sidebar(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    NSRect frame = ct->getframe(ct);
+- (void) addView:(NSView*)view fill:(BOOL)fill {
+    if(_uilayout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(_uilayout.fill);
+    }
     
-    // create and add views
-    NSSplitView *splitview = [[NSSplitView alloc] initWithFrame:frame];
-    [splitview setVertical:YES];
-    [splitview setDividerStyle:NSSplitViewDividerStyleThin];
-    ct->add(ct, splitview);
+    [self addArrangedSubview:view];
     
-    NSRect lframe;
-    lframe.origin.x = 0;
-    lframe.origin.y = 0;
-    lframe.size.width = 200;
-    lframe.size.height = frame.size.height;
-    
-    NSRect rframe;
-    rframe.origin.x = 0;
-    rframe.origin.y = 0;
-    rframe.size.width = frame.size.width - 201;
-    rframe.size.height = frame.size.height;
-    
-    NSView *sidebar = [[NSView alloc]initWithFrame:lframe];
-    NSView *contentarea = [[NSView alloc]initWithFrame:rframe];
+    if(self.orientation == NSUserInterfaceLayoutOrientationHorizontal) {
+        [view.heightAnchor constraintEqualToAnchor:self.heightAnchor].active = YES;
+        if(!fill) {
+            [view.widthAnchor constraintEqualToConstant:view.intrinsicContentSize.width].active = YES;
+        }
+    } else {
+        [view.widthAnchor constraintEqualToAnchor:self.widthAnchor].active = YES;
+        if(!fill) {
+            [view.heightAnchor constraintEqualToConstant:view.intrinsicContentSize.height].active = YES;
+        }
+    }
     
-    [splitview addSubview:sidebar];
-    [splitview addSubview:contentarea];
+    // at the moment, only the fill layout option needs to be reset
+    _uilayout.fill = UI_DEFAULT;
+}
+
+@end
+
+
+
+/* -------------------- public container functions --------------------- */
+
+static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, NSUserInterfaceLayoutOrientation orientation) {
+    BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args.spacing];
+    box.translatesAutoresizingMaskIntoConstraints = false;
     
-    // add ui objects for the sidebar and contentarea
-    // the sidebar is added last, so that new views are added first to it
-    UiObject *left = uic_object_new(obj, sidebar);
-    UiContainer *ct1 = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiContainer));
-    ct1->widget = sidebar;
-    ct1->add = ui_container_add;
-    ct1->getframe = ui_container_getframe;
-    left->container = ct1;
+    // add box to the parent
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, box, &layout, TRUE);
+    
+    // add new box to the obj container chain
+    uic_object_push_container(obj, ui_create_container(obj, box));
     
-    UiObject *right = uic_object_new(obj, sidebar);
-    UiContainer *ct2 = ucx_mempool_malloc(
-            obj->ctx->mempool,
-            sizeof(UiContainer));
-    ct2->widget = contentarea;
-    ct2->add = ui_container_add;
-    ct2->getframe = ui_container_getframe;
-    right->container = ct2;
+    return (__bridge void*)box;
+}
+
+UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationVertical);
+}
+
+UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationHorizontal);
+}
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    GridLayout *grid = [[GridLayout alloc] init];
+    grid.translatesAutoresizingMaskIntoConstraints = false;
     
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
+    // add box to the parent
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, grid, &layout, TRUE);
     
-    return splitview;
+    // add new box to the obj container chain
+    uic_object_push_container(obj, ui_create_container(obj, grid));
+    
+    return (__bridge void*)grid;
 }
 
 
-NSRect ui_container_getframe(UiContainer *ct) {
-    return [ct->widget frame];
+void ui_container_begin_close(UiObject *obj) {
+    UiContainerX *ct = obj->container_end;
+    ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+    UiContainerX *ct = obj->container_end;
+    if(ct->close) {
+        ui_end_new(obj);
+        return 0;
+    }
+    return 1;
 }
 
-void ui_container_add(UiContainer *ct, NSView *view) {
-    [ct->widget addSubview: view];
+/* ------------------------- private functions ------------------------- */
+
+UiContainerX* ui_create_container(UiObject *obj, id<Container> container) {
+    UiContainerX *ctn = ui_malloc(obj->ctx, sizeof(UiContainerX));
+    ctn->container = (__bridge void*)container;
+    ctn->close = 0;
+    ctn->prev = NULL;
+    ctn->next = NULL;
+    return ctn;
 }
+
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill) {
+    UiContainerX *ctn = obj->container_end;
+    id<Container> container = (__bridge id<Container>)ctn->container;
+    container.uilayout = *layout;
+    [container addView:view fill:fill];
+}
+
+/* ---------------------- public layout functions ----------------------- */
+
+void ui_newline(UiObject *obj) {
+    UiContainerX *ctn = obj->container_end;
+    if(ctn) {
+        id<Container> container = (__bridge id<Container>)ctn->container;
+        container.newline = TRUE;
+    } else {
+        fprintf(stderr, "Error: obj has no container\n");
+    }
+}
--- a/ui/cocoa/objs.mk	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/objs.mk	Thu Dec 12 20:01:43 2024 +0100
@@ -30,16 +30,15 @@
 COCOA_OBJPRE = $(OBJ_DIR)/$(COCOA_SRC_DIR)
 
 COCOAOBJ = toolkit.o
+COCOAOBJ += AppDelegate.o
+COCOAOBJ += GridLayout.o
+COCOAOBJ += EventData.o
+COCOAOBJ += UiJob.o
+COCOAOBJ += MainWindow.o
+COCOAOBJ += WindowManager.o
 COCOAOBJ += window.o
-COCOAOBJ += menu.o
-COCOAOBJ += stock.o
-COCOAOBJ += toolbar.o
-COCOAOBJ += container.o
-COCOAOBJ += text.o
-COCOAOBJ += resource.o
-COCOAOBJ += tree.o
-COCOAOBJ += graphics.o
-
+COCOAOBJ += Container.o
+COCOAOBJ += button.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/toolkit.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/toolkit.h	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -31,58 +31,7 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-
-@interface UiApplicationDelegate : NSObject<NSApplicationDelegate> {
-    
-}
-
-- (void)applicationWillTerminate:(NSNotification*)notification;
-
-- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible;
-
-- (BOOL)application:(NSApplication *)application openFile:(NSString *)filename;
-
-@end
-
-@interface EventWrapper : NSObject {
-    void         *data;
-    ui_callback  callback;
-    int          value;
-}
-
-- (EventWrapper*) initWithData: (void*)data callback:(ui_callback) f;
-
-- (void*) data;
-- (void) setData:(void*)d;
-- (ui_callback) callback;
-- (void) setCallback: (ui_callback)f;
-- (int) intval;
-- (void) setIntval:(int)i;
+void ui_cocoa_onstartup(void);
+void ui_cocoa_onopen(const char *file);
+void ui_cocoa_onexit(void);
 
-- (BOOL)handleEvent:(id)sender;
-- (BOOL)handleStateEvent:(id)sender;
-- (BOOL)handleToggleEvent:(id)sender;
-
-@end
-
-@interface UiThread : NSObject {
-    UiObject      *obj;
-    ui_threadfunc job_func;
-    void          *job_data;
-    ui_callback   finish_callback;
-    void          *finish_data;
-}
-
-- (id) initWithObject:(UiObject*)object;
-- (void) setJobFunction:(ui_threadfunc)func;
-- (void) setJobData:(void*)data;
-- (void) setFinishCallback:(ui_callback)callback;
-- (void) setFinishData:(void*)data;
-
-- (void) start;
-- (void) runJob:(id)n;
-- (void) finish:(id)n;
-
-@end
-
-
--- a/ui/cocoa/toolkit.m	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/toolkit.m	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,321 +26,126 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <stdio.h>
-#import <stdlib.h>
-#import <sys/stat.h>
-#import <sys/types.h>
-#import <errno.h>
-
-#import "../common/context.h"
-#import "../common/document.h"
-#import "../common/properties.h"
+#import "toolkit.h"
 
-#import "toolkit.h"
-#import "window.h"
-#import "menu.h"
-#import "toolbar.h"
-#import "stock.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+#include "../common/threadpool.h"
 
-NSAutoreleasePool *pool;
+#import "AppDelegate.h"
 
-static char *application_name;
+static const char *application_name;
+
+static int app_argc;
+static const char **app_argv;
 
-static ui_callback   appclose_fnc;
-static void          *appclose_udata;
+static ui_callback   startup_func;
+static void          *startup_data;
+static ui_callback   open_func;
+void                 *open_data;
+static ui_callback   exit_func;
+void                 *exit_data;
 
-static ui_callback   openfile_fnc;
-static void          *openfile_udata;
+/* ------------------- App Init / Event Loop functions ------------------- */
 
-void ui_init(char *appname, int argc, char **argv) {
-    pool = [[NSAutoreleasePool alloc] init];
-    [NSApplication sharedApplication];
-    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+void ui_init(const char *appname, int argc, char **argv) {
+    application_name = appname;
+    app_argc = argc;
+    app_argv = (const char**)argv;
     
-    UiApplicationDelegate *delegate = [[UiApplicationDelegate alloc]init];
-    [NSApp setDelegate: delegate];
-    
-    
+    uic_init_global_context();
+
     uic_docmgr_init();
-    ui_menu_init();
-    ui_toolbar_init();
-    ui_stock_init();
-    
+    uic_menu_init();
+    uic_toolbar_init();
+
     uic_load_app_properties();
+
+    [NSApplication sharedApplication];
+    //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ];
+    //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects];
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
-void ui_exitfunc(ui_callback f, void *userdata) {
-    appclose_fnc = f;
-    appclose_udata = userdata;
-}
-
-void ui_openfilefunc(ui_callback f, void *userdata) {
-    openfile_fnc = f;
-    openfile_udata = userdata;
+void ui_onstartup(ui_callback f, void *userdata) {
+    startup_func = f;
+    startup_data = userdata;
 }
 
-void ui_show(UiObject *obj) {
-    uic_check_group_widgets(obj->ctx);
-    if([obj->widget class] == [UiCocoaWindow class]) {
-        UiCocoaWindow *window = (UiCocoaWindow*)obj->widget;
-        [window makeKeyAndOrderFront:nil];
-    } else {
-        printf("Error: ui_show: Object is not a Window!\n");
-    }
-}
-
-void ui_set_show_all(UIWIDGET widget, int value) {
-    // TODO
-}
-
-void ui_set_visible(UIWIDGET widget, int visible) {
-    // TODO
-}
-
-void ui_set_enabled(UIWIDGET widget, int enabled) {
-    [(id)widget setEnabled: enabled];
+void ui_onopen(ui_callback f, void *userdata) {
+    open_func = f;
+    open_data = userdata;
 }
 
-
-
-void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
-    UiThread *thread = [[UiThread alloc]initWithObject:obj];
-    [thread setJobFunction:tf];
-    [thread setJobData:td];
-    [thread setFinishCallback:f];
-    [thread setFinishData:fd];
-    [thread start];
-}
-
-void ui_main() {
-    [NSApp run];
-    [pool release];
+void ui_onexit(ui_callback f, void *userdata) {
+    exit_func = f;
+    exit_data = userdata;
 }
 
-
-void ui_clipboard_set(char *str) {
-    NSString *string = [[NSString alloc] initWithUTF8String:str];
-    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
-    [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
-    [pasteBoard setString:string forType:NSStringPboardType];
-}
-
-char* ui_clipboard_get() {
-    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
-    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];
-    NSDictionary *options = [NSDictionary dictionary];
-    NSArray *data = [pasteBoard readObjectsForClasses:classes options:options];
-    
-    if(data != nil) {
-        NSString *str = [data componentsJoinedByString: @""];
-        
-        // copy C string
-        size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-        const char *cstr = [str UTF8String];
-        char *value = malloc(length + 1);
-        memcpy(value, cstr, length);
-        value[length] = '\0';
-        
-        return value;
-    } else {
-        return NULL;
+void ui_cocoa_onstartup(void) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(startup_func) {
+        startup_func(&e, startup_data);
     }
 }
 
-
-@implementation UiApplicationDelegate
-
-- (void)applicationWillTerminate:(NSNotification*)notification {
-    printf("terminate\n");
-}
-
-- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible; {
-    if(!visible) {
-        printf("reopen\n");
-    }
-    return NO;
-}
-
-- (BOOL)application:(NSApplication*)application openFile:(NSString*)filename {
-    if(openfile_fnc) {
-        UiEvent event;
-        event.obj = NULL;
-        event.document = NULL;
-        event.window = NULL;
-        event.eventdata = (void*)[filename UTF8String];
-        event.intval = 0;
-        openfile_fnc(&event, openfile_udata);
+void ui_cocoa_onopen(const char *file) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(open_func) {
+        open_func(&e, open_data);
     }
-    
-    return NO;
-}
-
-@end
-
-
-@implementation EventWrapper
-
-- (EventWrapper*) initWithData: (void*)d callback:(ui_callback) f {
-    data = d;
-    callback = f;
-    value = 0;
-    return self;
-}
-
-
-- (void*) data {
-    return data;
-}
-
-- (void) setData:(void*)d {
-    data = d;
-}
-
-
-- (ui_callback) callback {
-    return callback;
-}
-
-- (void) setCallback: (ui_callback)f {
-    callback = f;
-}
-
-- (int) intval {
-    return value;
-}
-
-- (void) setIntval:(int)i {
-    value = i;
-}
-
-
-- (BOOL)handleEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    
-    UiEvent event;
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-        event.intval = value;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    
-    return true;
 }
 
-- (BOOL)handleStateEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    int state = [sender state] ? NSOffState : NSOnState;
-    
-    UiEvent event;
-    event.intval = state;
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-        // if the sender is a menu item, we have to save the state for this
-        // window
-        UiMenuItem *wmi = [(UiCocoaWindow*)activeWindow getMenuItem: sender];
-        if(wmi) {
-            // update state in window data
-            wmi->state = state;
-        }
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    [sender setState: state];
-    
-    return true;
-}
-
-- (BOOL)handleToggleEvent:(id)sender {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    
-    UiEvent event;
-    event.intval = [sender state];
-    event.eventdata = NULL;
-    if([activeWindow class] == [UiCocoaWindow class]) {
-        event.obj = [(UiCocoaWindow*)activeWindow object];
-        event.window = event.obj->window;
-        event.document = event.obj->ctx->document;
-    } else {
-        event.window = NULL;
-        event.document = NULL;
-    }
-    if(callback) {
-        callback(&event, data);
-    }
-    
-    return true;
-}
-
-@end
-
-@implementation UiThread
-
-- (id) initWithObject:(UiObject*)object {
-    obj = object;
-    job_func = NULL;
-    job_data = NULL;
-    finish_callback = NULL;
-    finish_data = NULL;
-    return self;
-}
-
-- (void) setJobFunction:(ui_threadfunc)func {
-    job_func = func;
-}
-
-- (void) setJobData:(void*)data {
-    job_data = data;
-}
-
-- (void) setFinishCallback:(ui_callback)callback {
-    finish_callback = callback;
-}
-
-- (void) setFinishData:(void*)data {
-    finish_data = data;
-}
-
-- (void) start {
-    [NSThread detachNewThreadSelector:@selector(runJob:)
-                             toTarget:self
-                           withObject:nil];
-}
-
-- (void) runJob:(id)n {
-    int result = job_func(job_data);
-    if(!result) {
-        [self performSelectorOnMainThread:@selector(finish:)
-                               withObject:nil
-                            waitUntilDone:NO];
+void ui_cocoa_onexit(void) {
+    UiEvent e;
+    e.obj = NULL;
+    e.window = NULL;
+    e.document = NULL;
+    e.eventdata = NULL;
+    e.intval = 0;
+    if(exit_func) {
+        exit_func(&e, exit_data);
     }
 }
 
-- (void) finish:(id)n {
-    UiEvent event;
-    event.obj = obj;
-    event.window = obj->window;
-    event.document = obj->ctx->document;
-    event.eventdata = NULL;
-    event.intval = 0;
-    finish_callback(&event, finish_data);
+void ui_main(void) {
+    NSApplicationMain(app_argc, app_argv);
+}
+
+/* ------------------- Window Visibility functions ------------------- */
+
+void ui_show(UiObject *obj) {
+    if(obj->wobj) {
+        NSWindow *window = (__bridge NSWindow*)obj->wobj;
+        [window makeKeyAndOrderFront:nil];
+    }
 }
 
-@end
+void ui_close(UiObject *obj) {
+
+}
+
+/* ------------------- Job Control / Threadpool functions ------------------- */
 
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
 
+}
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+
+}
--- a/ui/cocoa/window.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/window.h	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,27 +26,4 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <Cocoa/Cocoa.h>
-#import "../ui/window.h"
-#import <ucx/list.h>
-#import <ucx/map.h>
-
-#import "menu.h"
-
-
-
-@interface UiCocoaWindow : NSWindow {
-    UiObject *uiobj;
-    UcxMap   *menus; // key: NSMenu value: UcxList of UiMenuItem
-    UcxMap   *items; // key: NSMenuItem value: UiMenuItem
-}
-
-- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj;
-- (UiObject*) object;
-- (void) setObject:(UiObject*)obj;
-- (void) setMenuItems:(UcxList*)menuItems;
-- (void) setMenuItemLists:(UcxList*)itemLists;
-- (UiMenuItem*) getMenuItem:(NSMenuItem*)item;
-- (void) updateMenu:(NSMenu*)menu;
-
-@end
+#import "toolkit.h"
\ No newline at end of file
--- a/ui/cocoa/window.m	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/cocoa/window.m	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2012 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,195 +26,43 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#import "window.h"
 
-#import "window.h"
-#import "menu.h"
-#import "toolbar.h"
-#import "container.h"
-#import <ucx/mempool.h>
-#import "../common/context.h"
-
-static int window_default_width = 600;
-static int window_default_height = 500;
-
-@implementation UiCocoaWindow
+#import "MainWindow.h"
+#import "WindowManager.h"
 
-- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj {
-    self = [self initWithContentRect:frame
-                           styleMask:NSTitledWindowMask |
-                                     NSResizableWindowMask |
-                                     NSClosableWindowMask |
-                                     NSMiniaturizableWindowMask
-                             backing:NSBackingStoreBuffered
-                               defer:false];
-    
-    uiobj = obj;
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
-    menus = ucx_map_new_a(allocator, 8);
-    items = ucx_map_new_a(allocator, 64);
-    
-    return self;
-}
+#include "../ui/window.h"
+#include "../ui/properties.h"
+#include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
 
-- (UiObject*) object {
-    return uiobj;
-}
+#include <cx/mempool.h>
 
-- (void)  setObject:(UiObject*)obj {
-    uiobj = obj;
-}
-
-- (void) setMenuItems:(UcxList*)menuItems {
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+static UiObject* create_window(const char *title, BOOL simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+    obj->ref = 0;
     
-    UCX_FOREACH(elm, menuItems) {
-        UiStateItem *item = elm->data;
-        NSMenu *menu = [item->item menu];
-        
-        // create UiMenuItem which represents an NSMenuItem for a Window
-        UiMenuItem *windowItem = ucx_mempool_malloc(uiobj->ctx->mempool, sizeof(UiMenuItem));
-        windowItem->item = item->item;
-        windowItem->state = 0;
-        if(item->var) {
-            // bind value
-            UiVar *var = uic_connect_var(uiobj->ctx, item->var, UI_VAR_INTEGER);
-            if(var) {
-                UiInteger *value = var->value;
-                value->obj = windowItem;
-                value->get = ui_menuitem_get;
-                value->set = ui_menuitem_set;
-                value = 0;
-            } else {
-                // TODO: error
-            }
-        }
-        
-        // add item
-        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
-        abstractItem->update = ui_update_item;
-        abstractItem->item_data = windowItem;
-        UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
-        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
-        ucx_map_put(menus, ucx_key(&menu, sizeof(void*)), itemList);
-        
-        ucx_map_put(items, ucx_key(&windowItem->item, sizeof(void*)), windowItem);
-    }
-}
-
-- (void) setMenuItemLists:(UcxList*)itemLists {
-    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
-    
-    UCX_FOREACH(elm, itemLists) {
-        UiMenuItemList *list = elm->data;
-        
-        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
-        abstractItem->update = ui_update_item_list;
-        abstractItem->item_data = list;
-        
-        UcxList *itemList = ucx_map_get(menus, ucx_key(&list->menu, sizeof(void*)));
-        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
-        ucx_map_put(menus, ucx_key(&list->menu, sizeof(void*)), itemList);
-        
-    }
-}
-
-- (UiMenuItem*) getMenuItem:(NSMenuItem*)item {
-    return ucx_map_get(items, ucx_key(&item, sizeof(void*)));
-}
-
-- (void) updateMenu:(NSMenu*)menu {
-    UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
-    UCX_FOREACH(elm, itemList) {
-        UiAbstractMenuItem *item = elm->data;
-        item->update(self, item->item_data);
-    }
-    
-    // update group items
-    // TODO: use only one loop for all items
-    int ngroups = 0;
-    int *groups = ui_active_groups(uiobj->ctx, &ngroups);
-    
-    NSArray *groupItems = [menu itemArray];
-    int count = [groupItems count];
-    for(int i=0;i<count;i++) {
-        id item = [groupItems objectAtIndex:i];
-        if([item class] == [UiGroupMenuItem class]) {
-            [item checkGroups: groups count:ngroups];
-        }
-    }
-    free(groups);
-}
-
-@end
-
-
-/* ------------------------------ public API ------------------------------ */
-
-UiObject* ui_window(char *title, void *window_data) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
     
-    // create native window
-    NSRect frame = NSMakeRect(
-                              300,
-                              200,
-                              window_default_width,
-                              window_default_height);
+    MainWindow *window = [[MainWindow alloc] init:obj];
+    [[WindowManager sharedWindowManager] addWindow:window];
+    window.releasedWhenClosed = false;
     
-    /*
-    UiCocoaWindow *window = [[UiCocoaWindow alloc] initWithContentRect:frame
-                                styleMask:NSTitledWindowMask | NSResizableWindowMask |
-                                NSClosableWindowMask | NSMiniaturizableWindowMask
-                                backing:NSBackingStoreBuffered
-                                defer:false];
-    */
-    UiCocoaWindow *window = [[UiCocoaWindow alloc] init:frame object:obj];
-    
-    NSString *titleStr = [[NSString alloc] initWithUTF8String:title];
-    [window setTitle:titleStr];
-    
-    UiMenuDelegate *menuDelegate = ui_menu_delegate();
-    [window setMenuItems: [menuDelegate items]];
-    [window setMenuItemLists: [menuDelegate lists]];
-    
-    NSToolbar *toolbar = ui_create_toolbar(obj);
-    [window setToolbar: toolbar];
-    
-    obj->widget = (NSView*)window;
-    obj->window = window_data;
-    obj->container = ui_window_container(obj, window);
-    
+    obj->wobj = (__bridge void*)window;
     
     return obj;
 }
 
-void ui_close(UiObject *obj) {
-    // TODO
+UiObject* ui_window(const char *title, void *window_data) {
+    UiObject *obj = create_window(title, FALSE);
+    obj->window = window_data;
+    return obj;
 }
 
-char* ui_openfiledialog(UiObject *obj) {
-    NSOpenPanel* op = [NSOpenPanel openPanel];
-    if ([op runModal] == NSOKButton) {
-        NSArray *urls = [op URLs];
-        NSURL *url = [urls objectAtIndex:0];
-        
-        const char *str = [[url path] UTF8String];
-        return (char*)strdup(str);
-    }
-    return NULL;
+UiObject* ui_simple_window(const char *title, void *window_data) {
+    UiObject *obj = create_window(title, TRUE);
+    obj->window = window_data;
+    return obj;
 }
-
-char* ui_savefiledialog(UiObject *obj) {
-    NSSavePanel* sp = [NSSavePanel savePanel];
-    if ([sp runModal] == NSOKButton) {
-        NSURL *url = [sp URL];
-        
-        const char *str = [[url path] UTF8String];
-        return (char*)strdup(str);
-    }
-    return NULL;
-}
--- a/ui/common/condvar.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/condvar.c	Thu Dec 12 20:01:43 2024 +0100
@@ -28,7 +28,7 @@
 
 #include "condvar.h"
 
-
+#include <stdlib.h>
 
 UiCondVar* ui_condvar_create(void) {
     UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar));
--- a/ui/common/context.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/context.c	Thu Dec 12 20:01:43 2024 +0100
@@ -187,6 +187,13 @@
 }
 
 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) {
--- a/ui/common/menu.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/menu.c	Thu Dec 12 20:01:43 2024 +0100
@@ -30,6 +30,7 @@
 
 #include <stdarg.h>
 #include <string.h>
+#include <stdio.h>
 
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
--- a/ui/common/object.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/object.c	Thu Dec 12 20:01:43 2024 +0100
@@ -32,6 +32,8 @@
 #include "object.h"
 #include "context.h"
 
+#include "../ui/container.h"
+
 void ui_end(UiObject *obj) {
     if(!obj->next) {
         return;
@@ -49,20 +51,30 @@
     }
 }
 
+void ui_end_new(UiObject *obj) {
+    if(!obj->container_end) {
+        return;
+    }
+    UiContainerX *rm = obj->container_end;
+    uic_object_pop_container(obj);
+    ui_free(obj->ctx, rm);
+}
+
 void ui_object_ref(UiObject *obj) {
     obj->ref++;
 }
 
-void ui_object_unref(UiObject *obj) {
+int ui_object_unref(UiObject *obj) {
     // it is possible to have 0 references, in case
     // a window was created but ui_show was never called
     if(obj->ref == 0 || --obj->ref == 0) {
         if(obj->destroy) {
             obj->destroy(obj);
-        } else {
-            uic_object_destroy(obj);
         }
+        uic_object_destroy(obj);
+        return 0;
     }
+    return 1;
 }
 
 void uic_object_destroy(UiObject *obj) {
@@ -109,3 +121,23 @@
 UiContainer* uic_get_current_container(UiObject *obj) {
     return uic_current_obj(obj)->container;
 }
+
+void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer) {
+    newcontainer->prev = toplevel->container_end;
+    if(toplevel->container_end) {
+        toplevel->container_end->next = newcontainer;
+        toplevel->container_end = newcontainer;
+    } else {
+        toplevel->container_begin = newcontainer;
+        toplevel->container_end = newcontainer;
+    }
+}
+
+void uic_object_pop_container(UiObject *toplevel) {
+    toplevel->container_end = toplevel->container_end->prev;
+    if(toplevel->container_end) {
+        toplevel->container_end->next = NULL;
+    } else {
+        toplevel->container_begin = NULL;
+    }
+}
--- a/ui/common/object.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/object.h	Thu Dec 12 20:01:43 2024 +0100
@@ -42,7 +42,11 @@
 void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
 UiObject* uic_current_obj(UiObject *toplevel);
 
-UiContainer* uic_get_current_container(UiObject *obj);;
+UiContainer* uic_get_current_container(UiObject *obj); // deprecated
+
+void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer);
+void uic_object_pop_container(UiObject *toplevel);
+
 
 
 #ifdef	__cplusplus
--- a/ui/common/threadpool.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/threadpool.c	Thu Dec 12 20:01:43 2024 +0100
@@ -29,10 +29,12 @@
 #include "threadpool.h"
 #include "context.h"
 
-#include <pthread.h>
-
 #ifndef _WIN32
 
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
 
 static threadpool_job kill_job;
 
--- a/ui/common/threadpool.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/common/threadpool.h	Thu Dec 12 20:01:43 2024 +0100
@@ -31,6 +31,10 @@
 
 #include "../ui/toolkit.h"
 
+#ifndef _WIN32
+#include <pthread.h>
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
--- a/ui/gtk/button.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/button.c	Thu Dec 12 20:01:43 2024 +0100
@@ -398,12 +398,14 @@
 } UiRadioButtonData;
 
 static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) {
-    ui_destroy_vardata(w, data->eventdata);
     if(data->first) {
+        ui_destroy_vardata(w, data->eventdata);
         g_slist_free(data->value->obj);
         data->value->obj = NULL;
         data->value->get = NULL;
         data->value->set = NULL;
+    } else {
+        free(data->eventdata);
     }
     free(data);
 }
--- a/ui/gtk/container.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/container.c	Thu Dec 12 20:01:43 2024 +0100
@@ -891,6 +891,42 @@
 
 #endif
 
+/* -------------------- Sidebar -------------------- */
+
+#ifdef UI_LIBADWAITA
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
+    GtkWidget *sidebar_toolbar_view = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
+    if(!sidebar_toolbar_view) {
+        fprintf(stderr, "Error: window is not configured for sidebar\n");
+        return NULL;
+    }
+    
+    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
+    ui_box_set_margin(box, args.margin);
+    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+#else
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
+    GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
+    
+    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
+    ui_box_set_margin(box, args.margin);
+    BOX_ADD_EXPAND(sidebar_vbox, box);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+#endif
+
 /* -------------------- Splitpane -------------------- */
 
 static GtkWidget* create_paned(UiOrientation orientation) {
--- a/ui/gtk/list.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/list.c	Thu Dec 12 20:01:43 2024 +0100
@@ -36,6 +36,7 @@
 #include "container.h"
 
 #include <cx/array_list.h>
+#include <cx/linked_list.h>
 
 #include "list.h"
 #include "icon.h"
@@ -982,3 +983,296 @@
         gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
     }
 }
+
+
+/* ------------------------------ Source List ------------------------------ */
+
+static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) {
+    cxListDestroy(v->sublists);
+    free(v);
+}
+
+static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) {
+    free(sublist->header);
+    ui_destroy_boundvar(obj->ctx, sublist->var);
+    cxListDestroy(sublist->widgets);
+}
+
+static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {
+    // first rows in sublists have the ui_listbox property
+    UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox");
+    if(!listbox) {
+        return;
+    }
+    
+    UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist");
+    if(!sublist) {
+        return;
+    }
+    
+    if(sublist->separator) {
+        GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        gtk_list_box_row_set_header(row, separator);
+    } else if(sublist->header) {
+        GtkWidget *header = gtk_label_new(sublist->header);
+        gtk_widget_set_halign(header, GTK_ALIGN_START);
+        if(row == listbox->first_row) {
+            WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first");
+        } else {
+            WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header");
+        }
+        gtk_list_box_row_set_header(row, header);
+    }
+} 
+
+#ifdef UI_GTK3
+typedef struct _UiSidebarListBoxClass {
+    GtkListBoxClass parent_class; 
+} UiSidebarListBoxClass;
+
+typedef struct _UiSidebarListBox {
+    GtkListBox parent_instance;
+} UiSidebarListBox;
+
+G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX)
+
+/* Initialize the instance */
+static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) {
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+    gtk_widget_class_set_css_name (widget_class, "placessidebar");
+}
+
+static void ui_sidebar_list_box_init(UiSidebarListBox *self) {
+    
+}
+#endif
+
+UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+#ifdef UI_GTK3
+    GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL);
+#else
+    GtkWidget *listbox = gtk_list_box_new();
+#endif
+    if(!args.style_class) {
+#if GTK_MAJOR_VERSION >= 4
+        WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar");
+#else
+        WIDGET_ADD_CSS_CLASS(listbox, "sidebar");
+#endif
+    }
+    gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox);
+    
+    ui_set_name_and_style(listbox, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, listbox, args.groups);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, TRUE);
+    
+    UiListBox *uilistbox = malloc(sizeof(UiListBox));
+    uilistbox->obj = obj;
+    uilistbox->listbox = GTK_LIST_BOX(listbox);
+    uilistbox->getvalue = args.getvalue;
+    uilistbox->onactivate = args.onactivate;
+    uilistbox->onactivatedata = args.onactivatedata;
+    uilistbox->onbuttonclick = args.onbuttonclick;
+    uilistbox->onbuttonclickdata = args.onbuttonclickdata;
+    uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4);
+    uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy;
+    uilistbox->sublists->collection.destructor_data = obj;
+    uilistbox->first_row = NULL;
+    
+    if(args.numsublists == 0 && args.sublists) {
+        args.numsublists = INT_MAX;
+    }
+    for(int i=0;i<args.numsublists;i++) {
+        UiSubList sublist = args.sublists[i];
+        if(!sublist.varname && !sublist.value) {
+            break;
+        }
+        
+        UiListBoxSubList uisublist;
+        uisublist.var = uic_widget_var(
+                obj->ctx,
+                current->ctx,
+                sublist.value,
+                sublist.varname,
+                UI_VAR_LIST);
+        uisublist.numitems = 0;
+        uisublist.header = sublist.header ? strdup(sublist.header) : NULL;
+        uisublist.separator = sublist.separator;
+        uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        uisublist.listbox = uilistbox;
+        uisublist.userdata = sublist.userdata;
+        uisublist.index = i;
+        
+        cxListAdd(uilistbox->sublists, &uisublist);
+        
+        // bind UiList
+        UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1);
+        UiList *list = uisublist.var->value;
+        if(list) {
+            list->obj = sublist_ptr;
+            list->update = ui_listbox_list_update;
+        }
+    }
+    // fill items
+    ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists));
+    
+    // register uilistbox for both widgets, so it doesn't matter which
+    // widget is used later
+    g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox);
+    g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox);
+    
+    // signals
+    g_signal_connect(
+                listbox,
+                "destroy",
+                G_CALLBACK(ui_destroy_sourcelist),
+                uilistbox);
+    
+    if(args.onactivate) {
+        g_signal_connect(
+                listbox,
+                "row-activated",
+                G_CALLBACK(ui_listbox_row_activate),
+                NULL);
+    }
+    
+    return scroll_area;
+}
+
+void ui_listbox_update(UiListBox *listbox, int from, int to) {
+    CxIterator i = cxListIterator(listbox->sublists);
+    size_t pos = 0;
+    cx_foreach(UiListBoxSubList *, sublist, i) {
+        if(i.index < from) {
+            pos += sublist->numitems;
+            continue;
+        }
+        if(i.index > to) {
+            break;
+        }
+        
+        // reload sublist
+        ui_listbox_update_sublist(listbox, sublist, pos);
+        pos += sublist->numitems;
+    }
+}
+
+static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
+    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
+    if(item->icon) {
+        GtkWidget *icon = ICON_IMAGE(item->icon);
+        BOX_ADD(hbox, icon);
+    }
+    GtkWidget *label = gtk_label_new(item->label);
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    BOX_ADD_EXPAND(hbox, label);
+    // TODO: badge, button
+    GtkWidget *row = gtk_list_box_row_new();
+    LISTBOX_ROW_SET_CHILD(row, hbox);
+    
+    // signals 
+    UiEventDataExt *event = malloc(sizeof(UiEventDataExt));
+    memset(event, 0, sizeof(UiEventDataExt));
+    event->obj = listbox->obj;
+    event->customdata0 = sublist;
+    event->customdata1 = sublist->var;
+    event->customdata2 = item->eventdata;
+    event->callback = listbox->onactivate;
+    event->userdata = listbox->onactivatedata;
+    event->callback2 = listbox->onbuttonclick;
+    event->userdata2 = listbox->onbuttonclickdata;
+    event->value0 = index;
+    
+    g_signal_connect(
+            row,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event);
+    
+    return row;
+}
+
+void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) {
+    // clear sublist
+    CxIterator r = cxListIterator(sublist->widgets);
+    cx_foreach(GtkWidget*, widget, r) {
+        LISTBOX_REMOVE(listbox->listbox, widget);
+    }
+    cxListClear(sublist->widgets);
+    
+    sublist->numitems = 0;
+    
+    // create items for each UiList element
+    UiList *list = sublist->var->value;
+    if(!list) {
+        return;
+    }
+    
+    size_t index = 0;
+    void *elm = list->first(list);
+    while(elm) {
+        UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
+        listbox->getvalue(sublist->userdata, elm, index, &item);
+        
+        // create listbox item
+        GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index);
+        if(index == 0) {
+            // first row in the sublist, set ui_listbox data to the row
+            // which is then used by the headerfunc
+            g_object_set_data(G_OBJECT(row), "ui_listbox", listbox);
+            g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist);
+            
+            if(listbox_insert_index == 0) {
+                // first row in the GtkListBox
+                listbox->first_row = GTK_LIST_BOX_ROW(row);
+            }
+        }
+        intptr_t rowindex = listbox_insert_index + index;
+        g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex);
+        gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index);
+        cxListAdd(sublist->widgets, row);
+        
+        // cleanup
+        free(item.label);
+        free(item.icon);
+        free(item.button_label);
+        free(item.button_icon);
+        free(item.badge);
+        
+        // next row
+        elm = list->next(list);
+        index++;
+    }
+    
+    sublist->numitems = cxListSize(sublist->widgets);
+}
+
+void ui_listbox_list_update(UiList *list, int i) {
+    UiListBoxSubList *sublist = list->obj;
+}
+
+void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
+    UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata");
+    if(!data) {
+        return;
+    }
+    UiListBoxSubList *sublist = data->customdata0;
+    
+    UiEvent event;
+    event.obj = data->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = data->customdata2;
+    event.intval = data->value0;
+    
+    if(data->callback) {
+        data->callback(&event, data->userdata);
+    }
+}
--- a/ui/gtk/list.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/list.h	Thu Dec 12 20:01:43 2024 +0100
@@ -32,6 +32,8 @@
 #include "../ui/tree.h"
 #include "toolkit.h"
 
+#include <cx/array_list.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -57,7 +59,33 @@
     void        *activatedata;
     void        *selectiondata;
 } UiTreeEventData;
+
+typedef struct UiListBox UiListBox;
+
+typedef struct UiListBoxSubList {
+    UiVar       *var;
+    size_t      numitems;
+    char        *header;
+    UiBool      separator;
+    CxList      *widgets;
+    UiListBox   *listbox;
+    void        *userdata;
+    size_t      index;
+} UiListBoxSubList;
+
+struct UiListBox {
+    UiObject                 *obj;
+    GtkListBox               *listbox;
+    CxList                   *sublists; // contains UiListBoxSubList elements
+    ui_sublist_getvalue_func getvalue;
+    ui_callback              onactivate;
+    void                     *onactivatedata;
+    ui_callback              onbuttonclick;
+    void                     *onbuttonclickdata;
     
+    GtkListBoxRow            *first_row;
+};
+
 void* ui_strmodel_getvalue(void *elm, int column);
 
 UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
@@ -94,6 +122,12 @@
 void ui_combobox_modelupdate(UiList *list, int i);
 UiListSelection ui_combobox_getselection(UiList *list);
 void ui_combobox_setselection(UiList *list, UiListSelection selection);
+
+void ui_listbox_update(UiListBox *listbox, int from, int to);
+void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index);
+void ui_listbox_list_update(UiList *list, int i);
+
+void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data);
         
 #ifdef	__cplusplus
 }
--- a/ui/gtk/text.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/text.c	Thu Dec 12 20:01:43 2024 +0100
@@ -551,6 +551,8 @@
     uitext->var = var;
     uitext->onchange = args.onchange;
     uitext->onchangedata = args.onchangedata;
+    uitext->onactivate = args.onactivate;
+    uitext->onactivatedata = args.onactivatedata;
     
     g_signal_connect(
                 textfield,
@@ -599,6 +601,14 @@
                 uitext);
     }
     
+    if(args.onactivate) {
+        g_signal_connect(
+                textfield,
+                "activate",
+                G_CALLBACK(ui_textfield_activate),
+                uitext);
+    }
+    
     return textfield;
 }
 
@@ -638,6 +648,17 @@
     }
 }
 
+void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) {
+    if(textfield->onactivate) {
+        UiEvent e;
+        e.obj = textfield->obj;
+        e.window = e.obj->window;
+        e.document = textfield->obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        textfield->onactivate(&e, textfield->onactivatedata);
+    }
+}
 
 char* ui_textfield_get(UiString *str) {
     if(str->value.ptr) {
--- a/ui/gtk/text.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/text.h	Thu Dec 12 20:01:43 2024 +0100
@@ -74,6 +74,8 @@
     UiVar       *var;
     ui_callback onchange;
     void        *onchangedata;
+    ui_callback onactivate;
+    void        *onactivatedata;
 } UiTextField;
 
 typedef struct UiPathTextField {
@@ -140,6 +142,7 @@
 
 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield);
 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield);
+void ui_textfield_activate(GtkEntry* self, UiTextField *textfield);
 
 char* ui_textfield_get(UiString *str);
 void ui_textfield_set(UiString *str, const char *value);
--- a/ui/gtk/toolkit.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/toolkit.c	Thu Dec 12 20:01:43 2024 +0100
@@ -300,6 +300,10 @@
     free(data);
 }
 
+void ui_destroy_widget_var(GtkWidget *object, UiVar *var) {
+    ui_destroy_boundvar(NULL, var);
+}
+
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
     uic_unbind_var(var);
     
@@ -362,6 +366,18 @@
 ".ui_label_title {\n"
 "  font-weight: bold;\n"
 "}\n"
+".ui-listbox-header {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 12px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+".ui-listbox-header-first {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 4px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
 ;
 
 #elif GTK_MAJOR_VERSION == 3
@@ -380,6 +396,21 @@
 ".ui_label_title {\n"
 "  font-weight: bold;\n"
 "}\n"
+"placessidebar row {\n"
+"  padding-left: 10px;\n"
+"}\n"
+".ui-listbox-header {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 12px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
+".ui-listbox-header-first {\n"
+"  font-weight: bold;\n"
+"  margin-left: 10px;\n"
+"  margin-top: 4px;\n"
+"  margin-bottom: 10px;\n"
+"}\n"
 ;
 #endif
 
--- a/ui/gtk/toolkit.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/toolkit.h	Thu Dec 12 20:01:43 2024 +0100
@@ -40,7 +40,7 @@
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
   
-#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 74
+#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 2074
 #define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS
 #else
 #define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE
@@ -70,6 +70,9 @@
 #define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child)
 #define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass)
 #define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass)
+#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon)
+#define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row)
+#define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child)
 #else
 #define WINDOW_SHOW(window) gtk_widget_show_all(window)
 #define WINDOW_DESTROY(window) gtk_widget_destroy(window)
@@ -86,6 +89,9 @@
 #define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child)
 #define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass)
 #define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass)
+#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON)
+#define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row)
+#define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child)
 #endif
     
 #ifdef UI_GTK2
@@ -168,6 +174,7 @@
 
 void ui_destroy_userdata(GtkWidget *object, void *userdata);
 void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_widget_var(GtkWidget *object, UiVar *var);
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
 
 void ui_set_active_window(UiObject *obj);
--- a/ui/gtk/window.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/gtk/window.c	Thu Dec 12 20:01:43 2024 +0100
@@ -101,7 +101,7 @@
 }
 #endif
 
-static UiObject* create_window(const char *title, void *window_data, UiBool simple) {
+static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) {
     CxMempool *mp = cxBasicMempoolCreate(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
     obj->ref = 0;
@@ -136,7 +136,7 @@
     } else {
         gtk_window_set_default_size(
                 GTK_WINDOW(obj->widget),
-                window_default_width,
+                window_default_width + sidebar*250,
                 window_default_height);
     }
     
@@ -163,8 +163,27 @@
     GtkWidget *vbox = ui_gtk_vbox_new(0);
 #ifdef UI_LIBADWAITA
     GtkWidget *toolbar_view = adw_toolbar_view_new();
-    adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
     adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
+    
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    
+    if(sidebar) {
+        GtkWidget *splitview = adw_overlay_split_view_new();
+        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview);
+        
+        GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new();
+        adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view);
+        GtkWidget *sidebar_headerbar = adw_header_bar_new();
+        adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar);
+        
+        adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view);
+        
+        g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view);
+    } else {
+        adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+    }
+    
 
     GtkWidget *headerbar = adw_header_bar_new();
     adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
@@ -174,10 +193,19 @@
         ui_fill_headerbar(obj, headerbar);
     }
 #elif GTK_MAJOR_VERSION >= 4
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
     WINDOW_SET_CONTENT(obj->widget, vbox);
+    if(sidebar) {
+        GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0);
+        gtk_paned_set_start_child(GTK_PANED(paned), sidebar_vbox);
+        gtk_paned_set_end_child(GTK_PANED(paned), content_box);
+        BOX_ADD_EXPAND(GTK_BOX(vbox), paned);
+        g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox);
+    } else {
+        BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    }
 #else
-    gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
-    
     if(!simple) {
         // menu
         if(uic_get_menu_list()) {
@@ -198,6 +226,21 @@
         //GtkWidget *hb = ui_create_headerbar(obj);
         //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb);
     }
+    
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    WINDOW_SET_CONTENT(obj->widget, vbox);
+    if(sidebar) {
+        GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0);
+        gtk_paned_add1(GTK_PANED(paned), sidebar_vbox);
+        gtk_paned_add2(GTK_PANED(paned), content_box);
+        BOX_ADD_EXPAND(GTK_BOX(vbox), paned);
+        g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox);
+        gtk_paned_set_position (GTK_PANED(paned), 200);
+    } else {
+        BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    }
+    
 #endif
     
     // window content
@@ -213,8 +256,6 @@
     gtk_container_add(GTK_CONTAINER(frame), content_box);
     obj->container = ui_box_container(obj, content_box);
     */
-    GtkWidget *content_box = ui_gtk_vbox_new(0);
-    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
     obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
     
     nwindows++;
@@ -223,11 +264,15 @@
 
 
 UiObject* ui_window(const char *title, void *window_data) {
-    return create_window(title, window_data, FALSE);
+    return create_window(title, window_data, FALSE, FALSE);
+}
+
+UiObject *ui_sidebar_window(const char *title, void *window_data) {
+    return create_window(title, window_data, TRUE, FALSE);
 }
 
 UiObject* ui_simple_window(const char *title, void *window_data) {
-    return create_window(title, window_data, TRUE);
+    return create_window(title, window_data, FALSE, TRUE);
 }
 
 void ui_window_size(UiObject *obj, int width, int height) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/Grid.c	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,582 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * 
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "Grid.h"
+
+#include <X11/Xlib.h>
+
+
+
+static XtActionsRec actionslist[] = {
+  {"getfocus",grid_getfocus},
+  {"loosefocus",grid_loosefocus},
+  {"NULL",NULL}
+};
+
+//static char defaultTranslations[] = "<BtnDown>: mousedown()\n";
+static char defaultTranslations[] = "\
+<EnterWindow>:		getfocus()\n\
+<LeaveWindow>:          loosefocus()\n";
+
+
+///*
+static XtResource constraints[] =
+{
+    {
+        gridColumn,
+        gridColumn,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.x),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridRow,
+        gridRow,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.y),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridColspan,
+        gridColspan,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.colspan),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridRowspan,
+        gridRowspan,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.rowspan),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginLeft,
+        gridMarginLeft,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_left),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginRight,
+        gridMarginRight,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_right),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginTop,
+        gridMarginTop,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_top),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridMarginBottom,
+        gridMarginBottom,
+        XmRDimension,
+        sizeof (Dimension),
+        XtOffsetOf( GridConstraintRec,
+                   grid.margin_bottom),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridHExpand,
+        gridHExpand,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.hexpand),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridVExpand,
+        gridVExpand,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.vexpand),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridHFill,
+        gridHFill,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.hfill),
+        XmRImmediate,
+        (XtPointer) 0
+    },
+    {
+        gridVFill,
+        gridVFill,
+        XmRBoolean,
+        sizeof (Boolean),
+        XtOffsetOf( GridConstraintRec,
+                   grid.vfill),
+        XmRImmediate,
+        (XtPointer) 0
+    }
+    
+};
+//*/
+//static XtResource constraints[] = {};
+
+GridClassRec gridClassRec = {
+    // Core Class
+    {
+        //(WidgetClass)&constraintClassRec,   // superclass  
+        (WidgetClass)&xmManagerClassRec,
+        "Grid",                       // class_name
+        sizeof(GridRec),          // widget_size
+        grid_class_initialize,    // class_initialize
+        NULL,                         // class_part_initialize
+        FALSE,                        // class_inited
+        (XtInitProc)grid_initialize,          // initialize
+        NULL,                         // initialize_hook
+        grid_realize,             // realize
+        actionslist,                         // actions
+        XtNumber(actionslist),                            // num_actions
+        NULL,                         // resources
+        0,                            // num_resources
+        NULLQUARK,                    // xrm_class
+        True,                         // compress_motion
+        True,                         // compress_exposure
+        True,                         // compress_enterleave
+        False,                        // visible_interest
+        (XtWidgetProc)grid_destroy,             // destroy
+        (XtWidgetProc)grid_resize,              // resize
+        (XtExposeProc)grid_expose,              // expose
+        grid_set_values,          // set_values
+        NULL,                         // set_values_hook
+        XtInheritSetValuesAlmost,     // set_values_almost
+        NULL,                         // get_values_hook
+        (XtAcceptFocusProc)grid_acceptfocus,       // accept_focus
+        XtVersion,                    // version
+        NULL,                         // callback_offsets
+        //NULL,                         // tm_table
+                defaultTranslations,
+        XtInheritQueryGeometry,       // query_geometry
+        NULL,                         // display_accelerator
+        NULL,                         // extension
+    },
+    // Composite Class
+    {
+        GridGeometryManager, /* geometry_manager */
+        GridChangeManaged,  /* change_managed */   
+        XtInheritInsertChild,  /* insert_child */ 
+        XtInheritDeleteChild,  /* delete_child */  
+        NULL,                 /* extension */    
+    },
+    // Constraint Class
+    {
+        constraints,    /* resources */  
+        XtNumber(constraints),  /* num_resources */    
+        sizeof(GridConstraintRec),  /* constraint_size */  
+        grid_constraint_init,  /* initialize */  
+        NULL,  /* destroy */
+        ConstraintSetValues,  /* set_values */   
+        NULL,  /* extension */    
+    },
+    // XmManager Class
+    ///*
+    {
+        NULL,
+        NULL,
+        0,
+        NULL,
+        0,
+        NULL,
+        NULL
+    },
+    //*/
+    // MyWidget Class
+    {
+        0
+    }
+};
+
+WidgetClass gridClass = (WidgetClass)&gridClassRec;
+
+
+void grid_class_initialize(Widget request, Widget new, ArgList args, Cardinal *num_args) {
+    
+}
+void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) {
+    MyWidget mn = (MyWidget)new;
+    
+    mn->mywidget.max_col = 0;
+    mn->mywidget.max_row = 0;
+    
+}
+void grid_realize(MyWidget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) {
+    XtMakeResizeRequest((Widget)w, 400, 400, NULL, NULL);
+    (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); 
+    grid_place_children(w);
+}
+
+
+void grid_destroy(MyWidget widget) {
+    
+}
+void grid_resize(MyWidget widget) {
+    grid_place_children(widget);
+}
+
+void grid_expose(MyWidget widget, XEvent *event, Region region) {
+    
+}
+
+
+Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    return False;
+}
+
+Boolean grid_acceptfocus(Widget w, Time *t) {
+    
+}
+
+void grid_getfocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+    
+}
+
+void grid_loosefocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) {
+    
+}
+
+
+
+XtGeometryResult GridGeometryManager(Widget	widget, XtWidgetGeometry *request, XtWidgetGeometry *reply) {
+    GridRec *grid = (GridRec*)XtParent(widget);
+    GridConstraintRec *constraints = widget->core.constraints;
+    //XtVaSetValues(widget, XmNwidth, request->width, XmNheight, request->height, NULL);
+    if((request->request_mode & CWWidth) == CWWidth) {
+        widget->core.width = request->width;
+        constraints->grid.pref_width = request->width;
+    }
+    if((request->request_mode & CWHeight) == CWHeight) {
+        widget->core.height = request->height;
+        constraints->grid.pref_height = request->height;
+    }
+    grid_place_children((MyWidget)XtParent(widget));
+    return XtGeometryYes;
+}
+
+void GridChangeManaged(Widget widget) {
+    
+}
+
+Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    GridConstraintRec *constraints = neww->core.constraints;
+    MyWidget grid = (MyWidget)XtParent(neww);
+    if(constraints->grid.x > grid->mywidget.max_col) {
+        grid->mywidget.max_col = constraints->grid.x;
+    }
+    if(constraints->grid.y > grid->mywidget.max_row) {
+        grid->mywidget.max_row = constraints->grid.y;
+    }
+}
+
+
+void grid_constraint_init(
+    Widget	request,
+    Widget	neww,
+    ArgList	args,
+    Cardinal*	num_args
+)
+{
+    GridConstraintRec *constraints = neww->core.constraints;
+    
+    MyWidget grid = (MyWidget)XtParent(neww);
+    if(constraints->grid.x > grid->mywidget.max_col) {
+        grid->mywidget.max_col = constraints->grid.x;
+    }
+    if(constraints->grid.y > grid->mywidget.max_row) {
+        grid->mywidget.max_row = constraints->grid.y;
+    }
+    constraints->grid.pref_width = neww->core.width;
+    constraints->grid.pref_height = neww->core.height;
+}
+
+void grid_place_children(MyWidget w) {
+    int ncols = w->mywidget.max_col+1;
+    int nrows = w->mywidget.max_row+1;
+    GridDef *cols = calloc(ncols, sizeof(GridDef));
+    GridDef *rows = calloc(nrows, sizeof(GridDef));
+    int num_cols_expanding = 0;
+    int num_rows_expanding = 0;
+    int req_width = 0;
+    int req_height = 0;
+    
+    // calculate the minimum size requirements for all columns and rows
+    // we need to run this 2 times: for widgets without colspan/rowspan first
+    // and then again for colspan/rowspan > 1
+    int span_max = 1;
+    for(int r=0;r<2;r++) {
+        for(int i=0;i<w->composite.num_children;i++) {
+            Widget child = w->composite.children[i];
+            GridConstraintRec *constraints = child->core.constraints;
+            
+            if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) {
+                continue;
+            }
+            
+            int x = constraints->grid.x;
+            int y = constraints->grid.y;
+            // make sure ncols/nrows is correct
+            // errors shouldn't happen, unless someone messes up the grid internals
+            if(x >= ncols) {
+                fprintf(stderr, "Error: widget x out of bounds\n");
+                continue;
+            }
+            if(y >= nrows) {
+                fprintf(stderr, "Error: widget y out of bounds\n");
+                continue;
+            }
+            GridDef *col = &cols[x];
+            GridDef *row = &rows[y];
+            
+            if(constraints->grid.hexpand) {
+                if(constraints->grid.colspan > 1) {
+                    // check if any column in the span is expanding
+                    // if not, make the last column expanding
+                    GridDef *last_col = col;
+                    for(int c=x;c<ncols;c++) {
+                        last_col = &cols[c];
+                        if(last_col->expand) {
+                            break;
+                        }
+                    }
+                    last_col->expand = TRUE;
+                } else {
+                    col->expand = TRUE;
+                }
+            }
+            if(constraints->grid.vexpand) {
+                if(constraints->grid.rowspan > 1) {
+                    GridDef *last_row = row;
+                    for(int c=x;c<nrows;c++) {
+                        last_row = &rows[c];
+                        if(last_row->expand) {
+                            break;
+                        }
+                    }
+                    last_row->expand = TRUE;
+                } else {
+                    row->expand = TRUE;
+                }
+            } 
+            
+            // column size
+            if(constraints->grid.colspan > 1) {
+                // check size of all columns in span
+                Dimension span_width = col->size;
+                GridDef *last_col = col;
+                for(int s=x+1;s<ncols;s++) {
+                    last_col = &cols[s];
+                    span_width = last_col->size;
+                    
+                }
+                int diff = constraints->grid.pref_width - span_width;
+                if(diff > 0) {
+                    last_col->size += diff; 
+                }
+            } else if(constraints->grid.pref_width > col->size) {
+                col->size = constraints->grid.pref_width;
+            }
+            // row size
+            if(constraints->grid.rowspan > 1) {
+                Dimension span_height = row->size;
+                GridDef *last_row = row;
+                for(int s=x+1;s<nrows;s++) {
+                    last_row = &rows[s];
+                    span_height = last_row->size;
+                    
+                }
+                int diff = constraints->grid.pref_height - span_height;
+                if(diff > 0) {
+                    last_row->size += diff; 
+                }
+            } else if(constraints->grid.pref_height > row->size) {
+                row->size = constraints->grid.pref_height;
+            }
+        }
+        span_max = 50000; // not sure if this is unreasonable low or high
+    }
+    
+    
+    for(int i=0;i<ncols;i++) {
+        if(cols[i].expand) {
+            num_cols_expanding++;
+        }
+        req_width += cols[i].size;
+    }
+    for(int i=0;i<nrows;i++) {
+        if(rows[i].expand) {
+            num_rows_expanding++;
+        }
+        req_height += rows[i].size;
+    }
+    
+    if(req_width > 0 && req_height > 0) {
+        Widget parent = w->core.parent;
+        Dimension rwidth = req_width;
+        Dimension rheight = req_height;
+        if(rwidth < w->core.width) {
+            //rwidth = w->core.width;
+        }
+        if(rheight < w->core.height) {
+            //rheight = w->core.height;
+        }
+        
+        if(!w->mywidget.sizerequest) {
+            Dimension actual_width, actual_height;
+            w->mywidget.sizerequest = TRUE;
+            XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height);
+            w->mywidget.sizerequest = FALSE;
+            //printf("size request: %d %d\n", (int)actual_width, (int)actual_height);
+        }
+        
+       
+        
+    }
+    
+    int hexpand = 0;
+    int width_diff = (int)w->core.width - req_width;
+    int hexpand2 = 0;
+    if(width_diff > 0 && num_cols_expanding > 0) {
+        hexpand = width_diff / num_cols_expanding;
+        hexpand2 = width_diff-hexpand*num_cols_expanding;
+    }
+    int x = 0;
+    for(int i=0;i<ncols;i++) {
+        cols[i].pos = x;
+        if(cols[i].expand) {
+            cols[i].size += hexpand + hexpand2;
+        }
+        x += cols[i].size;
+        
+        hexpand2 = 0;
+    }
+    
+    int vexpand = 0;
+    int height_diff = (int)w->core.height - req_height;
+    int vexpand2 = 0;
+    if(height_diff > 0 && num_rows_expanding > 0) {
+        vexpand = height_diff / num_rows_expanding;
+        vexpand2 = height_diff-vexpand*num_rows_expanding;
+    }
+    int y = 0;
+    for(int i=0;i<nrows;i++) {
+        rows[i].pos = y;
+        if(rows[i].expand) {
+            rows[i].size += vexpand + vexpand2;
+        }
+        y += rows[i].size;
+        
+        vexpand2 = 0;
+    }
+    
+    for(int i=0;i<w->composite.num_children;i++) {
+        Widget child = w->composite.children[i];
+        GridConstraintRec *constraints = child->core.constraints;
+        GridDef c = cols[constraints->grid.x];
+        GridDef r = rows[constraints->grid.y];
+        int x = c.pos;
+        int y = r.pos;
+        int width = constraints->grid.pref_width;
+        int height = constraints->grid.pref_height;
+        if(constraints->grid.hfill) {
+            if(constraints->grid.colspan > 1) {
+                Dimension cwidth = 0;
+                for(int j=0;j<constraints->grid.colspan;j++) {
+                    if(constraints->grid.x+j < ncols) {
+                        cwidth += cols[constraints->grid.x+j].size;
+                    }
+                }
+                width = cwidth;
+            } else {
+                width = c.size;
+            }
+        }
+        if(constraints->grid.vfill) {
+            if(constraints->grid.rowspan > 1) {
+                Dimension cheight = 0;
+                for(int j=0;j<constraints->grid.rowspan;j++) {
+                    if(constraints->grid.y+j < nrows) {
+                        cheight += rows[constraints->grid.y+j].size;
+                    }
+                }
+                height = cheight;
+            } else {
+                height = r.size;
+            }
+        }
+        
+        XtConfigureWidget(child, x, y, width, height, child->core.border_width);
+        //printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height);
+    }
+    
+    free(cols);
+    free(rows);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/Grid.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,152 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GRID_H
+#define GRID_H
+
+#include <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+#include <Xm/XmAll.h>
+#include <Xm/Primitive.h>
+#include <Xm/PrimitiveP.h>
+#include <Xm/ManagerP.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define gridColumn "gridColumn"
+#define gridRow "gridRow"
+#define gridColspan "gridColspan"
+#define gridRowspan "gridRowspan"
+#define gridHExpand "gridHExpand"
+#define gridVExpand "gridVExpand"
+#define gridHFill "gridHFill"
+#define gridVFill "gridVFill"
+#define gridMarginLeft "gridMarginLeft"
+#define gridMarginRight "gridMarginRight"
+#define gridMarginTop "gridMarginTop"
+#define gridMarginBottom "gridMarginBottom"
+    
+
+typedef struct GridDef {
+    Dimension size;
+    Dimension pos;
+    Boolean expand;
+} GridDef;
+    
+typedef struct GridClassPart {
+    int test;
+} GridClassPart;
+    
+typedef struct GridClassRec {
+    CoreClassPart        core_class;
+    CompositeClassPart   composite_class;
+    ConstraintClassPart  constraint_class;
+    XmManagerClassPart  manager_class;
+    GridClassPart    mywidgetclass;
+} GridClassRec;
+
+
+typedef struct GridPart {
+    int margin_left;
+    int margin_right;
+    int margin_top;
+    int margin_bottom;
+    int max_col;
+    int max_row;
+    
+    Boolean sizerequest;
+} GridPart;
+
+typedef struct GridRec {
+    CorePart	    core;
+    CompositePart   composite;
+    ConstraintPart  constraint;
+    XmManagerPart   manager;
+    GridPart    mywidget;
+} GridRec;
+
+typedef struct GridContraintPart {
+    Dimension x;
+    Dimension y;
+    Dimension margin_left;
+    Dimension margin_right;
+    Dimension margin_top;
+    Dimension margin_bottom;
+    Boolean hexpand;
+    Boolean vexpand;
+    Boolean hfill;
+    Boolean vfill;
+    Dimension colspan;
+    Dimension rowspan;
+    Dimension pref_width;
+    Dimension pref_height;
+} GridContraintPart;
+
+typedef struct GridConstraintRec {
+    XmManagerConstraintPart manager;
+    GridContraintPart grid;
+} GridConstraintRec;
+
+typedef GridRec* MyWidget;
+
+extern WidgetClass gridClass;
+
+void grid_class_initialize();
+void grid_initialize();
+void grid_realize();
+void grid_destroy();
+void grid_resize();
+void grid_expose();
+Boolean grid_set_values();
+Boolean grid_acceptfocus(Widget , Time*);
+
+void grid_place_children(MyWidget w);
+
+void grid_getfocus();
+void grid_loosefocus();
+
+void grid_constraint_init(
+    Widget	request,
+    Widget	neww,
+    ArgList	args,
+    Cardinal*	num_args
+);
+
+XtGeometryResult GridGeometryManager(Widget	widget, XtWidgetGeometry *request, XtWidgetGeometry *reply);
+void GridChangeManaged(Widget widget);
+Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRID_H */
+
--- a/ui/motif/button.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/button.c	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -38,69 +38,54 @@
 #include <cx/array_list.h>
 #include <cx/compare.h>
 
+#include <Xm/XmAll.h>
 
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+    Arg xargs[16];
+    int n = 0;
     
-    int n = 0;
-    Arg args[16];
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
+    Widget parent = ctn->prepare(ctn, xargs, &n);
     
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget button = XmCreatePushButton(parent, "button", args, n);
-    ct->add(ct, button);
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
     
-    if(f) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = data;
-        event->callback = f;
-        event->value = 0;
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreatePushButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    if(args.onclick) {
+        UiEventData *eventdata = malloc(sizeof(UiEventData));
+        eventdata->callback = args.onclick;
+        eventdata->userdata = args.onclickdata;
+        eventdata->obj = obj;
+        eventdata->value = 0;
         XtAddCallback(
                 button,
                 XmNactivateCallback,
                 (XtCallbackProc)ui_push_button_callback,
-                event);
+                eventdata);
+       XtAddCallback(
+                button,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_destroy_eventdata,
+                eventdata);
     }
     
-    XtManageChild(button);
     
+    XmStringFree(label);
     return button;
 }
 
-// wrapper
-int64_t ui_toggle_button_get(UiInteger *i) {
-    int state = 0;
-    XtVaGetValues(i->obj, XmNset, &state, NULL);
-    i->value = state;
-    return state;
-}
-
-void ui_toggle_button_set(UiInteger *i, int64_t value) {
-    Arg arg;
-    XtSetArg(arg, XmNset, value);
-    XtSetValues(i->obj, &arg, 1);
-    i->value = value;
-}
-
-void ui_toggle_button_callback(
-        Widget widget,
-        UiEventData *event,
-        XmToggleButtonCallbackStruct *tb)
-{
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    // TODO: e.document
-    e.intval = tb->set;
-    event->callback(&e, event->userdata); 
-}
-
 void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) {
     UiEvent e;
     e.obj = event->obj;
@@ -110,105 +95,295 @@
     event->callback(&e, event->userdata);
 }
 
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    XtSetArg(xargs[n], XmNfillOnSelect, True); n++;
+    XtSetArg(xargs[n], XmNindicatorOn, False); n++;
+    
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "togglebutton";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group);
+    
+    XmStringFree(label);
+    return button;
+}
 
-static void radio_callback(
-        Widget widget,
-        RadioEventData *event,
-        XmToggleButtonCallbackStruct *tb)
-{
-    if(tb->set) {
-        RadioButtonGroup *group = event->group;
-        if(group->current) {
-            Arg arg;
-            XtSetArg(arg, XmNset, FALSE);
-            XtSetValues(group->current, &arg, 1);
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    
+    ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group);
+    
+    XmStringFree(label);
+    return button;
+}
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+    return ui_checkbox_create(obj, args);
+}
+
+static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {
+    if(event->value > 0) {
+        // button in configured to enable/disable states
+        if(tb->set) {
+            ui_set_group(event->obj->ctx, event->value);
+        } else {
+            ui_unset_group(event->obj->ctx, event->value);
         }
-        group->current = widget;
+    }
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = XmToggleButtonGetState(w);
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(event->var && event->var->value) {
+        UiInteger *v = event->var->value;
+        v->value = e.intval;
+        ui_notify_evt(v->observers, &e);
     }
 }
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
-    
-    int n = 0;
-    Arg args[16];
-    
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
-    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget button = XmCreateToggleButton(parent, "radiobutton", args, n);
-    ct->add(ct, button);
-    
-    if(rgroup) {
-        RadioButtonGroup *group;
-        if(rgroup->obj) {
-            group = rgroup->obj;
-            if(!group->buttons) {
-                group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
-            }
-            cxListAdd(group->buttons, button);
-            group->ref++;
-        } else {
-            group = malloc(sizeof(RadioButtonGroup));
-            group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
-            cxListAdd(group->buttons, button);
-            group->current = button;
-            // this is the first button in the radiobutton group
-            // so we should enable it
-            Arg arg;
-            XtSetArg(arg, XmNset, TRUE);
-            XtSetValues(button, &arg, 1);
-            rgroup->obj = group;
-            
-            group->current = button;
+void ui_bind_togglebutton(
+        UiObject *obj,
+        Widget widget,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state)
+{
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER);
+    if(var) {
+        value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = ui_togglebutton_get;
+        value->set = ui_togglebutton_set;
+        
+        if(value->value) {
+            XmToggleButtonSetState(widget, True, False);
         }
-        
-        RadioEventData *event = malloc(sizeof(RadioEventData));
-        event->obj = obj;
-        event->callback = NULL;
-        event->userdata = NULL;
-        event->group = group;
-        XtAddCallback(
-            button,
-            XmNvalueChangedCallback,
-            (XtCallbackProc)radio_callback,
-            event);
-        
-        rgroup->get = ui_radiobutton_get;
-        rgroup->set = ui_radiobutton_set;
     }
     
-    XtManageChild(button); 
-    return button;
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = onchange;
+    event->userdata = onchangedata;
+    event->var = var;
+    event->observers = NULL;
+    event->value = enable_state;
+    XtAddCallback(
+            widget,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)togglebutton_changed,
+            event);
+    XtAddCallback(
+            widget,
+            XmNdestroyCallback,
+            (XtCallbackProc)ui_destroy_eventdata,
+            event);
+}
+
+int64_t ui_togglebutton_get(UiInteger *i) {
+    Widget togglebutton = i->obj;
+    Boolean state = XmToggleButtonGetState(togglebutton);
+    i->value = state;
+    return state;
+}
+
+void ui_togglebutton_set(UiInteger *i, int64_t value) {
+    Widget togglebutton = i->obj;
+    i->value = value;
+    XmToggleButtonSetState(togglebutton, (Boolean)value, False);
+}
+
+static void destroy_list(Widget w, CxList *list, XtPointer d) {
+    cxListDestroy(list);
 }
 
-int64_t ui_radiobutton_get(UiInteger *value) {
-    RadioButtonGroup *group = value->obj;
+static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {
+    if(event->value > 0) {
+        // button in configured to enable/disable states
+        if(tb->set) {
+            ui_set_group(event->obj->ctx, event->value);
+        } else {
+            ui_unset_group(event->obj->ctx, event->value);
+        }
+    }
+    
+    if(!tb->set) {
+        return; // only handle set-events
+    }
     
-    int i = cxListFind(group->buttons, group->current);
-    if (i >= 0) {
-        value->value = i;
-        return i;
-    } else {
-        return 0;
+    UiInteger *value = NULL;
+    int64_t v = 0;
+    if(event->var) {
+        value = event->var->value;
+        // find widget index and update all radiobuttons
+        // the UiInteger value must always be up-to-date
+        CxList *list = value->obj;
+        CxIterator i = cxListIterator(list);
+        cx_foreach(Widget, button, i) {
+            Boolean state = False;
+            if(button == w) {
+                value->value = i.index+1; // update value
+                state = True;
+            }
+            XmToggleButtonSetState(button, state, False);
+        }
+        v = value->value;
+    }
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = value;
+    e.intval = v;
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(value) {
+        ui_notify_evt(value->observers, &e);
     }
 }
 
-void ui_radiobutton_set(UiInteger *value, int64_t i) {
-    RadioButtonGroup *group = value->obj;
-    Arg arg;
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    XtSetArg(xargs[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++;
+    XmString label = NULL;
+    if(args.label) {
+        label = XmStringCreateLocalized((char*)args.label);
+        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    }
+    
+    char *name = args.name ? (char*)args.name : "button";
+    Widget button = XmCreateToggleButton(parent, name, xargs, n);
+    XtManageChild(button);
+    ctn->add(ctn, button);
+    
+    ui_set_widget_groups(obj->ctx, button, args.groups);
     
-    XtSetArg(arg, XmNset, FALSE);
-    XtSetValues(group->current, &arg, 1);
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        CxList *rb = value->obj;
+        if(!rb) {
+            // first button in the radiobutton group
+            // create a list for all buttons and use the list as value obj
+            rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+            value->obj = rb;
+            value->get = ui_radiobutton_get;
+            value->set = ui_radiobutton_set;
+            
+            // the first radio button is also responsible for cleanup
+            XtAddCallback(
+                    button,
+                    XmNdestroyCallback,
+                    (XtCallbackProc)destroy_list,
+                    rb);
+        }
+        cxListAdd(rb, button);
+        
+        // set the radiobutton state, if the value is already set
+        if(cxListSize(rb) == value->value) {
+            XmToggleButtonSetState(button, True, False);
+        }
+    }
     
-    Widget button = cxListAt(group->buttons, i);
-    if(button) {
-        XtSetArg(arg, XmNset, TRUE);
-        XtSetValues(button, &arg, 1);
-        group->current = button;
+    // the radio button needs to handle change events to update all
+    // other buttons in the radio button group
+    UiVarEventData *event = malloc(sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = args.onchange;
+    event->userdata = args.onchangedata;
+    event->observers = NULL;
+    event->var = var;
+    event->value = args.enable_group;
+    XtAddCallback(
+            button,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)radiobutton_changed,
+            event);
+    XtAddCallback(
+            button,
+            XmNdestroyCallback,
+            (XtCallbackProc)ui_destroy_eventdata,
+            event);
+    
+    XmStringFree(label);
+    return button;
+    
+    
+}
+
+int64_t ui_radiobutton_get(UiInteger *i) {
+    // the UiInteger should be updated automatically by change events
+    return i->value;
+}
+
+void ui_radiobutton_set(UiInteger *i, int64_t value) {
+    CxList *list = i->obj;
+    if(i->value > 0) {
+        Widget current = cxListAt(list, i->value-1);
+        if(current) {
+            XmToggleButtonSetState(current, False, False);
+        }
+    }
+    if(value > 0 && value <= cxListSize(list)) {
+        Widget button = cxListAt(list, value-1);
+        if(button) {
+            XmToggleButtonSetState(button, True, False);
+            i->value = value;
+        }
     }
 }
--- a/ui/motif/button.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/button.h	Thu Dec 12 20:01:43 2024 +0100
@@ -36,30 +36,22 @@
 extern "C" {
 #endif
 
-typedef struct {
-    CxList  *buttons;
-    Widget  current;
-    int     ref;
-} RadioButtonGroup;
-
-typedef struct {
-    UiObject         *obj;
-    ui_callback      callback;
-    void             *userdata;
-    RadioButtonGroup *group;
-} RadioEventData;
-
-// wrapper
-int64_t ui_toggle_button_get(UiInteger *i);
-void ui_toggle_button_set(UiInteger *i, int64_t value);
-void ui_toggle_button_callback(
-        Widget widget,
-        UiEventData *data,
-        XmToggleButtonCallbackStruct *e);
 void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d);
 
-int64_t ui_radiobutton_get(UiInteger *value);
-void ui_radiobutton_set(UiInteger *value, int64_t i);
+void ui_bind_togglebutton(
+        UiObject *obj,
+        Widget widget,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state);
+
+int64_t ui_togglebutton_get(UiInteger *i);
+void ui_togglebutton_set(UiInteger *i, int64_t value);
+
+int64_t ui_radiobutton_get(UiInteger *i);
+void ui_radiobutton_set(UiInteger *i, int64_t value);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/container.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/container.c	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -34,751 +34,189 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-#include <cx/array_list.h>
-#include <cx/linked_list.h>
-#include <cx/compare.h>
-
-#define UI_GRID_MAX_COLUMNS 512
-
-static UiBool ui_lb2bool(UiLayoutBool b) {
-    return b == UI_LAYOUT_TRUE ? TRUE : FALSE;
-}
-
-static UiLayoutBool ui_bool2lb(UiBool b) {
-    return b ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE;
-}
-
+#include "Grid.h"
 
-UiContainer* ui_frame_container(UiObject *obj, Widget frame) {
-    UiContainer *ct = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            sizeof(UiContainer));
-    ct->widget = frame;
-    ct->prepare = ui_frame_container_prepare;
-    ct->add = ui_frame_container_add;
-    return ct;
-}
-
-Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    return ct->widget;
-}
-
-void ui_frame_container_add(UiContainer *ct, Widget widget) {
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
+/* ---------------------------- Box Container ---------------------------- */
 
-UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation) {
-    UiBoxContainer *ct = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            sizeof(UiBoxContainer));
-    ct->container.widget = box;
-    ct->container.prepare = ui_box_container_prepare;
-    ct->container.add = ui_box_container_add;
-    ct->orientation = orientation;
-    ct->margin = margin;
-    ct->spacing = spacing;
-    return (UiContainer*)ct;
-}
-
-Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    UiBoxContainer *bc = (UiBoxContainer*)ct;
-    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
-        fill = ui_lb2bool(ct->layout.fill);
-    }
+static UIWIDGET box_create(UiObject *obj, UiContainerArgs args, UiBoxOrientation orientation) { 
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    if(bc->has_fill && fill) {
-        fprintf(stderr, "UiError: container has 2 filled widgets");
-        fill = FALSE;
-    }
-    if(fill) {
-        bc->has_fill = TRUE;
-    }
+    Arg xargs[16];
+    int n = 0;
     
-    int a = *n;
-    // determine fixed and dynamic attachments
-    void *f1;
-    void *f2;
-    void *d1;
-    void *d2;
-    void *w1;
-    void *w2;
-    if(bc->orientation == UI_BOX_VERTICAL) {
-        f1 = XmNleftAttachment;
-        f2 = XmNrightAttachment;
-        d1 = XmNtopAttachment;
-        d2 = XmNbottomAttachment;
-        w1 = XmNtopWidget;
-        w2 = XmNbottomWidget;
-        
-        // margin/spacing
-        XtSetArg(args[a], XmNleftOffset, bc->margin); a++;
-        XtSetArg(args[a], XmNrightOffset, bc->margin); a++;
-        
-        XtSetArg(args[a], XmNtopOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+    if(orientation == UI_BOX_VERTICAL) {
+        //XtSetArg(xargs[n], gridRowSpacing, args.spacing); n++;
     } else {
-        f1 = XmNtopAttachment;
-        f2 = XmNbottomAttachment;
-        d1 = XmNleftAttachment;
-        d2 = XmNrightAttachment;
-        w1 = XmNleftWidget;
-        w2 = XmNrightWidget;
-        
-        // margin/spacing
-        XtSetArg(args[a], XmNtopOffset, bc->margin); a++;
-        XtSetArg(args[a], XmNbottomOffset, bc->margin); a++;
-        
-        XtSetArg(args[a], XmNleftOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
-    }
-    XtSetArg(args[a], f1, XmATTACH_FORM); a++;
-    XtSetArg(args[a], f2, XmATTACH_FORM); a++;
-
-    if(fill) {
-        XtSetArg(args[a], d2, XmATTACH_FORM); a++;
-    }
-    if(bc->prev_widget) {
-        XtSetArg(args[a], d1, XmATTACH_WIDGET); a++;
-        XtSetArg(args[a], w1, bc->prev_widget); a++;
-    } else {
-        XtSetArg(args[a], d1, XmATTACH_FORM); a++;
-    }
-    
-    *n = a;
-    return ct->widget;
-}
-
-void ui_box_container_add(UiContainer *ct, Widget widget) {
-    UiBoxContainer *bc = (UiBoxContainer*)ct;
-    // determine dynamic attachments
-    void *d1;
-    void *d2;
-    void *w1;
-    void *w2;
-    if(bc->orientation == UI_BOX_VERTICAL) {
-        d1 = XmNtopAttachment;
-        d2 = XmNbottomAttachment;
-        w1 = XmNtopWidget;
-        w2 = XmNbottomWidget;
-        
-    } else {
-        d1 = XmNleftAttachment;
-        d2 = XmNrightAttachment;
-        w1 = XmNleftWidget;
-        w2 = XmNrightWidget;
-    }
-    
-    if(bc->prev_widget) {
-        int v = 0;
-        XtVaGetValues(bc->prev_widget, d2, &v, NULL);
-        if(v == XmATTACH_FORM) {
-            XtVaSetValues(
-                    bc->prev_widget,
-                    d2,
-                    XmATTACH_WIDGET,
-                    w2,
-                    widget,
-                    NULL);
-            XtVaSetValues(
-                    widget,
-                    d1,
-                    XmATTACH_NONE,
-                    d2,
-                    XmATTACH_FORM,
-                    NULL);
-        }
-    }
-    bc->prev_widget = widget;
-    
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing) {
-    UiGridContainer *ct = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            sizeof(UiGridContainer));
-    ct->container.widget = form;
-    ct->container.prepare = ui_grid_container_prepare;
-    ct->container.add = ui_grid_container_add;
-    ct->columnspacing = columnspacing;
-    ct->rowspacing = rowspacing;
-    ct->lines = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    return (UiContainer*)ct;
-}
-
-void ui_grid_newline(UiGridContainer *grid) {
-    if(grid->current) {
-        grid->current = NULL;
-    }
-    grid->container.layout.newline = FALSE;
-}
-
-Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    UiGridContainer *grid = (UiGridContainer*)ct;
-    if(ct->layout.newline) {
-        ui_grid_newline(grid);
-    }
-    return ct->widget;
-}
-
-void ui_grid_container_add(UiContainer *ct, Widget widget) {
-    UiGridContainer *grid = (UiGridContainer*)ct;
-    
-    if(grid->current) {
-        cxListAdd(grid->current, widget);
-    } else {
-        grid->current = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-        cxListAdd(grid->current, widget);
-        cxListAdd(grid->lines, grid->current);
+        //XtSetArg(xargs[n], gridColumnSpacing, args.spacing); n++;
     }
     
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-static void ui_grid_resize(Widget widget, XtPointer udata, XtPointer cdata) {
-    UiGridContainer *grid = udata;
-    
-    CxList *rowdim = cxArrayListCreateSimple(sizeof(int), grid->lines->size);
-    int coldim[UI_GRID_MAX_COLUMNS];
-    memset(coldim, 0, UI_GRID_MAX_COLUMNS*sizeof(int));
-    int numcol = 0;
-    
-    // get the minimum size of the columns and rows
-    int sumw = 0;
-    int sumh = 0;
-    CxIterator lineIterator = cxListIterator(grid->lines);
-    cx_foreach(CxList *, row, lineIterator) {
-        int rheight = 0;
-        int i=0;
-        int sum_width = 0;
-        CxIterator colIterator = cxListIterator(row);
-        cx_foreach(Widget, w, colIterator) {
-            int widget_width = 0;
-            int widget_height = 0;
-            XtVaGetValues(
-                    w,
-                    XmNwidth,
-                    &widget_width,
-                    XmNheight,
-                    &widget_height, 
-                    NULL);
-            
-            // get the maximum height in this row
-            if(widget_height > rheight) {
-                rheight = widget_height;
-            }
-            
-            // get the maximum width in this column
-            if(widget_width > coldim[i]) {
-                coldim[i] = widget_width;
-            }
-            sum_width += widget_width;
-            if(sum_width > sumw) {
-                sumw = sum_width;
-            }
-            
-            i++;
-            if(i > numcol) {
-                numcol = i;
-            }
-        }
-        cxListAdd(rowdim, &rheight);
-        sumh += rheight;
-    }
-    
-    // check container size
-    int gwidth = 0;
-    int gheight = 0;
-    XtVaGetValues(widget, XmNwidth, &gwidth, XmNheight, &gheight, NULL);
-    if(gwidth < sumw || gheight < sumh) {
-        XtVaSetValues(widget, XmNwidth, sumw, XmNheight, sumh, NULL);
-        cxListDestroy(rowdim);
-        return;
-    }
-    
-    
-    // adjust the positions of all children
-    int y = 0;
-    lineIterator = cxListIterator(grid->lines);
-    cx_foreach(CxList *, row, lineIterator) {
-        int x = 0;       
-        int i=0;
-        int *rowheight = cxListAt(rowdim, lineIterator.index);
-        CxIterator colIterator = cxListIterator(row);
-        cx_foreach(Widget, w, colIterator) {
-            XtVaSetValues(
-                    w,
-                    XmNx, x,
-                    XmNy, y,
-                    XmNwidth, coldim[i],
-                    XmNheight, *rowheight,
-                    NULL);
-            
-            x += coldim[i];
-            i++;
-        }
-        y += *rowheight;
-    }
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget grid = XtCreateManagedWidget(args.name ? args.name : "boxcontainer", gridClass, parent, xargs, n);
+    ctn->add(ctn, grid);
     
-    cxListDestroy(rowdim);
-}
-
-UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
-    UiContainer *ct = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            sizeof(UiContainer));
-    ct->widget = scrolledwindow;
-    ct->prepare = ui_scrolledwindow_container_prepare;
-    ct->add = ui_scrolledwindow_container_add;
-    return ct;
-}
-
-Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    return ct->widget;
-}
-
-void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget) {
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-
-UiContainer* ui_tabview_container(UiObject *obj, Widget frame) {
-    UiTabViewContainer *ct = cxCalloc(
-            obj->ctx->allocator,
-            1,
-            sizeof(UiTabViewContainer));
-    ct->context = obj->ctx;
-    ct->container.widget = frame;
-    ct->container.prepare = ui_tabview_container_prepare;
-    ct->container.add = ui_tabview_container_add;
-    ct->tabs = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
-    return (UiContainer*)ct;
-}
-
-Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
-    int a = *n;
-    XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNtopAttachment, XmATTACH_FORM); a++;
-    XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
-    *n = a;
-    return ct->widget;
-}
-
-void ui_tabview_container_add(UiContainer *ct, Widget widget) {
-    UiTabViewContainer *tabview = (UiTabViewContainer*)ct; 
-    
-    if(tabview->current) {
-        XtUnmanageChild(tabview->current);
-    }
-
-    tabview->current = widget;
-    cxListAdd(tabview->tabs, widget);
-    
-    ui_select_tab(ct->widget, 0);
-    ui_reset_layout(ct->layout);
-    ct->current = widget;
-}
-
-UIWIDGET ui_box(UiObject *obj, int margin, int spacing, UiBoxOrientation orientation) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget form = XmCreateForm(parent, "vbox", args, n);
-    ct->add(ct, form);
-    XtManageChild(form);
-    
-    UiObject *newobj = uic_object_new(obj, form);
-    newobj->container = ui_box_container(obj, form, margin, spacing, orientation);
-    uic_obj_add(obj, newobj);
-    
-    return form;
-}
-
-UIWIDGET ui_vbox(UiObject *obj) {
-    return ui_box(obj, 0, 0, UI_BOX_VERTICAL);
-}
-
-UIWIDGET ui_hbox(UiObject *obj) {
-    return ui_box(obj, 0, 0, UI_BOX_HORIZONTAL);
-}
-
-UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
-    return ui_box(obj, margin, spacing, UI_BOX_VERTICAL);
-}
-
-UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
-    return ui_box(obj, margin, spacing, UI_BOX_HORIZONTAL);
-}
-
-UIWIDGET ui_grid(UiObject *obj) {
-    return ui_grid_sp(obj, 0, 0, 0);
-}
-
-UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget grid = XmCreateDrawingArea(parent, "grid", args, n);
-    ct->add(ct, grid);
-    XtManageChild(grid);
-    
-    UiObject *newobj = uic_object_new(obj, grid);
-    newobj->container = ui_grid_container(obj, grid, columnspacing, rowspacing);
-    uic_obj_add(obj, newobj);
-    
-    XtAddCallback (grid, XmNresizeCallback , ui_grid_resize, newobj->container);
+    UiContainerX *container = ui_box_container(obj, grid, orientation);
+    uic_object_push_container(obj, container);
     
     return grid;
 }
 
-UIWIDGET ui_scrolledwindow(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); // TODO: dosn't work, use XmAPPLICATION_DEFINED
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", args, n);
-    ct->add(ct, scrolledwindow);
-    XtManageChild(scrolledwindow);
-    
-    UiObject *newobj = uic_object_new(obj, scrolledwindow);
-    newobj->container = ui_scrolledwindow_container(obj, scrolledwindow);
-    uic_obj_add(obj, newobj);
-    
-    return scrolledwindow;
+// public
+UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return box_create(obj, args, UI_BOX_VERTICAL);
+}
+
+// public
+UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return box_create(obj, args, UI_BOX_HORIZONTAL);
 }
 
-UIWIDGET ui_sidebar(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Arg args[16];
-    int n = 0;
-    
-    XtSetArg(args[n], XmNorientation, XmHORIZONTAL);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget pane = XmCreatePanedWindow(parent, "pane", args, n);
-    ct->add(ct, pane);
-    XtManageChild(pane);
-    
-    // add sidebar widget
-    Widget sidebar = XmCreateForm(pane, "sidebar", args, 0);
-    XtManageChild(sidebar);
-    
-    UiObject *left = uic_object_new(obj, sidebar);
-    left->container = ui_box_container(left, sidebar, 0, 0, UI_BOX_VERTICAL);
-    
-    // add content widget
-    XtSetArg (args[0], XmNpaneMaximum, 8000);
-    Widget content = XmCreateForm(pane, "content_area", args, 1);
-    XtManageChild(content);
-    
-    UiObject *right = uic_object_new(obj, content);
-    right->container = ui_box_container(right, content, 0, 0, UI_BOX_VERTICAL);
-    
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
-    
-    return sidebar;
+UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation) {
+    UiBoxContainer *ctn = ui_malloc(obj->ctx, sizeof(UiBoxContainer));
+    memset(ctn, 0, sizeof(UiBoxContainer));
+    ctn->container.prepare = orientation == UI_BOX_VERTICAL ? ui_vbox_prepare : ui_hbox_prepare;
+    ctn->container.add = ui_box_container_add;
+    ctn->container.widget = grid;
+    ctn->n = 0;
+    return (UiContainerX*)ctn;
 }
 
-UIWIDGET ui_tabview(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    // create a simple frame as container widget
-    // when tabs are selected, the current child will be replaced by the
-    // the new tab widget
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    n++;
-    XtSetArg(args[n], XmNshadowThickness, 0);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget form = XmCreateForm(parent, "tabview", args, n);
-    ct->add(ct, form);
-    XtManageChild(form);
-    
-    UiObject *tabviewobj = uic_object_new(obj, form);
-    tabviewobj->container = ui_tabview_container(obj, form);
-    uic_obj_add(obj, tabviewobj);
-    
-    XtVaSetValues(form, XmNuserData, tabviewobj->container, NULL);
-    
-    return form;
+static Widget ui_box_container_prepare(UiBoxContainer *box, Arg *args, int *n) {
+    int a = *n;
+    box->n++;
+    return box->container.widget;
 }
 
-void ui_tab(UiObject *obj, char *title) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.label = title;
-    
-    ui_vbox(obj);
-}
-
-void ui_select_tab(UIWIDGET tabview, int tab) {
-    UiTabViewContainer *ct = NULL;
-    XtVaGetValues(tabview, XmNuserData, &ct, NULL);
-    if(ct) {
-        XtUnmanageChild(ct->current);
-        Widget w = cxListAt(ct->tabs, tab);
-        if(w) {
-            XtManageChild(w);
-            ct->current = w;
-        } else {
-            fprintf(stderr, "UiError: front tab index: %d\n", tab);
-        }
-    } else {
-        fprintf(stderr, "UiError: widget is not a tabview\n");
+Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiBoxContainer *box = (UiBoxContainer*)ctn;
+    int a = *n;
+    XtSetArg(args[a], gridRow, box->n); a++;
+    if(box->container.layout.fill == UI_ON) {
+        XtSetArg(args[a], gridVExpand, TRUE); a++;
+        XtSetArg(args[a], gridVFill, TRUE); a++;
     }
+    XtSetArg(args[a], gridHExpand, TRUE); a++;
+    XtSetArg(args[a], gridHFill, TRUE); a++;
+    *n = a;
+    return ui_box_container_prepare(box, args, n);
 }
 
-
-/* document tabview */
-
-static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
-    MotifTabbedPane *v = (MotifTabbedPane*)udata;
-    
-    int width = 0;
-    int height = 0;
-    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
-    int button_width = width / 4;
-    int x = 0;
-    CxIterator tabIterator = cxListIterator(v->tabs);
-    cx_foreach(UiTab*, tab, tabIterator) {
-        XtVaSetValues(
-                tab->tab_button,
-                XmNx, x,
-                XmNy, 0,
-                XmNwidth,
-                button_width,
-                
-                NULL);
-        x += button_width;
+Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiBoxContainer *box = (UiBoxContainer*)ctn;
+    int a = *n;
+    XtSetArg(args[a], gridColumn, box->n); a++;
+    if(box->container.layout.fill == UI_ON) {
+        XtSetArg(args[a], gridHExpand, TRUE); a++;
+        XtSetArg(args[a], gridHFill, TRUE); a++;
     }
-    
-    if(height <= v->height) {
-        XtVaSetValues(widget, XmNheight, v->height + 4, NULL);
-    }
+    XtSetArg(args[a], gridVExpand, TRUE); a++;
+    XtSetArg(args[a], gridVFill, TRUE); a++;
+    *n = a;
+    return ui_box_container_prepare(box, args, n);
 }
 
-static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
-    MotifTabbedPane *v = (MotifTabbedPane*)udata;
-    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
-    XEvent *event = cbs->event;
-    Display *dpy = XtDisplay(widget); 
-    
-    XGCValues gcvals;
-    GC gc;
-    Pixel fgpix;
-    
-    int tab_x;
-    int tab_width;
-    XtVaGetValues(v->current->tab_button, XmNx, &tab_x, XmNwidth, &tab_width, XmNhighlightColor, &fgpix, NULL);
-    
-    gcvals.foreground = v->bg1;
-    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
-      
-    int width = 0;
-    int height = 0;
-    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
-    XFillRectangle(dpy, XtWindow(widget), gc, 0, 0, width, height);
-    
-    gcvals.foreground = fgpix;
-    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
-    
-    XFillRectangle(dpy, XtWindow(widget), gc, tab_x, 0, tab_width, height);
+void ui_box_container_add(UiContainerPrivate *ctn, Widget widget) {
+    ui_reset_layout(ctn->layout);
     
 }
 
-UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+
+/* ---------------------------- Grid Container ---------------------------- */
+
+// public
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    Arg xargs[16];
     int n = 0;
-    Arg args[16];
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    
-    Widget tabview = XmCreateForm(parent, "tabview_form", args, n);
-    XtManageChild(tabview);
     
-    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
-    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
-    XtSetArg(args[2], XmNspacing, 1);
-    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[6], XmNmarginWidth, 0);
-    XtSetArg(args[7], XmNmarginHeight, 0);
-    Widget tabbar = XmCreateDrawingArea(tabview, "tabbar", args, 8);
-    XtManageChild(tabbar);
+    XtSetArg(xargs[n], XmNbackground, 0); n++;
     
-    XtSetArg(args[0], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[1], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[2], XmNtopAttachment, XmATTACH_WIDGET);
-    XtSetArg(args[3], XmNtopWidget, tabbar);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNshadowThickness, 0);
-    Widget tabct = XmCreateForm(tabview, "tabview", args, 6);
-    XtManageChild(tabct);
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
     
-    MotifTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(MotifTabbedPane));
-    tabbedpane->view.ctx = uic_current_obj(obj)->ctx;
-    tabbedpane->view.widget = tabct;
-    tabbedpane->view.document = NULL;
-    tabbedpane->tabbar = tabbar;
-    tabbedpane->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
-    tabbedpane->current = NULL;
-    tabbedpane->height = 0;
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget grid = XtCreateManagedWidget(args.name ? args.name : "gridcontainer", gridClass, parent, xargs, n);
+    ctn->add(ctn, grid);
     
-    XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabbedpane);
-    XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabbedpane);
+    UiContainerX *container = ui_grid_container(obj, grid);
+    uic_object_push_container(obj, container);
     
-    return &tabbedpane->view;
+    return grid;
 }
 
-UiObject* ui_document_tab(UiTabbedPane *view) {
-    MotifTabbedPane *v = (MotifTabbedPane*)view;
-    int n = 0;
-    Arg args[16];
-    
-    // hide the current tab content
-    if(v->current) {
-        XtUnmanageChild(v->current->content->widget);
+UiContainerX* ui_grid_container(UiObject *obj, Widget grid) {
+    UiGridContainer *ctn = ui_malloc(obj->ctx, sizeof(UiGridContainer));
+    memset(ctn, 0, sizeof(UiBoxContainer));
+    ctn->container.prepare = ui_grid_container_prepare;
+    ctn->container.add = ui_grid_container_add;
+    ctn->container.widget = grid;
+    ctn->x = 0;
+    ctn->y = 0;
+    return (UiContainerX*)ctn;
+}
+
+Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    UiGridContainer *grid = (UiGridContainer*)ctn;
+    if(ctn->layout.newline) {
+        grid->y++;
+        grid->x = 0;
     }
     
-    UiTab *tab = ui_malloc(view->ctx, sizeof(UiTab));
-    
-    // create the new tab content
-    XtSetArg(args[0], XmNshadowThickness, 0);
-    XtSetArg(args[1], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[2], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    XtSetArg(args[5], XmNuserData, tab);
-    Widget frame = XmCreateFrame(view->widget, "tab", args, 6);
-    XtManageChild(frame);
-    
-    UiObject *content = ui_malloc(view->ctx, sizeof(UiObject));
-    content->widget = NULL; // initialization for uic_context()
-    content->ctx = uic_context(content, view->ctx->mp);
-    content->ctx->parent = view->ctx;
-    content->ctx->attach_document = uic_context_attach_document;
-    content->ctx->detach_document2 = uic_context_detach_document2;
-    content->widget = frame;
-    content->window = view->ctx->obj->window;
-    content->container = ui_frame_container(content, frame);
-    content->next = NULL;
-    
-    // add tab button
-    cxListAdd(v->tabs, tab);
+    int a = *n;
+    XtSetArg(args[a], gridColumn, grid->x); a++;
+    XtSetArg(args[a], gridRow, grid->y); a++;
+    if(ctn->layout.colspan > 0) {
+        XtSetArg(args[a], gridColspan, ctn->layout.colspan); a++;
+    }
+    if(ctn->layout.rowspan > 0) {
+        XtSetArg(args[a], gridRowspan, ctn->layout.rowspan); a++;
+    }
     
-    XmString label = XmStringCreateLocalized("tab");
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 0);
-    XtSetArg(args[2], XmNhighlightThickness, 0);
-    
-    Widget button = XmCreatePushButton(v->tabbar, "tab_button", args, 3);
-    tab->tabbedpane = v;
-    tab->content = content;
-    tab->tab_button = button; 
-    XtManageChild(button);
-    XtAddCallback(
-        button,
-        XmNactivateCallback,
-        (XtCallbackProc)ui_tab_button_callback,
-        tab);
-    
-    if(v->height == 0) {
-        XtVaGetValues(
-                button,
-                XmNarmColor,
-                &v->bg1,
-                XmNbackground,
-                &v->bg2,
-                XmNheight,
-                &v->height,
-                NULL);
-        v->height += 2; // border
+    if(grid->container.layout.fill == UI_ON) {
+        grid->container.layout.hfill = TRUE;
+        grid->container.layout.vfill = TRUE;
+        grid->container.layout.hexpand = TRUE;
+        grid->container.layout.vexpand = TRUE;
     }
     
-    ui_change_tab(v, tab);
-    ui_tabbar_resize(v->tabbar, v, NULL);
+    if(grid->container.layout.hfill) {
+        XtSetArg(args[a], gridHFill, TRUE); a++;
+    }
+    if(grid->container.layout.vfill) {
+        XtSetArg(args[a], gridVFill, TRUE); a++;
+    }
+    if(grid->container.layout.hexpand) {
+        XtSetArg(args[a], gridHExpand, TRUE); a++;
+    }
+    if(grid->container.layout.vexpand) {
+        XtSetArg(args[a], gridVExpand, TRUE); a++;
+    }
     
-    return content;
-}
-
-void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) {  
-    MotifTabbedPane *t = tab->tabbedpane;
-    if(t->current) {
-        XtUnmanageChild(t->current->content->widget);
-        XtVaSetValues(t->current->tab_button, XmNset, 0, NULL);
-    }
-    XtManageChild(tab->content->widget);
-    
-    ui_change_tab(t, tab);
-    
+    *n = a;
+    return ctn->widget;
 }
 
-void ui_change_tab(MotifTabbedPane *pane, UiTab *tab) {
-    UiContext *ctx = tab->content->ctx;
-    ctx->parent->detach_document2(ctx->parent, pane->current->content->ctx->document);
-    ctx->parent->attach_document(ctx->parent, ctx->document);
-    
-    if(pane->current) {
-        XtVaSetValues(pane->current->tab_button, XmNshadowThickness, 0, XmNbackground, pane->bg1, NULL);
-    }
-    XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, pane->bg2, NULL);
-    
-    pane->current = tab;
-    pane->index = cxListFind(pane->tabs, tab);
-    printf("index: %d\n", pane->index);
-    
-    // redraw tabbar
-    Display *dpy = XtDisplay(pane->tabbar);
-    Window window = XtWindow(pane->tabbar);
-    if(dpy && window) {
-        XClearArea(dpy, XtWindow(pane->tabbar), 0, 0, 0, 0, TRUE);
-        XFlush(dpy);
-    }
+void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget) {
+    UiGridContainer *grid = (UiGridContainer*)ctn;
+    grid->x++;
+    ui_reset_layout(ctn->layout);
 }
 
-void ui_tab_set_document(UiContext *ctx, void *document) {
-    if(ctx->parent->document) {
-        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
-    }
-    uic_context_attach_document(ctx, document);
-    //uic_context_set_document(ctx->parent, document);
-    //ctx->parent->document = document;
-    
-    UiTab *tab = NULL;
-    XtVaGetValues(
-            ctx->obj->widget,
-            XmNuserData,
-            &tab,
-            NULL);
-    if(tab) {
-        if(tab->tabbedpane->current == tab) {
-            ctx->parent->attach_document(ctx->parent, ctx->document);
-        }
-    } else {
-        fprintf(stderr, "UiError: ui_bar_set_document: Cannot set document");
-    }
+
+/* -------------------- Container Helper Functions -------------------- */
+
+void ui_container_begin_close(UiObject *obj) {
+    UiContainerPrivate *ct = ui_obj_container(obj);
+    ct->container.close = 1;
 }
 
+int ui_container_finish(UiObject *obj) {
+    UiContainerPrivate *ct = ui_obj_container(obj);
+    if(ct->container.close) {
+        ui_end_new(obj);
+        return 0;
+    }
+    return 1;
+}
 
 
 /*
@@ -788,27 +226,7 @@
  *
  */
 
-void ui_layout_fill(UiObject *obj, UiBool fill) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.fill = ui_bool2lb(fill);
-}
-
-void ui_layout_hexpand(UiObject *obj, UiBool expand) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.hexpand = expand;
-}
-
-void ui_layout_vexpand(UiObject *obj, UiBool expand) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.vexpand = expand;
-}
-
-void ui_layout_gridwidth(UiObject *obj, int width) {
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->layout.gridwidth = width;
-}
-
 void ui_newline(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
+    UiContainerPrivate *ct = ui_obj_container(obj);
     ct->layout.newline = TRUE;
 }
--- a/ui/motif/container.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/container.h	Thu Dec 12 20:01:43 2024 +0100
@@ -37,26 +37,37 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
-
-#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
     
-typedef struct MotifTabbedPane    MotifTabbedPane;
-typedef struct UiTab              UiTab;
-typedef struct UiBoxContainer     UiBoxContainer;
-typedef struct UiGridContainer    UiGridContainer;
-typedef struct UiTabViewContainer UiTabViewContainer;
-typedef struct UiLayout           UiLayout;
+#define UI_APPLY_LAYOUT(layout, args) \
+    layout.fill = args.fill; \
+    layout.hexpand = args.hexpand; \
+    layout.vexpand = args.vexpand; \
+    layout.hfill = args.hfill; \
+    layout.vfill = args.vfill; \
+    layout.colspan = args.colspan; \
+    layout.rowspan = args.rowspan
+    
+typedef enum UiBoxOrientation UiBoxOrientation;
+    
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
 
-typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool);
-
-typedef enum UiLayoutBool     UiLayoutBool;
-typedef enum UiBoxOrientation UiBoxOrientation;
+#define ui_obj_container(obj) (UiContainerPrivate*)obj->container_end
+    
+typedef struct UiLayout UiLayout;
 
-
-enum UiLayoutBool {
-    UI_LAYOUT_UNDEFINED = 0,
-    UI_LAYOUT_TRUE,
-    UI_LAYOUT_FALSE,
+struct UiLayout {
+    UiTri        fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    int          width;
+    int          colspan;
+    int          rowspan;
 };
 
 enum UiBoxOrientation {
@@ -64,93 +75,37 @@
     UI_BOX_HORIZONTAL
 };
 
-struct UiLayout {
-    UiLayoutBool fill;
-    UiBool       newline;
-    char         *label;
-    UiBool       hexpand;
-    UiBool       vexpand;
-    int          gridwidth;
-};
+typedef struct UiContainerPrivate UiContainerPrivate;
 
-struct UiContainer {
-    Widget   widget;
-    Widget   (*prepare)(UiContainer*, Arg *, int*, UiBool);
-    void     (*add)(UiContainer*, Widget);
-    UiLayout layout;
-    Widget   current;
-    Widget   menu;
-};
 
-struct UiBoxContainer {
-    UiContainer container;
-    Widget      prev_widget;
-    UiBool      has_fill;
-    UiBoxOrientation orientation;
-    int         margin;
-    int         spacing;
-};
-
-struct UiGridContainer {
-    UiContainer container;
-    CxList      *lines;
-    CxList      *current;
-    int         columnspacing;
-    int         rowspacing;
-};
-
-struct UiTabViewContainer {
-    UiContainer container;
-    UiContext   *context;
-    Widget      widget;
-    CxList      *tabs;
-    Widget      current;
+struct UiContainerPrivate {
+    UiContainerX container;
+    Widget       (*prepare)(UiContainerPrivate*, Arg *, int*);
+    void         (*add)(UiContainerPrivate*, Widget);
+    Widget       widget;
+    UiLayout     layout;
 };
 
-struct MotifTabbedPane {
-    UiTabbedPane view;
-    Widget       tabbar;
-    CxList       *tabs;
-    UiTab        *current;
-    int          index;
-    Pixel        bg1;
-    Pixel        bg2;
-    int          height;
-};
-
-struct UiTab {
-    MotifTabbedPane *tabbedpane;
-    UiObject        *content;
-    Widget          tab_button;
-};
-    
-
-UiContainer* ui_frame_container(UiObject *obj, Widget frame);
-Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_frame_container_add(UiContainer *ct, Widget widget);
+typedef struct UiBoxContainer {
+    UiContainerPrivate container;
+    Dimension n;
+} UiBoxContainer;
 
-UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation);
-Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_box_container_add(UiContainer *ct, Widget widget);
-
-UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing);
-Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_grid_container_add(UiContainer *ct, Widget widget);
+typedef struct UiGridContainer {
+    UiContainerPrivate container;
+    Dimension x;
+    Dimension y;
+} UiGridContainer;
 
-UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow);
-Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget);
+UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation);
+Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+void ui_box_container_add(UiContainerPrivate *ctn, Widget widget);
 
-UiContainer* ui_tabview_container(UiObject *obj, Widget rowcolumn);
-Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
-void ui_tabview_container_add(UiContainer *ct, Widget widget);
 
-void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d);
-void ui_change_tab(MotifTabbedPane *pane, UiTab *tab);
-
-void ui_tab_set_document(UiContext *ctx, void *document);
-void ui_tab_detach_document(UiContext *ctx);
-
+UiContainerX* ui_grid_container(UiObject *obj, Widget grid);
+Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/dnd.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/dnd.c	Thu Dec 12 20:01:43 2024 +0100
@@ -28,18 +28,3 @@
 
 #include "dnd.h"
 
-void ui_selection_settext(UiSelection *sel, char *str, int len) {
-    
-}
-
-void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
-    
-}
-
-char* ui_selection_gettext(UiSelection *sel) {
-    return NULL;
-}
-
-char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
-    return NULL;
-}
--- a/ui/motif/graphics.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/graphics.c	Thu Dec 12 20:01:43 2024 +0100
@@ -34,250 +34,3 @@
 #include "graphics.h"
 
 #include "container.h"
-
-static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) {
-    UiDrawEvent *drawevent = u;
-    //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c;
-    //XEvent *event = cbs->event;
-    Display *dpy = XtDisplay(widget);
-    
-    UiEvent ev;
-    ev.obj = drawevent->obj;
-    ev.window = drawevent->obj->window;
-    ev.document = drawevent->obj->ctx->document;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    XtVaGetValues(
-            widget,
-            XmNwidth,
-            &drawevent->gr.g.width,
-            XmNheight,
-            &drawevent->gr.g.height,
-            NULL);
-    
-    XGCValues gcvals;
-    gcvals.foreground = BlackPixelOfScreen(XtScreen(widget));
-    drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals);
-    
-    drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata);
-}
-
-UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    int n = 0;
-    Arg args[16];
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n);
-    
-    if(f) {
-        UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
-        event->obj = obj;
-        event->callback = f;
-        event->userdata = userdata;
-        
-        event->gr.display = XtDisplay(drawingarea);
-        event->gr.widget = drawingarea;
-        
-        Colormap colormap;
-        XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL);    
-        event->gr.colormap = colormap;
-        
-        XtAddCallback(
-                drawingarea,
-                XmNexposeCallback,
-                ui_drawingarea_expose,
-                event);
-        
-        XtVaSetValues(drawingarea, XmNuserData, event, NULL);
-    }
-    
-    XtManageChild(drawingarea);
-    return drawingarea;
-}
-
-static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) {
-    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
-    XEvent *xevent = cbs->event;
-    UiMouseEventData *event = u;
-    
-    if (cbs->reason == XmCR_INPUT) {
-        if (xevent->xany.type == ButtonPress) {
-            UiMouseEvent me;
-            me.x = xevent->xbutton.x;
-            me.y = xevent->xbutton.y;
-            // TODO: configurable double click time
-            me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2;
-            
-            UiEvent e;
-            e.obj = event->obj;
-            e.window = event->obj->window;
-            e.document = event->obj->ctx->document;
-            e.eventdata = &me;
-            e.intval = 0;
-            event->callback(&e, event->userdata);
-            
-            
-            event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time;
-        }
-    }
-    
-}
-
-void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
-    if(f) {
-        UiMouseEventData *event = malloc(sizeof(UiMouseEventData));
-        event->obj = obj;
-        event->callback = f;
-        event->userdata = u;
-        event->last_event = 0;
-        
-        XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event);
-    }
-}
-
-void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
-    XtVaGetValues(
-            drawingarea,
-            XmNwidth,
-            width,
-            XmNheight,
-            height,
-            NULL);
-}
-
-void ui_drawingarea_redraw(UIWIDGET drawingarea) {
-    //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True);
-    UiDrawEvent *event;
-    XtVaGetValues(drawingarea, XmNuserData, &event, NULL);
-    ui_drawingarea_expose(drawingarea, event, NULL);
-}
-
-
-/* -------------------- text layout functions -------------------- */
-UiTextLayout* ui_text(UiGraphics *g) {
-    UiTextLayout *text = malloc(sizeof(UiTextLayout));
-    memset(text, 0, sizeof(UiTextLayout));
-    text->text = NULL;
-    text->length = 0;
-    text->widget = ((UiXlibGraphics*)g)->widget;
-    text->fontset = NULL;
-    return text;
-}
-
-static void create_default_fontset(UiTextLayout *layout) {
-    char **missing = NULL;
-    int num_missing = 0;
-    char *def = NULL;
-    Display *dpy = XtDisplay(layout->widget);
-    XFontSet fs = XCreateFontSet(
-        dpy,
-        "-dt-interface system-medium-r-normal-s*utf*:,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u,"
-                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni,"
-                "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208",
-        &missing, &num_missing, &def);
-    layout->fontset = fs;
-}
-
-void ui_text_free(UiTextLayout *text) {
-    // TODO
-}
-
-void ui_text_setstring(UiTextLayout *layout, char *str) {
-    ui_text_setstringl(layout, str, strlen(str));
-}
-
-void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
-    layout->text = str;
-    layout->length = len;
-    layout->changed = 1;
-}
-
-void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
-    create_default_fontset(layout);//TODO
-    layout->changed = 1;
-}
-
-void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
-    if(layout->changed) {
-        XRectangle ext, lext;
-        XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext);
-        layout->width = ext.width;
-        layout->height = ext.height;
-        layout->changed = 0;
-    }
-    *width = layout->width;
-    *height = layout->height;
-}
-
-void ui_text_setwidth(UiTextLayout *layout, int width) {
-    layout->maxwidth = width;
-}
-
-
-/* -------------------- drawing functions -------------------- */
-
-void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    XColor color;
-    color.flags= DoRed | DoGreen | DoBlue; 
-    color.red = red * 257;
-    color.green = green * 257;
-    color.blue = blue * 257;
-    XAllocColor(gr->display, gr->colormap, &color);
-    XSetForeground(gr->display, gr->gc, color.pixel);
-}
-
-void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2);
-}
-
-void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    if(fill) {
-        XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
-    } else {
-        XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
-    }
-}
-
-void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
-    UiXlibGraphics *gr = (UiXlibGraphics*)g;
-    int width, height;
-    ui_text_getsize(text, &width, &height);
-    if(text->maxwidth > 0) {
-        XRectangle clip;
-        clip.x = x;
-        clip.y = y;
-        clip.width = text->maxwidth;
-        clip.height = height;
-        XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted);
-    }
-    
-    XmbDrawString(
-            gr->display,
-            XtWindow(gr->widget),
-            text->fontset,
-            gr->gc,
-            x,
-            y + height,
-            text->text,
-            text->length);
-    
-    XSetClipMask(gr->display, gr->gc, None);
-}
--- a/ui/motif/graphics.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/graphics.h	Thu Dec 12 20:01:43 2024 +0100
@@ -36,38 +36,6 @@
 extern "C" {
 #endif
 
-typedef struct UiXlibGraphics {
-    UiGraphics g;
-    Display    *display;
-    Widget     widget;
-    Colormap   colormap;
-    GC         gc;
-} UiXlibGraphics;
-
-typedef struct UiDrawEvent {
-    ui_drawfunc    callback;
-    UiObject       *obj;
-    void           *userdata;
-    UiXlibGraphics gr;
-} UiDrawEvent;
-
-typedef struct UiMouseEventData {
-    UiObject    *obj;
-    ui_callback callback;
-    void        *userdata;
-    Time        last_event;
-} UiMouseEventData;
-
-struct UiTextLayout {
-    char     *text;
-    size_t   length;
-    Widget   widget;
-    XFontSet fontset;
-    int      maxwidth;
-    int      width;
-    int      height;
-    int      changed;
-};
 
 
 #ifdef	__cplusplus
--- a/ui/motif/image.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/image.c	Thu Dec 12 20:01:43 2024 +0100
@@ -6,35 +6,3 @@
 
 #include "image.h"
 
-UiIcon* ui_icon(const char *name, int size) {
-    return NULL;
-}
-
-UiIcon* ui_icon_unscaled(const char *name, int size) {
-    return NULL;
-}
-
-void ui_free_icon(UiIcon *icon) {
-    
-}
-
-UiImage* ui_icon_image(UiIcon *icon) {
-    return NULL;
-}
-
-UiImage* ui_image(const char *filename) {
-    return NULL;
-}
-
-UiImage* ui_named_image(const char *filename, const char *name) {
-    return NULL;
-}
-
-UiImage* ui_load_image_from_path(const char *path, const char *name) {
-    return NULL;
-}
-
-void ui_free_image(UiImage *img) {
-    
-}
-
--- a/ui/motif/label.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/label.c	Thu Dec 12 20:01:43 2024 +0100
@@ -34,36 +34,3 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-UIWIDGET ui_label(UiObject *obj, char *label) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized(label);
-    
-    int n = 0;
-    Arg args[16]; 
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget widget = XmCreateLabel(parent, "label", args, n);
-    ct->add(ct, widget);  
-    XtManageChild(widget);
-    
-    return widget;
-}
-
-UIWIDGET ui_space(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    XmString str = XmStringCreateLocalized("");
-    
-    int n = 0;
-    Arg args[16]; 
-    XtSetArg(args[n], XmNlabelString, str);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget widget = XmCreateLabel(parent, "space_label", args, n);
-    ct->add(ct, widget);  
-    XtManageChild(widget);
-    
-    return widget;
-}
--- a/ui/motif/list.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/list.c	Thu Dec 12 20:01:43 2024 +0100
@@ -34,175 +34,3 @@
 #include "list.h"
 #include "../common/object.h"
 
-
-void* ui_strmodel_getvalue(void *elm, int column) {
-    return column == 0 ? elm : NULL;
-}
-
-
-UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    int count;
-    XmStringTable items = ui_create_stringlist(var->value, getvalue, &count);
-    
-    Arg args[8];
-    int n = 0;
-    XtSetArg(args[n], XmNitemCount, count);
-    n++;
-    XtSetArg(args[n], XmNitems, count == 0 ? NULL : items);
-    n++;
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget widget = XmCreateScrolledList(parent, "listview", args, n);
-    ct->add(ct, XtParent(widget));
-    XtManageChild(widget);
-    
-    UiListView *listview = cxMalloc(obj->ctx->allocator, sizeof(UiListView));
-    listview->widget = widget;
-    listview->list = var;
-    listview->getvalue = getvalue;
-    
-    for (int i=0;i<count;i++) {
-        XmStringFree(items[i]);
-    }
-    XtFree((char *)items);
-    
-    if(f) {
-        UiListViewEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiListViewEventData));
-        event->event.obj = obj;
-        event->event.userdata = udata;
-        event->event.callback = f;
-        event->event.value = 0;
-        event->var = var;
-        XtAddCallback(
-                widget,
-                XmNdefaultActionCallback,
-                (XtCallbackProc)ui_list_selection_callback,
-                event);
-    }
-    
-    return widget;
-}
-
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_listview_var(obj, var, getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = var->value;
-        return ui_listview_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-
-XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) { 
-    int num = list->count(list);
-    XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString));
-    void *data = list->first(list);
-    for(int i=0;i<num;i++) {
-        items[i] = XmStringCreateLocalized(getvalue(data, 0));
-        data = list->next(list);
-    }
-    
-    *count = num;
-    return items;
-}
-
-
-void ui_listview_update(UiEvent *event, UiListView *view) {
-    int count;
-    XmStringTable items = ui_create_stringlist(
-            view->list->value,
-            view->getvalue,
-            &count);
-    
-    XtVaSetValues(
-            view->widget,
-            XmNitems, count == 0 ? NULL : items,
-            XmNitemCount,
-            count,
-            NULL);
-    
-    for (int i=0;i<count;i++) {
-        XmStringFree(items[i]);
-    }
-    XtFree((char *)items);
-}
-
-void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data) {
-    XmListCallbackStruct *cbs = (XmListCallbackStruct *)data;
-    
-    UiEvent e;
-    e.obj = event->event.obj;
-    e.window = event->event.obj->window;
-    e.document = event->event.obj->ctx->document;
-    UiList *list = event->var->value;
-    e.eventdata = list->get(list, cbs->item_position - 1);
-    e.intval = cbs->item_position - 1;
-    event->event.callback(&e, event->event.userdata);
-}
-
-
-/* --------------------------- ComboBox ---------------------------  */
-
-UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
-    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
-}
-
-UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = list;
-    var->type = UI_VAR_SPECIAL;
-    return ui_combobox_var(obj, var, getvalue, f, udata);
-}
-
-UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = var->value;
-        return ui_combobox_var(obj, var, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
-    UiListView *listview = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiListView));
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    Arg args[16];
-    int n = 0;
-    XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_NONE);
-    n++;
-    XtSetArg(args[n], XmNtraversalOn, FALSE);
-    n++;
-    XtSetArg(args[n], XmNwidth, 160);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget combobox = XmCreateDropDownList(parent, "combobox", args, n);
-    XtManageChild(combobox);
-    listview->widget = combobox;
-    listview->list = var;
-    listview->getvalue = getvalue;
-    
-    ui_listview_update(NULL, listview);
-    
-    return parent;
-}
--- a/ui/motif/list.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/list.h	Thu Dec 12 20:01:43 2024 +0100
@@ -37,24 +37,7 @@
 extern "C" {
 #endif
 
-typedef struct UiListView {
-    Widget          widget;
-    UiVar           *list;
-    ui_getvaluefunc getvalue;
-} UiListView;
-
-typedef struct UiListViewEventData {
-    UiEventData event;
-    UiVar *var;
-} UiListViewEventData;
-
-void* ui_strmodel_getvalue(void *elm, int column);
-
-XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count);
-void ui_listview_update(UiEvent *event, UiListView *view);
-void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data);
-
-UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+    
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/menu.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/menu.c	Thu Dec 12 20:01:43 2024 +0100
@@ -41,444 +41,3 @@
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
 
-static ui_menu_add_f createMenuItem[] = {
-    /* UI_MENU                 */ add_menu_widget,
-    /* UI_MENU_SUBMENU         */ add_menu_widget,
-    /* UI_MENU_ITEM            */ add_menuitem_widget,
-    /* UI_MENU_STOCK_ITEM      */ add_menuitem_st_widget,
-    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
-    /* UI_MENU_CHECK_ITEM_NV   */ add_checkitemnv_widget,
-    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
-    /* UI_MENU_ITEM_LIST_NV    */ NULL, // TODO
-    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
-};
-
-// private menu functions
-void ui_create_menubar(UiObject *obj) {
-    UiMenu *menus = uic_get_menu_list();
-    if(!menus) {
-        return;
-    }
-    
-    Widget menubar = XmCreateMenuBar(obj->widget, "main_list", NULL, 0);
-    XtManageChild(menubar);
-    
-    UiMenu *menu = menus;
-    int menu_index = 0;
-    while(menu) {
-        menu_index += add_menu_widget(menubar, menu_index, &menu->item, obj);
-        
-        menu = (UiMenu*)menu->item.next;
-    }
-}
-
-int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
-    UiMenu *menu = (UiMenu*)item;
-    
-    Widget menuItem = XtVaCreateManagedWidget(
-            menu->label,
-            xmCascadeButtonWidgetClass,
-            parent,
-            NULL);
-    Widget m = XmVaCreateSimplePulldownMenu(parent, menu->label, i, NULL, NULL);
-    
-    UiMenuItemI *mi = menu->items_begin;
-    int menu_index = 0;
-    while(mi) {
-        menu_index += createMenuItem[mi->type](m, menu_index, mi, obj);
-        mi = mi->next;
-    }
-    
-    return 1;
-}
-
-int add_menuitem_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiMenuItem *mi = (UiMenuItem*)item;
-    
-    Arg args[1];
-    XmString label = XmStringCreateLocalized(mi->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    
-    Widget mitem = XtCreateManagedWidget(
-            "menubutton",
-            xmPushButtonWidgetClass,
-            parent,
-            args,
-            1);
-    XmStringFree(label);
-    
-    if(mi->callback != NULL) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = mi->userdata;
-        event->callback = mi->callback;
-        event->value = 0;
-        XtAddCallback(
-                mitem,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    if(mi->groups) {
-        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
-    }
-    
-    return 1;
-}
-
-int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
-    UiStMenuItem *mi = (UiStMenuItem*)item;
-    
-    UiStockItem *si = ui_get_stock_item(mi->stockid);
-    if(!si) {
-        fprintf(stderr, "UI Error: unknown stock id: %s\n", mi->stockid);
-        return 0;
-    }
-    
-    int n = 0;
-    Arg args[4];
-    XmString label = XmStringCreateLocalized(si->label);
-    XmString at = NULL;
-    
-    XtSetArg(args[n], XmNlabelString, label);
-    n++;
-    if(si->accelerator) {
-        XtSetArg(args[n], XmNaccelerator, si->accelerator);
-        n++;
-    }
-    if(si->accelerator_label) {
-        at = XmStringCreateLocalized(si->accelerator_label);
-        XtSetArg(args[n], XmNacceleratorText, at);
-        n++;
-    }
-    
-    Widget mitem = XtCreateManagedWidget(
-            "menubutton",
-            xmPushButtonWidgetClass,
-            parent,
-            args,
-            n);
-    XmStringFree(label);
-    if(at) {
-        XmStringFree(at);
-    }
-    
-    if(mi->callback != NULL) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = mi->userdata;
-        event->callback = mi->callback;
-        event->value = 0;
-        XtAddCallback(
-                mitem,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    if(mi->groups) {
-        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
-    }
-    
-    return 1;
-}
-
-int add_menuseparator_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    Widget s = XmCreateSeparatorGadget (parent, "menu_separator", NULL, 0);
-    XtManageChild(s);
-    return 1;
-}
-
-int add_checkitem_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiCheckItem *ci = (UiCheckItem*)item;
-    
-    Arg args[3];
-    XmString label = XmStringCreateLocalized(ci->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNvisibleWhenOff, 1);
-    Widget checkbox = XtCreateManagedWidget(
-            "menutogglebutton",
-            xmToggleButtonWidgetClass,
-            parent,
-            args,
-            2);
-    XmStringFree(label);
-    
-    if(ci->callback) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = ci->userdata;
-        event->callback = ci->callback;
-        XtAddCallback(
-            checkbox,
-            XmNvalueChangedCallback,
-            (XtCallbackProc)ui_toggle_button_callback,
-            event);
-    }
-    
-    return 1;
-}
-
-int add_checkitemnv_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiCheckItemNV *ci = (UiCheckItemNV*)item;
-    
-    Arg args[3];
-    XmString label = XmStringCreateLocalized(ci->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNvisibleWhenOff, 1);
-    Widget checkbox = XtCreateManagedWidget(
-            "menutogglebutton",
-            xmToggleButtonWidgetClass,
-            parent,
-            args,
-            2);
-    XmStringFree(label);
-    
-    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
-    if(var) {
-        UiInteger *value = var->value;
-        value->obj = checkbox;
-        value->get = ui_toggle_button_get;
-        value->set = ui_toggle_button_set;
-        value = 0;
-    } else {
-        // TODO: error
-    }
-    
-    return 1;
-}
-
-int add_menuitem_list_widget(
-        Widget parent,
-        int i,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiMenuItemList *il = (UiMenuItemList*)item;
-    
-    UiActiveMenuItemList *ls = cxMalloc(
-            obj->ctx->allocator,
-            sizeof(UiActiveMenuItemList));
-    
-    ls->object = obj;
-    ls->menu = parent;
-    ls->index = i;
-    ls->oldcount = 0;
-    ls->list = il->list;
-    ls->callback = il->callback;
-    ls->userdata = il->userdata;
-    
-    ls->list->observers = ui_add_observer(
-            ls->list->observers,
-            (ui_callback)ui_update_menuitem_list,
-            ls);
-    
-    ui_update_menuitem_list(NULL, ls);
-    
-    return 0;
-}
-
-void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
-    Arg args[4];
-    
-    // remove old items
-    if(list->oldcount > 0) {
-        Widget *children;
-        int nc;
-        
-        XtVaGetValues(
-                list->menu,
-                XmNchildren,
-                &children,
-                XmNnumChildren,
-                &nc,
-                NULL);
-        
-        for(int i=0;i<list->oldcount;i++) {
-            XtDestroyWidget(children[list->index + i]);
-        }
-    }
-    
-    char *str = ui_list_first(list->list);
-    if(str) {
-        // add separator
-        XtSetArg(args[0], XmNpositionIndex, list->index);
-        Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1);
-        XtManageChild(s);
-    }
-    int i = 1;
-    while(str) {
-        XmString label = XmStringCreateLocalized(str);
-        XtSetArg(args[0], XmNlabelString, label);
-        XtSetArg(args[1], XmNpositionIndex, list->index + i);
-
-        Widget mitem = XtCreateManagedWidget(
-                "menubutton",
-                xmPushButtonWidgetClass,
-                list->menu,
-                args,
-                2);
-        XmStringFree(label);
-        
-        if(list->callback) {
-            // TODO: use mempool
-            UiEventData *event = malloc(sizeof(UiEventData));
-            event->obj = list->object;
-            event->userdata = list->userdata;
-            event->callback = list->callback;
-            event->value = i - 1;
-
-            XtAddCallback(
-                mitem,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-        }
-        
-        str = ui_list_next(list->list);
-        i++;
-    }
-    
-    list->oldcount = i;
-}
-
-void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) {
-    UiEventData *event = udata;
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.intval = 0;
-    event->callback(&e, event->userdata);    
-}
-
-
-/*
- * widget menu functions
- */
-
-static void ui_popup_handler(Widget widget, XtPointer data,  XEvent *event, Boolean *c) {
-    Widget menu = data;
-    XmMenuPosition(menu, (XButtonPressedEvent *)event);
-    XtManageChild(menu);
-    
-    *c = FALSE;
-}
-
-UIMENU ui_contextmenu(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(ct->current) {
-        return ui_contextmenu_w(obj, ct->current);
-    } else {
-        return NULL; // TODO: warn
-    }
-}
-
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0);
-    ct->menu = menu;
-    
-    XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu);
-    
-    return menu;
-}
-
-void ui_contextmenu_popup(UIMENU menu) {
-    
-}
-
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
-    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
-}
-
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    CxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!groups) {
-            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
-        }
-        cxListAdd(groups, &group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    Arg args[4];
-    XmString labelstr = XmStringCreateLocalized(label);
-    XtSetArg(args[0], XmNlabelString, labelstr);
-    
-    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
-    XtManageChild(item);
-    XmStringFree(labelstr);
-}
-
-void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
-    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
-}
-
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    CxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!groups) {
-            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
-        }
-        cxListAdd(groups, &group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    UiStockItem *stockItem = ui_get_stock_item(stockid);
-    Arg args[4];
-    XmString labelstr = XmStringCreateLocalized(stockItem->label);
-    XtSetArg(args[0], XmNlabelString, labelstr);
-    
-    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
-    XtManageChild(item);
-    XmStringFree(labelstr);
-}
--- a/ui/motif/menu.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/menu.h	Thu Dec 12 20:01:43 2024 +0100
@@ -37,33 +37,6 @@
 #endif
 
 
-typedef struct UiActiveMenuItemList UiActiveMenuItemList;
-
-typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
-
-struct UiActiveMenuItemList {
-    UiObject     *object;
-    Widget       menu;
-    int          index;
-    int          oldcount;
-    UiList       *list;
-    ui_callback  callback;
-    void         *userdata;
-};
-
-void ui_create_menubar(UiObject *obj);
-
-int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuseparator_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_checkitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_checkitemnv_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-int add_menuitem_list_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
-
-void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
-void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata);
-
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/objs.mk	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/objs.mk	Thu Dec 12 20:01:43 2024 +0100
@@ -39,11 +39,11 @@
 MOTIFOBJ += label.o
 MOTIFOBJ += text.o
 MOTIFOBJ += list.o
-MOTIFOBJ += tree.o
 MOTIFOBJ += graphics.o
 MOTIFOBJ += range.o
 MOTIFOBJ += dnd.o
 MOTIFOBJ += image.o
+MOTIFOBJ += Grid.o
 
 TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
 TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- a/ui/motif/range.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/range.c	Thu Dec 12 20:01:43 2024 +0100
@@ -34,104 +34,3 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-
-static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    int n = 0;
-    Arg args[16];
-    XtSetArg(args[n], XmNorientation, orientation == UI_HORIZONTAL ? XmHORIZONTAL : XmVERTICAL);
-    n++;
-    XtSetArg (args[n], XmNmaximum, 10);
-    n++;
-    XtSetArg (args[n], XmNsliderSize, 1);
-    n++;
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget scrollbar = XmCreateScrollBar(parent, "scrollbar", args, n);
-    XtManageChild(scrollbar);
-    ct->add(ct, scrollbar);
-    
-    if(range) {
-        range->get = ui_scrollbar_get;
-        range->set = ui_scrollbar_set;
-        range->setrange = ui_scrollbar_setrange;
-        range->setextent = ui_scrollbar_setextent;
-        range->obj = scrollbar;
-    }
-    
-    if(f) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
-        event->value = 0;
-        XtAddCallback(
-                scrollbar,
-                XmNvalueChangedCallback,
-                (XtCallbackProc)ui_scrollbar_callback,
-                event);
-    }
-    
-    return scrollbar;
-}
-
-UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
-    return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
-}
-
-UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
-    return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
-}
-
-void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata) {
-    UiEvent e;
-    e.obj = event->obj;
-    e.window = event->obj->window;
-    e.document = event->obj->ctx->document;
-    e.intval = event->value;
-    event->callback(&e, event->userdata);
-}
-
-double ui_scrollbar_get(UiRange *range) {
-    int intval;
-    XtVaGetValues(
-            range->obj,
-            XmNvalue,
-            &intval,
-            NULL);
-    double value = (double)intval / 10;
-    range->value = value;
-    return value;
-}
-
-void   ui_scrollbar_set(UiRange *range, double value) {
-    XtVaSetValues(
-            range->obj,
-            XmNvalue,
-            (int)(value * 10),
-            NULL);
-    range->value = value;
-}
-
-void   ui_scrollbar_setrange(UiRange *range, double min, double max) {
-    XtVaSetValues(
-            range->obj,
-            XmNminimum,
-            (int)(min * 10),
-            XmNmaximum,
-            (int)(max * 10),
-            NULL);
-    range->min = min;
-    range->max = max;
-}
-
-void   ui_scrollbar_setextent(UiRange *range, double extent) {
-    XtVaSetValues(
-            range->obj,
-            XmNsliderSize,
-            (int)(extent * 10),
-            NULL);
-    range->extent = extent;
-}
--- a/ui/motif/range.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/range.h	Thu Dec 12 20:01:43 2024 +0100
@@ -36,11 +36,6 @@
 extern "C" {
 #endif
 
-void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata);
-double ui_scrollbar_get(UiRange *range);
-void   ui_scrollbar_set(UiRange *range, double value);
-void   ui_scrollbar_setrange(UiRange *range, double min, double max);
-void   ui_scrollbar_setextent(UiRange *range, double extent);
 
 
 #ifdef __cplusplus
--- a/ui/motif/stock.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/stock.c	Thu Dec 12 20:01:43 2024 +0100
@@ -33,44 +33,4 @@
 #include "../ui/properties.h"
 #include <cx/hash_map.h>
 
-static CxMap *stock_items;
 
-void ui_stock_init() {
-    stock_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64);
-    
-    ui_add_stock_item(UI_STOCK_NEW, "New", "Ctrl<Key>N", "Ctrl+N", NULL);
-    ui_add_stock_item(UI_STOCK_OPEN, "Open", "Ctrl<Key>O", "Ctrl+O", NULL);
-    ui_add_stock_item(UI_STOCK_SAVE, "Save", "Ctrl<Key>S", "Ctrl+S", NULL);
-    ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_CLOSE, "Close", "Ctrl<Key>W", "Ctrl+W", NULL);
-    ui_add_stock_item(UI_STOCK_UNDO, "Undo", "Ctrl<Key>Z", "Ctrl+Z", NULL);
-    ui_add_stock_item(UI_STOCK_REDO, "Redo", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_GO_BACK, "Back", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", NULL, NULL, NULL);
-    ui_add_stock_item(UI_STOCK_CUT, "Cut", "Ctrl<Key>X", "Ctrl+X", NULL);
-    ui_add_stock_item(UI_STOCK_COPY, "Copy", "Ctrl<Key>C", "Ctrl+C", NULL);
-    ui_add_stock_item(UI_STOCK_PASTE, "Paste", "Ctrl<Key>V", "Ctrl+V", NULL);
-    ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL);
-}
-
-void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) {
-    UiStockItem *i = malloc(sizeof(UiStockItem));
-    i->label = label;
-    i->accelerator = accelerator;
-    i->accelerator_label = accelerator_label;
-    // TODO: icon
-    
-    cxMapPut(stock_items, id, i);
-}
-
-UiStockItem* ui_get_stock_item(char *id) {
-    UiStockItem *item = cxMapGet(stock_items, id);
-    if(item) {
-        char *label = uistr_n(id);
-        if(label) {
-            item->label = label;
-        }
-    }
-    return item;
-}
--- a/ui/motif/stock.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/stock.h	Thu Dec 12 20:01:43 2024 +0100
@@ -35,18 +35,7 @@
 extern "C" {
 #endif
 
-typedef struct UiStockItem {
-    char *label;
-    char *accelerator;
-    char *accelerator_label;
-    // TODO: icon
-} UiStockItem;
-    
-void ui_stock_init();
 
-void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon);
-
-UiStockItem* ui_get_stock_item(char *id);
 
 #ifdef	__cplusplus
 }
--- a/ui/motif/text.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/text.c	Thu Dec 12 20:01:43 2024 +0100
@@ -32,476 +32,3 @@
 #include "text.h"
 #include "container.h"
 
-
-UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
-    UiContainer *ct = uic_get_current_container(obj);
-    int n = 0;
-    Arg args[16];
-    
-    //XtSetArg(args[n], XmNeditable, TRUE);
-    //n++;
-    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
-    n++;
-    
-    Widget parent = ct->prepare(ct, args, &n, TRUE);
-    Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
-    ct->add(ct, XtParent(text_area));
-    XtManageChild(text_area);
-    
-    UiTextArea *uitext = cxMalloc(
-            obj->ctx->allocator,
-            sizeof(UiTextArea));
-    uitext->ctx = obj->ctx;
-    uitext->last_selection_state = 0;
-    XtAddCallback(
-                text_area,
-                XmNmotionVerifyCallback,
-                (XtCallbackProc)ui_text_selection_callback,
-                uitext);
-    
-    // bind value
-    if(var->value) {
-        UiText *value = var->value;
-        if(value->value.ptr) {
-            XmTextSetString(text_area, value->value.ptr);
-            value->value.free(value->value.ptr);
-        }
-        
-        value->set = ui_textarea_set;
-        value->get = ui_textarea_get;
-        value->getsubstr = ui_textarea_getsubstr;
-        value->insert = ui_textarea_insert;
-        value->setposition = ui_textarea_setposition;
-        value->position = ui_textarea_position;
-        value->selection = ui_textarea_selection;
-        value->length = ui_textarea_length;
-        value->value.ptr = NULL;
-        value->obj = text_area;
-        
-        if(!value->undomgr) {
-            value->undomgr = ui_create_undomgr();
-        }
-        
-        XtAddCallback(
-                text_area,
-                XmNmodifyVerifyCallback,
-                (XtCallbackProc)ui_text_modify_callback,
-                var);
-    }
-    
-    return text_area;
-}
-
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    UiVar *var = malloc(sizeof(UiVar));
-    var->value = value;
-    var->type = UI_VAR_SPECIAL;
-    return ui_textarea_var(obj, var);
-}
-
-UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
-    if(var) {
-        return ui_textarea_var(obj, var);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-char* ui_textarea_get(UiText *text) {
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    char *str = XmTextGetString(text->obj);
-    text->value.ptr = str;
-    text->value.free = (ui_freefunc)XtFree;
-    return str;
-}
-
-void ui_textarea_set(UiText *text, const char *str) {
-    XmTextSetString(text->obj, str);
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    text->value.ptr = NULL;
-}
-
-char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-    int length = end - begin;
-    char *str = XtMalloc(length + 1);
-    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
-    text->value.ptr = str;
-    text->value.free = (ui_freefunc)XtFree;
-    return str;
-}
-
-void ui_textarea_insert(UiText *text, int pos, char *str) {
-    text->value.ptr = NULL;
-    XmTextInsert(text->obj, pos, str);
-    if(text->value.ptr) {
-        text->value.free(text->value.ptr);
-    }
-}
-
-void ui_textarea_setposition(UiText *text, int pos) {
-    XmTextSetInsertionPosition(text->obj, pos);
-}
-
-int ui_textarea_position(UiText *text) {
-    long begin;
-    long end;
-    XmTextGetSelectionPosition(text->obj, &begin, &end);
-    text->pos = begin;
-    return text->pos;
-}
-
-void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
-}
-
-int ui_textarea_length(UiText *text) {
-    return (int)XmTextGetLastPosition(text->obj);
-}
-
-
-void ui_text_set(UiText *text, char *str) {
-    if(text->set) {
-        text->set(text, str);
-    } else {
-        if(text->value.ptr) {
-            text->value.free(text->value.ptr);
-        }
-        text->value.ptr = XtNewString(str);
-        text->value.free = (ui_freefunc)XtFree;
-    }
-}
-
-char* ui_text_get(UiText *text) {
-    if(text->get) {
-        return text->get(text);
-    } else {
-        return text->value.ptr;
-    }
-}
-
-
-UiUndoMgr* ui_create_undomgr() {
-    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
-    mgr->begin = NULL;
-    mgr->end = NULL;
-    mgr->cur = NULL;
-    mgr->length = 0;
-    mgr->event = 1;
-    return mgr;
-}
-
-void ui_destroy_undomgr(UiUndoMgr *mgr) {
-    UiTextBufOp *op = mgr->begin;
-    while(op) {
-        UiTextBufOp *nextOp = op->next;
-        if(op->text) {
-            free(op->text);
-        }
-        free(op);
-        op = nextOp;
-    }
-    free(mgr);
-}
-
-void ui_text_selection_callback(
-        Widget widget,
-        UiTextArea *textarea,
-        XtPointer data)
-{
-    long left = 0;
-    long right = 0;
-    XmTextGetSelectionPosition(widget, &left, &right);
-    int sel = left < right ? 1 : 0;
-    if(sel != textarea->last_selection_state) {
-        if(sel) {
-            ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
-        } else {
-            ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
-        }
-    }
-    textarea->last_selection_state = sel;
-}
-
-void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
-    UiText *value = var->value;
-    if(!value->obj) {
-        // TODO: bug, fix
-        return;
-    }
-    if(!value->undomgr) {
-        value->undomgr = ui_create_undomgr();
-    }
-    
-    XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
-    int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
-    UiUndoMgr *mgr = value->undomgr;
-    if(!mgr->event) {
-        return;
-    }
-    
-    char *text = txv->text->ptr;
-    int length = txv->text->length;
-    
-    if(mgr->cur) {
-        UiTextBufOp *elm = mgr->cur->next;
-        if(elm) {
-            mgr->cur->next = NULL;
-            mgr->end = mgr->cur;
-            while(elm) {
-                elm->prev = NULL;   
-                UiTextBufOp *next = elm->next;
-                ui_free_textbuf_op(elm);
-                elm = next;
-            }
-        }
-        
-        UiTextBufOp *last_op = mgr->cur;
-        if(
-            last_op->type == UI_TEXTBUF_INSERT &&
-            ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
-        {
-            // append text to last op       
-            int ln = last_op->len;
-            char *newtext = malloc(ln + length + 1);
-            memcpy(newtext, last_op->text, ln);
-            memcpy(newtext+ln, text, length);
-            newtext[ln+length] = '\0';
-            
-            last_op->text = newtext;
-            last_op->len = ln + length;
-            last_op->end += length;
-            
-            return;
-        }
-    }
-    
-    char *str;
-    if(type == UI_TEXTBUF_INSERT) {
-        str = malloc(length + 1);
-        memcpy(str, text, length);
-        str[length] = 0;
-    } else {
-        length = txv->endPos - txv->startPos;
-        str = malloc(length + 1);
-        XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
-    }
-    
-    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
-    op->prev = NULL;
-    op->next = NULL;
-    op->type = type;
-    op->start = txv->startPos;
-    op->end = txv->endPos + 1;
-    op->len = length;
-    op->text = str;
-    
-    cx_linked_list_add(
-            (void**)&mgr->begin,
-            (void**)&mgr->end,
-            offsetof(UiTextBufOp, prev),
-            offsetof(UiTextBufOp, next),
-            op);
-    
-    mgr->cur = op;
-}
-
-int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
-    // return 1 if oldstr + newstr are one word
-    
-    int has_space = 0;
-    for(int i=0;i<oldlen;i++) {
-        if(oldstr[i] < 33) {
-            has_space = 1;
-            break;
-        }
-    }
-    
-    for(int i=0;i<newlen;i++) {
-        if(has_space && newstr[i] > 32) {
-            return 1;
-        }
-    }
-    
-    return 0;
-}
-
-void ui_free_textbuf_op(UiTextBufOp *op) {
-    if(op->text) {
-        free(op->text);
-    }
-    free(op);
-}
-
-
-void ui_text_undo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
-    
-    if(mgr->cur) {
-        UiTextBufOp *op = mgr->cur;
-        mgr->event = 0;
-        switch(op->type) {
-            case UI_TEXTBUF_INSERT: {
-                XmTextReplace(value->obj, op->start, op->end, "");
-                break;
-            }
-            case UI_TEXTBUF_DELETE: {
-                XmTextInsert(value->obj, op->start, op->text);
-                break;
-            }
-        }
-        mgr->event = 1;
-        mgr->cur = mgr->cur->prev;
-    }
-}
-
-void ui_text_redo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
-    
-    UiTextBufOp *elm = NULL;
-    if(mgr->cur) {
-        if(mgr->cur->next) {
-            elm = mgr->cur->next;
-        }
-    } else if(mgr->begin) {
-        elm = mgr->begin;
-    }
-    
-    if(elm) {
-        UiTextBufOp *op = elm;
-        mgr->event = 0;
-        switch(op->type) {
-            case UI_TEXTBUF_INSERT: {
-                XmTextInsert(value->obj, op->start, op->text);
-                break;
-            }
-            case UI_TEXTBUF_DELETE: {
-                XmTextReplace(value->obj, op->start, op->end, "");
-                break;
-            }
-        }
-        mgr->event = 1;
-        mgr->cur = elm;
-    }
-}
-
-
-/* ------------------------- textfield ------------------------- */
-
-static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
-    UiContainer *ct = uic_get_current_container(obj);
-    int n = 0;
-    Arg args[16];
-    XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
-    n++;
-    if(width > 0) {
-        XtSetArg(args[n], XmNcolumns, width / 2 + 1);
-        n++;
-    }
-    if(frameless) {
-        XtSetArg(args[n], XmNshadowThickness, 0);
-        n++;
-    }
-    if(password) {
-        // TODO
-    }
-    
-    Widget parent = ct->prepare(ct, args, &n, FALSE);
-    Widget textfield = XmCreateText(parent, "text_field", args, n);
-    ct->add(ct, textfield);
-    XtManageChild(textfield);
-    
-    // bind value
-    if(value) {
-        if(value->value.ptr) {
-            XmTextSetString(textfield, value->value.ptr);
-            value->value.free(value->value.ptr);
-        }
-        
-        value->set = ui_textfield_set;
-        value->get = ui_textfield_get;
-        value->value.ptr = NULL;
-        value->obj = textfield;
-    }
-    
-    return textfield;
-}
-
-static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
-    if(var) {
-        UiString *value = var->value;
-        return ui_textfield(obj, value);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, TRUE, FALSE, value);
-}
-
-UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
-}
-
-UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
-}
-
-UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
-}
-
-
-char* ui_textfield_get(UiString *str) {
-    if(str->value.ptr) {
-        str->value.free(str->value.ptr);
-    }
-    char *value = XmTextGetString(str->obj);
-    str->value.ptr = value;
-    str->value.free = (ui_freefunc)XtFree;
-    return value;
-}
-
-void ui_textfield_set(UiString *str, char *value) {
-    XmTextSetString(str->obj, value);
-    if(str->value.ptr) {
-        str->value.free(str->value.ptr);
-    }
-    str->value.ptr = NULL;
-}
-
--- a/ui/motif/text.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/text.h	Thu Dec 12 20:01:43 2024 +0100
@@ -37,54 +37,6 @@
 extern "C" {
 #endif
 
-#define UI_TEXTBUF_INSERT 0
-#define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp UiTextBufOp;
-struct UiTextBufOp {
-    UiTextBufOp *prev;
-    UiTextBufOp *next;
-    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
-    int  start;
-    int  end;
-    int  len;
-    char *text;
-};
-    
-typedef struct UiUndoMgr {
-    UiTextBufOp *begin;
-    UiTextBufOp *end;
-    UiTextBufOp *cur;
-    int         length;
-    int         event;
-} UiUndoMgr;
-
-typedef struct UiTextArea {
-    UiContext *ctx;
-    int last_selection_state;
-} UiTextArea;
-    
-char* ui_textarea_get(UiText *text);
-void ui_textarea_set(UiText *text, const char *str);
-char* ui_textarea_getsubstr(UiText *text, int begin, int end);
-void ui_textarea_insert(UiText *text, int pos, char *str);
-void ui_textarea_setposition(UiText *text, int pos);
-int ui_textarea_position(UiText *text);
-void ui_textarea_selection(UiText *text, int *begin, int *end);
-int ui_textarea_length(UiText *text);
-
-UiUndoMgr* ui_create_undomgr();
-void ui_destroy_undomgr(UiUndoMgr *mgr);
-void ui_text_selection_callback(
-        Widget widget,
-        UiTextArea *textarea,
-        XtPointer data);
-void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
-int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
-void ui_free_textbuf_op(UiTextBufOp *op);
-
-char* ui_textfield_get(UiString *str);
-void ui_textfield_set(UiString *str, char *value);
-
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/toolbar.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/toolbar.c	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -42,340 +42,3 @@
 
 #include "../common/context.h"
 
-static CxMap *toolbar_items;
-static CxList *defaults;
-
-void ui_toolbar_init() {
-    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    defaults = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-}
-
-void ui_toolitem(char *name, char *label, ui_callback f, void *userdata) {
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
-    item->label = label;
-    item->image = NULL;
-    item->callback = f;
-    item->userdata = userdata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    cxMapPut(toolbar_items, name, item);
-}
-
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgr(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    UiStToolItem *item = malloc(sizeof(UiStToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
-    item->stockid = stockid;
-    item->callback = f;
-    item->userdata = userdata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!item->groups) {
-            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
-        }
-        cxListAdd(item->groups, &group);
-    }
-    va_end(ap);
-    
-    cxMapPut(toolbar_items, name, item);
-}
-
-void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
-    // TODO
-    
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
-    item->label = label;
-    item->image = img;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    cxMapPut(toolbar_items, name, item);
-}
-
-
-void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
-    // TODO
-    
-    UiStToolItem *item = malloc(sizeof(UiStToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_toggle_widget;
-    item->stockid = stockid;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, udata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!item->groups) {
-            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
-        }
-        cxListAdd(item->groups, &group);
-    }
-    va_end(ap);
-    
-    cxMapPut(toolbar_items, name, item);
-}
-
-void ui_toolitem_toggle_imggr(char *name, char *label, char *img, ui_callback f, void *udata, ...) {
-    // TODO
-    
-    UiToolItem *item = malloc(sizeof(UiToolItem));
-    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
-    item->label = label;
-    item->image = img;
-    item->callback = f;
-    item->userdata = udata;
-    item->groups = NULL;
-    item->isimportant = FALSE;
-    
-    // add groups
-    va_list ap;
-    va_start(ap, udata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!item->groups) {
-            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
-        }
-        cxListAdd(item->groups, &group);
-    }
-    va_end(ap);
-    
-    cxMapPut(toolbar_items, name, item);
-}
-
-void ui_toolbar_combobox(
-        char *name,
-        UiList *list,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;  
-    cb->list = list;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    cxMapPut(toolbar_items, name, cb);
-}
-
-void ui_toolbar_combobox_str(
-        char *name,
-        UiList *list,
-        ui_callback f,
-        void *udata)
-{
-    ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
-}
-
-void ui_toolbar_combobox_nv(
-        char *name,
-        char *listname,
-        ui_getvaluefunc getvalue,
-        ui_callback f,
-        void *udata)
-{
-    UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
-    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;  
-    cb->listname = listname;
-    cb->getvalue = getvalue;
-    cb->callback = f;
-    cb->userdata = udata;
-    
-    cxMapPut(toolbar_items, name, cb);
-}
-
-
-void ui_toolbar_add_default(char *name) {
-    char *s = strdup(name);
-    cxListAdd(defaults, s);
-}
-
-Widget ui_create_toolbar(UiObject *obj, Widget parent) {
-    if(!defaults) {
-        return NULL;
-    }
-    
-    Arg args[8];
-    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtopAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
-    Widget frame = XmCreateFrame(parent, "toolbar_frame", args, 5);
-    
-    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
-    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
-    XtSetArg(args[2], XmNspacing, 1);
-    Widget toolbar = XmCreateRowColumn (frame, "toolbar", args, 3);
-    
-    CxIterator i = cxListIterator(defaults);
-    cx_foreach(char *, def, i) {
-        UiToolItemI *item = cxMapGet(toolbar_items, def);
-        if(item) {
-            item->add_to(toolbar, item, obj);
-        } else if(!strcmp(def, "@separator")) {
-            // TODO
-        } else {
-            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
-        }
-    }
-    
-    XtManageChild(toolbar);
-    XtManageChild(frame);
-    
-    return frame;
-}
-
-void add_toolitem_widget(Widget parent, UiToolItem *item, UiObject *obj) {
-    Arg args[4];
-    
-    XmString label = XmStringCreateLocalized(item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
-    
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_st_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
-    Arg args[8];
-    
-    UiStockItem *stock_item = ui_get_stock_item(item->stockid);
-     
-    XmString label = XmStringCreateLocalized(stock_item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
- 
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNactivateCallback,
-                (XtCallbackProc)ui_push_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_toggle_widget(Widget parent, UiToolItem *item, UiObject *obj) {
-    Arg args[8];
-    
-    XmString label = XmStringCreateLocalized(item->label);
-    XtSetArg(args[0], XmNlabelString, label);
-    XtSetArg(args[1], XmNshadowThickness, 1);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    XtSetArg(args[3], XmNindicatorOn, XmINDICATOR_NONE);
-    Widget button = XmCreateToggleButton(parent, "toolbar_toggle_button", args, 4);
-    
-    XmStringFree(label);
-    
-    if(item->callback) {
-        UiEventData *event = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = item->userdata;
-        event->callback = item->callback;
-        XtAddCallback(
-                button,
-                XmNvalueChangedCallback,
-                (XtCallbackProc)ui_toggle_button_callback,
-                event);
-    }
-    
-    XtManageChild(button);
-    
-    if(item->groups) {
-        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
-    }
-}
-
-void add_toolitem_st_toggle_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
-    
-}
-
-void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj) {
-    UiListView *listview = cxMalloc(
-                obj->ctx->allocator,
-                sizeof(UiListView));
-    
-    UiVar *var = cxMalloc(obj->ctx->allocator, sizeof(UiVar));
-    var->value = item->list;
-    var->type = UI_VAR_SPECIAL;
-    
-    Arg args[8];
-    XtSetArg(args[0], XmNshadowThickness, 1);
-    XtSetArg(args[1], XmNindicatorOn, XmINDICATOR_NONE);
-    XtSetArg(args[2], XmNtraversalOn, FALSE);
-    XtSetArg(args[3], XmNwidth, 120);
-    Widget combobox = XmCreateDropDownList(tb, "toolbar_combobox", args, 4);
-    XtManageChild(combobox);
-    listview->widget = combobox;
-    listview->list = var;
-    listview->getvalue = item->getvalue;
-    
-    ui_listview_update(NULL, listview);
-    
-    if(item->callback) {
-        // TODO:
-        
-    }
-}
-
-void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj) {
-    
-}
--- a/ui/motif/toolbar.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/toolbar.h	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -37,66 +37,6 @@
 extern "C" {
 #endif
 
-typedef struct UiToolItemI    UiToolItemI;
-typedef struct UiToolItem     UiToolItem;
-typedef struct UiStToolItem   UiStToolItem;
-
-typedef struct UiToolbarComboBox   UiToolbarComboBox;
-typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
-
-typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*);
-
-struct UiToolItemI {
-    ui_toolbar_add_f  add_to;
-};
-
-struct UiToolItem {
-    UiToolItemI item;
-    char           *label;
-    void           *image;
-    ui_callback    callback;
-    void           *userdata;
-    CxList         *groups;
-    Boolean        isimportant;
-};
-
-struct UiStToolItem {
-    UiToolItemI    item;
-    char           *stockid;
-    ui_callback    callback;
-    void           *userdata;
-    CxList         *groups;
-    Boolean        isimportant;
-};
-
-struct UiToolbarComboBox {
-    UiToolItemI         item;
-    UiList              *list;
-    ui_getvaluefunc     getvalue;
-    ui_callback         callback;
-    void                *userdata;
-};
-
-struct UiToolbarComboBoxNV {
-    UiToolItemI         item;
-    char                *listname;
-    ui_getvaluefunc     getvalue;
-    ui_callback         callback;
-    void                *userdata;
-};
-
-void ui_toolbar_init();
-
-Widget ui_create_toolbar(UiObject *obj, Widget parent);
-
-void add_toolitem_widget(Widget tb, UiToolItem *item, UiObject *obj);
-void add_toolitem_st_widget(Widget tb, UiStToolItem *item, UiObject *obj);
-void add_toolitem_toggle_widget(Widget tb, UiToolItem *item, UiObject *obj);
-void add_toolitem_st_toggle_widget(Widget tb, UiStToolItem *item, UiObject *obj);
-
-void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj);
-void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj);
-
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/toolkit.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/toolkit.c	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -33,15 +33,21 @@
 
 #include "toolkit.h"
 #include "toolbar.h"
+#include "container.h"
 #include "stock.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
 #include "../common/document.h"
 #include "../common/properties.h"
 #include <cx/buffer.h>
 
+#include <X11/Intrinsic.h>
+#include <Xm/CutPaste.h>
+
 static XtAppContext app;
 static Display *display;
 static Widget active_window;
-static char *application_name;
+static const char *application_name;
 
 static ui_callback   startup_func;
 static void          *startup_data;
@@ -68,6 +74,11 @@
         "*rt*fontType: FONT_IS_XFT",
         "*rt*fontName: Sans",
         "*rt*fontSize: 11",
+        
+        "*window_frame.shadowType: SHADOW_ETCHED_OUT",
+        "*window_frame.shadowThickness: 1",
+        "*togglebutton.shadowThickness: 1",
+        "*togglebutton.highlightThickness: 2",
 	NULL
 };
 
@@ -76,8 +87,9 @@
     read(event_pipe[0], &ptr, sizeof(void*));
 }
 
-void ui_init(char *appname, int argc, char **argv) { 
+void ui_init(const char *appname, int argc, char **argv) { 
     application_name = appname;
+    uic_init_global_context();
     
     XtToolkitInitialize();
     XtSetLanguageProc(NULL, NULL, NULL);
@@ -85,15 +97,10 @@
     XtAppSetFallbackResources(app, fallback);
     
     display =  XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv);
-    char **missing = NULL;
-    int nm = 0;
-    char *def = NULL;
-    XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def);
     
     uic_docmgr_init();
-    ui_toolbar_init();
-    ui_stock_init();
-    
+    uic_menu_init();
+    uic_toolbar_init();
     uic_load_app_properties();
     
     if(pipe(event_pipe)) {
@@ -108,11 +115,11 @@
             NULL);
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
-Display* ui_get_display() {
+Display* ui_motif_get_display() {
     return display;
 }
 
@@ -157,11 +164,12 @@
 void ui_show(UiObject *obj) {
     uic_check_group_widgets(obj->ctx);
     XtRealizeWidget(obj->widget);
-    ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if
 }
 
-// implemented in window.c
-//void ui_close(UiObject *obj)
+void ui_close(UiObject *obj) {
+    
+}
+
 
 void ui_set_enabled(UIWIDGET widget, int enabled) {
     XtSetSensitive(widget, enabled);
@@ -182,17 +190,16 @@
 }
 
 static Boolean ui_job_finished(void *data) {
-    printf("WorkProc\n");
     UiJob *job = data;
-    
-    UiEvent event;
-    event.obj = job->obj;
-    event.window = job->obj->window;
-    event.document = job->obj->ctx->document;
-    event.intval = 0;
-    event.eventdata = NULL;
-
-    job->finish_callback(&event, job->finish_data);
+    if(job->finish_callback) {
+        UiEvent event;
+        event.obj = job->obj;
+        event.window = job->obj->window;
+        event.document = job->obj->ctx->document;
+        event.intval = 0;
+        event.eventdata = NULL;
+        job->finish_callback(&event, job->finish_data);
+    }
     free(job);
     return TRUE;
 }
@@ -201,7 +208,6 @@
     UiJob *job = data;
     int result = job->job_func(job->job_data);
     if(!result) {
-        printf("XtAppAddWorkProc\n");
         write(event_pipe[1], &job, sizeof(void*)); // hack
         XtAppAddWorkProc(app, ui_job_finished, job);
         
@@ -221,7 +227,6 @@
 }
 
 void ui_clipboard_set(char *str) {
-    printf("copy: {%s}\n", str);
     int length = strlen(str) + 1;
     
     Display *dp = XtDisplayOfObject(active_window);
@@ -287,6 +292,9 @@
     return active_window;
 }
 
+/*
+ * doesn't work with gnome anymore
+ */
 void ui_window_dark_theme(Display *dp, Window window) {
     Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False);
     Atom type = XInternAtom(dp, "UTF8_STRING", False);
@@ -300,3 +308,23 @@
             (const unsigned char*)"dark",
             4);
 }
+
+void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d) {
+    free(data);
+}
+
+void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) {
+    if(!groups) {
+        return;
+    }
+    size_t ngroups = uic_group_array_size(groups);
+    ui_set_widget_ngroups(ctx, widget, groups, ngroups);
+}
+
+void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups) {
+    if(ngroups > 0) {
+        uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);
+        ui_set_enabled(widget, FALSE);
+    }
+}
+
--- a/ui/motif/toolkit.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/toolkit.h	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -38,8 +38,6 @@
 extern "C" {
 #endif
 
-Display* ui_get_display();
-
 typedef struct UiEventData {
     UiObject    *obj;
     ui_callback callback;
@@ -47,6 +45,31 @@
     int         value;
 } UiEventData;
 
+typedef struct UiEventDataExt {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    ui_callback callback2;
+    void        *userdata2;
+    int         value0;
+    int         value1;
+    int         value2;
+    int         value3;
+    void        *customdata0;
+    void        *customdata1;
+    void        *customdata2;
+    void        *customdata3;
+} UiEventDataExt;
+
+typedef struct UiVarEventData {
+    UiObject    *obj;
+    UiVar       *var;
+    UiObserver  **observers;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+} UiVarEventData;
+
 typedef struct UiJob {
     UiObject      *obj;
     ui_threadfunc job_func;
@@ -60,12 +83,19 @@
 
 void ui_exit_mainloop();
 
+Display* ui_motif_get_display(void);
+
 void ui_set_active_window(Widget w);
 Widget ui_get_active_window();
 
 void ui_secondary_event_loop(int *loop);
 void ui_window_dark_theme(Display *dp, Window window);
 
+void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d);
+
+void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ;
+void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/window.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/window.c	Thu Dec 12 20:01:43 2024 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -36,6 +36,8 @@
 #include "../ui/window.h"
 #include "../common/context.h"
 
+#include "Grid.h"
+
 #include <cx/mempool.h>
 
 static int nwindows = 0;
@@ -45,17 +47,7 @@
 
 static void window_close_handler(Widget window, void *udata, void *cdata) {
     UiObject *obj = udata;
-    UiEvent ev;
-    ev.window = obj->window;
-    ev.document = obj->ctx->document;
-    ev.obj = obj;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    if(obj->ctx->close_callback) {
-        obj->ctx->close_callback(&ev, obj->ctx->close_data);
-    }
-    // TODO: free UiObject
+    uic_object_destroy(obj);
     
     nwindows--;
     if(nwindows == 0) {
@@ -63,7 +55,8 @@
     }
 }
 
-static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+
+static UiObject* create_window(const char *title, void *window_data, Boolean simple) {
     CxMempool *mp = cxBasicMempoolCreate(256);
     const CxAllocator *a = mp->allocator;
     UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
@@ -72,23 +65,20 @@
     
     Arg args[16];
     int n = 0;
-    
-    XtSetArg(args[0], XmNtitle, title);
-    //XtSetArg(args[1], XmNbaseWidth, window_default_width);
-    //XtSetArg(args[2], XmNbaseHeight, window_default_height);
-    XtSetArg(args[1], XmNminWidth, 100);
-    XtSetArg(args[2], XmNminHeight, 50);
-    XtSetArg(args[3], XmNwidth, window_default_width);
-    XtSetArg(args[4], XmNheight, window_default_height);
+    XtSetArg(args[n], XmNtitle, title); n++;
+    XtSetArg(args[n], XmNminWidth, 100); n++;
+    XtSetArg(args[n], XmNminHeight, 50); n++;
+    XtSetArg(args[n], XmNwidth, window_default_width); n++;
+    XtSetArg(args[n], XmNheight, window_default_height); n++;
     
     Widget toplevel = XtAppCreateShell(
-            "Test123",
-            "abc",
+            ui_appname(),
+            "mainwindow",
             //applicationShellWidgetClass,
             vendorShellWidgetClass,
-            ui_get_display(),
+            ui_motif_get_display(),
             args,
-            5);
+            n);
     
     Atom wm_delete_window;
     wm_delete_window = XmInternAtom(
@@ -101,111 +91,30 @@
             window_close_handler,
             obj);
     
-    // TODO: use callback
-    ui_set_active_window(toplevel);
-    
     Widget window = XtVaCreateManagedWidget(
             title,
             xmMainWindowWidgetClass,
             toplevel,
             NULL);
-    obj->widget = window;
-    Widget form = XtVaCreateManagedWidget(
-            "window_form",
-            xmFormWidgetClass,
-            window,
-            NULL);
-    Widget toolbar = NULL;
     
-    if(!simple) {
-        ui_create_menubar(obj);
-        toolbar = ui_create_toolbar(obj, form);
-    }
-    
-    // window content
-    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
-    XtSetArg(args[1], XmNshadowThickness, 0);
-    XtSetArg(args[2], XmNleftAttachment, XmATTACH_FORM);
-    XtSetArg(args[3], XmNrightAttachment, XmATTACH_FORM);
-    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
-    if(toolbar) {
-        XtSetArg(args[5], XmNtopAttachment, XmATTACH_WIDGET);
-        XtSetArg(args[6], XmNtopWidget, toolbar);
-        n = 7;
-    } else {
-        XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
-        n = 6;
-    }
-    Widget frame = XmCreateFrame(form, "content_frame", args, n);
+    // content frame
+    n = 0;
+    Widget frame = XmCreateFrame(window, "window_frame", args, n);
     XtManageChild(frame);
     
-    Widget content_form = XmCreateForm(frame, "content_form", NULL, 0);
-    XtManageChild(content_form);
-    obj->container = ui_box_container(obj, content_form, 0, 0, UI_BOX_VERTICAL);
+    Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, frame, NULL, 0);
+    UiContainerX *container = ui_box_container(obj, vbox, UI_BOX_VERTICAL);
+    uic_object_push_container(obj, container);
     
-    XtManageChild(form);
-      
     obj->widget = toplevel;
     nwindows++;
     return obj;
-}
+} 
 
-UiObject* ui_window(char *title, void *window_data) {
+UiObject* ui_window(const char *title, void *window_data) {
     return create_window(title, window_data, FALSE);
 }
 
-UiObject* ui_simplewindow(char *title, void *window_data) {
+UiObject* ui_simple_window(const char *title, void *window_data) {
     return create_window(title, window_data, TRUE);
 }
-
-void ui_close(UiObject *obj) {
-    XtDestroyWidget(obj->widget);
-    window_close_handler(obj->widget, obj, NULL);
-}
-
-typedef struct FileDialogData {
-    int  running;
-    char *file;
-} FileDialogData;
-
-static void filedialog_select(
-        Widget widget,
-        FileDialogData *data,
-        XmFileSelectionBoxCallbackStruct *selection)
-{
-    char *path = NULL;
-    XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &path);
-    data->running = 0;
-    data->file = strdup(path);
-    XtFree(path);
-    XtUnmanageChild(widget);
-}
-
-static void filedialog_cancel(
-        Widget widget,
-        FileDialogData *data,
-        XmFileSelectionBoxCallbackStruct *selection)
-
-{
-    data->running = 0;
-    XtUnmanageChild(widget);
-}
-
-char* ui_openfiledialog(UiObject *obj) {
-    Widget dialog = XmCreateFileSelectionDialog(obj->widget, "openfiledialog", NULL, 0);
-    XtManageChild(dialog);
-    
-    FileDialogData data;
-    data.running = 1;
-    data.file = NULL;
-    
-    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, &data);
-    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, &data);
-    
-    ui_secondary_event_loop(&data.running);
-    return data.file;
-}
-
-char* ui_savefiledialog(UiObject *obj) {
-    return ui_openfiledialog(obj);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/window.h	Thu Dec 12 20:01:43 2024 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+
--- a/ui/ui/container.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/ui/container.h	Thu Dec 12 20:01:43 2024 +0100
@@ -141,6 +141,20 @@
     int alt_spacing;
 } UiHeaderbarArgs;
 
+typedef struct UiSidebarArgs {
+    const char *name;
+    const char *style_class;
+    int margin;
+    int spacing;
+} UiSidebarArgs;
+
+
+struct UiContainerX {
+    void *container;
+    int close;
+    UiContainerX *prev;
+    UiContainerX *next;
+};
 
 
 #define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
@@ -153,6 +167,7 @@
 #define ui_scrolledwindow(obj, ...) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_tabview(obj, ...) for(ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_sidebar(obj, ...) for(ui_sidebar_create(obj, (UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 
 #define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
@@ -162,6 +177,7 @@
 #define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_tabview0(obj) for(ui_tabview_create(obj, (UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_sidebar0(obj) for(ui_sidebar_create(obj, (UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 
 #define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
 
@@ -169,7 +185,8 @@
 #define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
 
-UIEXPORT void ui_end(UiObject *obj);
+UIEXPORT void ui_end(UiObject *obj); // deprecated
+UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end
     
 UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args);
 UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args);
@@ -189,10 +206,8 @@
 UIEXPORT void ui_headerbar_center_create(UiObject *obj);
 UIEXPORT void ui_headerbar_end_create(UiObject *obj);
 
+UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args);
 
-UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj); // TODO
-
-UIEXPORT UIWIDGET ui_sidebar(UiObject *obj); // TODO
 
 UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
 UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
--- a/ui/ui/text.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/ui/text.h	Thu Dec 12 20:01:43 2024 +0100
@@ -71,6 +71,8 @@
     const char *varname;
     ui_callback onchange;
     void *onchangedata;
+    ui_callback onactivate;
+    void *onactivatedata;
     
     const int *groups;
 } UiTextFieldArgs;
--- a/ui/ui/toolkit.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/ui/toolkit.h	Thu Dec 12 20:01:43 2024 +0100
@@ -33,14 +33,13 @@
 
 #ifdef UI_COCOA
 
+#include <stdlib.h>
 #ifdef __OBJC__
 #import <Cocoa/Cocoa.h>
-#define UIWIDGET NSView*
-#define UIMENU   NSMenu*
-#else
-typedef void* UIWIDGET;
-typedef void* UIMENU;
 #endif
+typedef void* UIWIDGET; // NSView*
+typedef void* UIWINDOW; // NSWindow*
+typedef void* UIMENU;   // NSMenu*
 
 #elif UI_GTK2 || UI_GTK3 || UI_GTK4
 
@@ -152,9 +151,10 @@
 #define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 }
     
 /* public types */
-typedef int UiBool;
+typedef _Bool UiBool;
 
 typedef struct UiObject     UiObject;
+typedef struct UiContainerX UiContainerX;
 typedef struct UiEvent      UiEvent;
 typedef struct UiMouseEvent UiMouseEvent;
 typedef struct UiObserver   UiObserver;
@@ -215,7 +215,7 @@
      */
     UIWIDGET    widget;
 
-#ifdef UI_WINUI
+#if defined(UI_COCOA) || defined(UI_WINUI)
     /*
      * native window object 
      */
@@ -233,11 +233,18 @@
     UiContext   *ctx;
     
     /*
-     * container interface
+     * container interface (deprecated)
      */
     UiContainer *container;
     
     /*
+     * container list
+     * TODO: remove old UiContainer and rename UiContainerX to UiContainer
+     */
+    UiContainerX *container_begin;
+    UiContainerX *container_end;
+    
+    /*
      * next container object
      */
     UiObject    *next;
@@ -434,13 +441,13 @@
 UIEXPORT void ui_context_destroy(UiContext *ctx);
 
 UIEXPORT void ui_object_ref(UiObject *obj);
-UIEXPORT void ui_object_unref(UiObject *obj);
+UIEXPORT int ui_object_unref(UiObject *obj);
 
 UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
 UIEXPORT void ui_onopen(ui_callback f, void *userdata);
 UIEXPORT void ui_onexit(ui_callback f, void *userdata);
 
-UIEXPORT void ui_main();
+UIEXPORT void ui_main(void);
 UIEXPORT void ui_show(UiObject *obj);
 UIEXPORT void ui_close(UiObject *obj);
 
--- a/ui/ui/tree.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/ui/tree.h	Thu Dec 12 20:01:43 2024 +0100
@@ -35,11 +35,15 @@
 extern "C" {
 #endif
 
-typedef struct UiModel         UiModel;
-typedef struct UiListCallbacks UiListCallbacks;
-typedef struct UiListDnd       UiListDnd;
+typedef struct UiModel          UiModel;
+typedef struct UiListCallbacks  UiListCallbacks;
+typedef struct UiListDnd        UiListDnd;
 
-typedef struct UiListArgs      UiListArgs;
+typedef struct UiListArgs       UiListArgs;
+typedef struct UiSourceListArgs UiSourceListArgs;
+
+typedef struct UiSubList        UiSubList;
+typedef struct UiSubListItem    UiSubListItem;
 
 typedef enum UiModelType {
     UI_STRING = 0,
@@ -130,6 +134,81 @@
     const int *groups;
 };
 
+typedef void (*ui_sublist_getvalue_func)(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item);
+
+struct UiSubList {
+    UiList *value;
+    const char *varname;
+    const char *header;
+    UiBool separator;
+    void *userdata;
+};
+
+/*
+ * list item members must be filled by the sublist getvalue func
+ * all members must be allocated (by malloc, strdup, ...) the pointer
+ * will be passed to free
+ */
+struct UiSubListItem {
+    char *icon;
+    char *label;
+    char *button_icon;
+    char *button_label;
+    char *badge;
+    void *eventdata;
+};
+
+struct UiSourceListArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    const int *groups;
+    
+    /*
+     * list of sublists
+     * a sublist must have a varname or a value
+     * 
+     * the last entry in the list must contain all NULL values or numsublists
+     * must contain the number of sublists
+     */
+    UiSubList *sublists;
+    /*
+     * optional number of sublists
+     * if the value is 0, it is assumed, that sublists is null-terminated
+     * (last item contains only NULL values)
+     */
+    size_t numsublists;
+    
+    /*
+     * callback for each list item, that should fill all necessary
+     * UiSubListItem fields
+     */
+    ui_sublist_getvalue_func getvalue;
+    
+    /*
+     * activated when a list item is selected
+     */
+    ui_callback onactivate;
+    void        *onactivatedata;
+    
+    /*
+     * activated, when the additional list item button is clicked
+     */
+    ui_callback onbuttonclick;
+    void        *onbuttonclickdata;
+};
+
+#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ }
+#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} }
+
+
 UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
 UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
 UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
@@ -138,16 +217,14 @@
 #define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } )
 #define ui_combobox(obj, ...) ui_combobox_create(obj, (UiListArgs) { __VA_ARGS__ } )
 #define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, (UiSourceListArgs) { __VA_ARGS__ } )
 
 UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args);
 UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args);
 UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args);
 UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args);
 
-void ui_table_dragsource_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
-void ui_table_dragsource_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
-void ui_table_dragdest_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
-void ui_table_dragdest_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args);
 
 
 #ifdef	__cplusplus
--- a/ui/ui/window.h	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/ui/window.h	Thu Dec 12 20:01:43 2024 +0100
@@ -72,9 +72,10 @@
     void *onclickdata;
 } UiDialogWindowArgs;
 
-UIEXPORT UiObject* ui_window(const char *title, void *window_data);
-UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data);
-UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
+UIEXPORT UiObject *ui_window(const char *title, void *window_data);
+UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data);
+UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data);
+UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
 
 #define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ });
 #define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 });

mercurial