update uwproj, toolkit, libidav

2 weeks ago

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 05 Apr 2025 16:46:11 +0200 (2 weeks ago)
changeset 103
6606616eca9f
parent 102
64ded9f6a6c6
child 104
6bef2d77ad9a

update uwproj, toolkit, libidav

application/davcontroller.c file | annotate | diff | comparison | revisions
configure file | annotate | diff | comparison | revisions
libidav/crypto.c file | annotate | diff | comparison | revisions
libidav/crypto.h file | annotate | diff | comparison | revisions
libidav/methods.c file | annotate | diff | comparison | revisions
libidav/resource.c file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
make/configure.vm file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
make/uwproj.xsd file | annotate | diff | comparison | revisions
ui/Makefile file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.m file | annotate | diff | comparison | revisions
ui/cocoa/appdelegate.m file | annotate | diff | comparison | revisions
ui/cocoa/container.m file | annotate | diff | comparison | revisions
ui/cocoa/menu.h file | annotate | diff | comparison | revisions
ui/cocoa/menu.m file | annotate | diff | comparison | revisions
ui/cocoa/objs.mk file | annotate | diff | comparison | revisions
ui/cocoa/text.h file | annotate | diff | comparison | revisions
ui/cocoa/text.m file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.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/context.h file | annotate | diff | comparison | revisions
ui/common/objs.mk file | annotate | diff | comparison | revisions
ui/common/properties.c file | annotate | diff | comparison | revisions
ui/common/threadpool.c file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/container.h file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/image.h file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
ui/gtk/objs.mk file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/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/webview.h file | annotate | diff | comparison | revisions
ui/gtk/widget.c file | annotate | diff | comparison | revisions
ui/gtk/widget.h file | annotate | diff | comparison | revisions
ui/motif/button.c 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/list.c file | annotate | diff | comparison | revisions
ui/motif/objs.mk file | annotate | diff | comparison | revisions
ui/motif/text.c file | annotate | diff | comparison | revisions
ui/motif/toolkit.c file | annotate | diff | comparison | revisions
ui/motif/widget.c file | annotate | diff | comparison | revisions
ui/motif/window.c file | annotate | diff | comparison | revisions
ui/qt/Makefile file | annotate | diff | comparison | revisions
ui/qt/button.cpp file | annotate | diff | comparison | revisions
ui/qt/button.h file | annotate | diff | comparison | revisions
ui/qt/container.cpp file | annotate | diff | comparison | revisions
ui/qt/container.h file | annotate | diff | comparison | revisions
ui/qt/graphics.cpp file | annotate | diff | comparison | revisions
ui/qt/graphics.h file | annotate | diff | comparison | revisions
ui/qt/label.cpp file | annotate | diff | comparison | revisions
ui/qt/menu.cpp file | annotate | diff | comparison | revisions
ui/qt/menu.h file | annotate | diff | comparison | revisions
ui/qt/model.cpp file | annotate | diff | comparison | revisions
ui/qt/model.h file | annotate | diff | comparison | revisions
ui/qt/qt5.pro file | annotate | diff | comparison | revisions
ui/qt/stock.cpp file | annotate | diff | comparison | revisions
ui/qt/stock.h file | annotate | diff | comparison | revisions
ui/qt/text.cpp file | annotate | diff | comparison | revisions
ui/qt/text.h file | annotate | diff | comparison | revisions
ui/qt/toolbar.cpp file | annotate | diff | comparison | revisions
ui/qt/toolbar.h file | annotate | diff | comparison | revisions
ui/qt/toolkit.cpp file | annotate | diff | comparison | revisions
ui/qt/toolkit.h file | annotate | diff | comparison | revisions
ui/qt/tree.cpp file | annotate | diff | comparison | revisions
ui/qt/tree.h file | annotate | diff | comparison | revisions
ui/qt/widget.cpp file | annotate | diff | comparison | revisions
ui/qt/widget.h file | annotate | diff | comparison | revisions
ui/qt/window.cpp file | annotate | diff | comparison | revisions
ui/ui/container.h file | annotate | diff | comparison | revisions
ui/ui/display.h file | annotate | diff | comparison | revisions
ui/ui/image.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/ui.h file | annotate | diff | comparison | revisions
ui/ui/widget.h file | annotate | diff | comparison | revisions
ui/win32/Makefile file | annotate | diff | comparison | revisions
ui/win32/objs.mk file | annotate | diff | comparison | revisions
ui/win32/toolkit.c file | annotate | diff | comparison | revisions
ui/win32/toolkit.h file | annotate | diff | comparison | revisions
ui/winui/container.cpp file | annotate | diff | comparison | revisions
ui/winui/container.h file | annotate | diff | comparison | revisions
ui/winui/toolkit.cpp file | annotate | diff | comparison | revisions
ui/winui/window.cpp file | annotate | diff | comparison | revisions
--- a/application/davcontroller.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/application/davcontroller.c	Sat Apr 05 16:46:11 2025 +0200
@@ -76,7 +76,7 @@
         ui_list_append(browser->resources, res);
     }
 
-    browser->resources->update(browser->resources, 0);
+    ui_list_update(browser->resources);
     
     ui_set_group(ui->ctx, APP_STATE_BROWSER_SESSION);
 }
--- a/configure	Tue Feb 25 21:11:00 2025 +0100
+++ b/configure	Sat Apr 05 16:46:11 2025 +0200
@@ -1,5 +1,121 @@
 #!/bin/sh
 
+
+# some utility functions
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notisplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+    PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+    PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+    PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+    PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+    PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+
+
+# help text
+printhelp()
+{
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [$prefix]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+
+Build Types:
+--debug                 add extra compile flags for debug builds
+--release               add extra compile flags for release builds
+
+Options:
+  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+
+__EOF__
+}
+
 # create temporary directory
 TEMP_DIR=".tmp-`uname -n`"
 rm -Rf "$TEMP_DIR"
@@ -36,47 +152,6 @@
 
 # features
 
-# clean abort
-abort_configure()
-{
-    rm -Rf "$TEMP_DIR"
-    exit 1
-}
-
-# help text
-printhelp()
-{
-    echo "Usage: $0 [OPTIONS]..."
-    cat << __EOF__
-Installation directories:
-  --prefix=PREFIX         path prefix for architecture-independent files
-                          [/usr]
-  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
-                          [PREFIX]
-
-  --bindir=DIR            user executables [EPREFIX/bin]
-  --sbindir=DIR           system admin executables [EPREFIX/sbin]
-  --libexecdir=DIR        program executables [EPREFIX/libexec]
-  --sysconfdir=DIR        system configuration files [PREFIX/etc]
-  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
-  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
-  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
-  --libdir=DIR            object code libraries [EPREFIX/lib]
-  --includedir=DIR        C header files [PREFIX/include]
-  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
-  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
-  --infodir=DIR           info documentation [DATAROOTDIR/info]
-  --mandir=DIR            man documentation [DATAROOTDIR/man]
-  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
-
-Options:
-  --debug                 add extra compile flags for debug builds
-  --release               add extra compile flags for release builds
-  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
-
-__EOF__
-}
-
 #
 # parse arguments
 #
@@ -99,10 +174,11 @@
         "--infodir="*)        infodir=${ARG#--infodir=} ;;
         "--mandir"*)          mandir=${ARG#--mandir} ;;
         "--localedir"*)       localedir=${ARG#--localedir} ;;
-        "--help"*) printhelp; abort_configure ;;
-        "--debug")           BUILD_TYPE="debug" ;;
-        "--release")         BUILD_TYPE="release" ;;
+        "--help"*)            printhelp; abort_configure ;;
+        "--debug")            BUILD_TYPE="debug" ;;
+        "--release")          BUILD_TYPE="release" ;;
         "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+        "--toolkit")  echo "option '$ARG' needs a value:"; echo "  $ARG=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)"; abort_configure ;;
         "-"*) echo "unknown option: $ARG"; abort_configure ;;
     esac
 done
@@ -144,76 +220,6 @@
     echo ok
 fi
 
-# Test for availability of pkg-config
-PKG_CONFIG=`command -v pkg-config`
-: ${PKG_CONFIG:="false"}
-
-# Simple uname based platform detection
-# $PLATFORM is used for platform dependent dependency selection
-OS=`uname -s`
-OS_VERSION=`uname -r`
-printf "detect platform... "
-if [ "$OS" = "SunOS" ]; then
-    PLATFORM="solaris sunos unix svr4"
-elif [ "$OS" = "Linux" ]; then
-    PLATFORM="linux unix"
-elif [ "$OS" = "FreeBSD" ]; then
-    PLATFORM="freebsd bsd unix"
-elif [ "$OS" = "OpenBSD" ]; then
-    PLATFORM="openbsd bsd unix"
-elif [ "$OS" = "NetBSD" ]; then
-    PLATFORM="netbsd bsd unix"
-elif [ "$OS" = "Darwin" ]; then
-    PLATFORM="macos osx bsd unix"
-elif echo "$OS" | grep -i "MINGW" > /dev/null; then
-    PLATFORM="windows mingw"
-fi
-: ${PLATFORM:="unix"}
-
-PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
-echo "$PLATFORM_NAME"
-
-isplatform()
-{
-    for p in $PLATFORM
-    do
-        if [ "$p" = "$1" ]; then
-            return 0
-        fi
-    done
-    return 1
-}
-notisplatform()
-{
-    for p in $PLATFORM
-    do
-        if [ "$p" = "$1" ]; then
-            return 1
-        fi
-    done
-    return 0
-}
-istoolchain()
-{
-    for t in $TOOLCHAIN
-    do
-        if [ "$t" = "$1" ]; then
-            return 0
-        fi
-    done
-    return 1
-}
-notistoolchain()
-{
-    for t in $TOOLCHAIN
-    do
-        if [ "$t" = "$1" ]; then
-            return 1
-        fi
-    done
-    return 0
-}
-
 
 # generate vars.mk
 cat > "$TEMP_DIR/vars.mk" << __EOF__
@@ -694,9 +700,9 @@
 DEPENDENCIES_FAILED=
 ERROR=0
 # unnamed dependencies
-TEMP_CFLAGS=
-TEMP_CXXFLAGS=
-TEMP_LDFLAGS=
+TEMP_CFLAGS="$CFLAGS"
+TEMP_CXXFLAGS="$CXXFLAGS"
+TEMP_LDFLAGS="$LDFLAGS"
 while true
 do
     while true
@@ -721,6 +727,7 @@
         cat >> "$TEMP_DIR/make.mk" << __EOF__
 OBJ_EXT = .o
 LIB_EXT = .a
+LIB_PREFIX = lib
 PACKAGE_SCRIPT = package_osx.sh
 __EOF__
         break
@@ -741,6 +748,7 @@
         cat >> "$TEMP_DIR/make.mk" << __EOF__
 OBJ_EXT = .o
 LIB_EXT = .a
+LIB_PREFIX = lib
 PACKAGE_SCRIPT = package_unix.sh
 __EOF__
         break
@@ -1088,6 +1096,11 @@
             ERROR=1
             DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
         fi
+    else
+        echo
+        echo "Invalid option value - usage:"
+        echo "  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)"
+        abort_configure
     fi
 fi
 
--- a/libidav/crypto.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/libidav/crypto.c	Sat Apr 05 16:46:11 2025 +0200
@@ -66,7 +66,7 @@
 
 AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
     AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
-    SHA256_Init(&dec->sha256);
+    dav_sha256_init(&dec->sha256);
     dec->stream = stream;
     dec->write = write_func;
     dec->key = key;
@@ -122,7 +122,7 @@
     unsigned char *out = malloc(outlen);
     EVP_DecryptUpdate(dec->ctx, out, &outlen, buf, len);
     ssize_t wlen = dec->write(out, 1, outlen, dec->stream);
-    SHA256_Update(&dec->sha256, out, wlen);
+    dav_sha256_update(&dec->sha256, out, wlen);
     free(out);
     return (s*n) / s;
 }
@@ -133,7 +133,7 @@
         int len = 0;
         EVP_DecryptFinal_ex(dec->ctx, out, &len);
         dec->write(out, 1, len, dec->stream);
-        SHA256_Update(&dec->sha256, out, len);
+        dav_sha256_update(&dec->sha256, out, len);
         free(out);
         //EVP_CIPHER_CTX_cleanup(&dec->ctx);
         EVP_CIPHER_CTX_free(dec->ctx);
@@ -153,7 +153,7 @@
     }
     
     AESEncrypter *enc = malloc(sizeof(AESEncrypter));
-    SHA256_Init(&enc->sha256);
+    dav_sha256_init(&enc->sha256);
     enc->stream = stream;
     enc->read = read_func;
     enc->seek = seek_func;
@@ -200,7 +200,7 @@
     void *in = malloc(len);
     size_t in_len = enc->read(in, 1, len, enc->stream);
     
-    SHA256_Update(&enc->sha256, in, in_len);
+    dav_sha256_update(&enc->sha256, in, in_len);
     
     unsigned char *out = NULL;
     int outlen = 0;
@@ -357,33 +357,56 @@
 
 
 void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf){
-    SHA256_Final((unsigned char*)buf, sha256);
+    dav_sha256_final(sha256, (unsigned char*)buf);
 }
 
 char* dav_create_hash(const char *data, size_t len) {
     unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
     DAV_SHA_CTX ctx;
-    SHA256_Init(&ctx);
-    SHA256_Update(&ctx, data, len);
-    SHA256_Final(hash, &ctx);
+    dav_sha256_init(&ctx);
+    dav_sha256_update(&ctx, data, len);
+    dav_sha256_final(&ctx, hash);
     return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
 }
 
-DAV_SHA_CTX* dav_hash_init(void) {
+DAV_SHA_CTX* dav_sha256_create(void) {
     DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
-    SHA256_Init(ctx);
+    dav_sha256_init(ctx);
     return ctx;
 }
 
-void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
-    SHA256_Update(ctx, data, len);
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+
+void dav_sha256_init(DAV_SHA_CTX *ctx) {
+    SHA256_Init(ctx);
+}
+
+void dav_sha256_update(DAV_SHA_CTX *ctx, const void *data, size_t length) {
+    SHA256_Update(ctx, data, length);
+}
+
+void dav_sha256_final(char *md, DAV_SHA_CTX *ctx) {
+    SHA256_Final(md, ctx);
 }
 
-void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
-    SHA256_Final(buf, ctx);
-    free(ctx);
+#else
+
+void dav_sha256_init(DAV_SHA_CTX *ctx) {
+    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+    EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
+    *ctx = mdctx;
 }
 
+void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t length) {
+    EVP_DigestUpdate(*ctx, data, length);
+}
+
+void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *md) {
+    EVP_DigestFinal(*ctx, md, NULL);
+}
+
+#endif
+
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
 static int crypto_pw2key_error = 0;
 DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
@@ -819,17 +842,17 @@
     return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
 }
 
-DAV_SHA_CTX* dav_hash_init(void) {
+DAV_SHA_CTX* dav_sha256_create(void) {
     DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
     CC_SHA256_Init(ctx);
     return ctx;
 }
 
-void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
     CC_SHA256_Update(ctx, data, len);
 }
 
-void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
     CC_SHA256_Final(buf, ctx);
     free(ctx);
 }
@@ -1396,15 +1419,15 @@
 
 char* dav_create_hash(const char *data, size_t len) {
     unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
-    DAV_SHA_CTX *ctx = dav_hash_init();
+    DAV_SHA_CTX *ctx = dav_sha256_create();
     if(ctx) {
-        dav_hash_update(ctx, data, len);
-        dav_hash_final(ctx, hash);
+        dav_sha256_update(ctx, data, len);
+        dav_sha256_final(ctx, hash);
     }
     return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
 }
 
-DAV_SHA_CTX* dav_hash_init(void) {
+DAV_SHA_CTX* dav_sha256_create(void) {
     DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
     if(!ctx) {
         return NULL;
@@ -1416,11 +1439,11 @@
     return ctx;
 }
 
-void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
     BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0);
 }
 
-void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
     BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0);
     
     // cleanup
--- a/libidav/crypto.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/libidav/crypto.h	Sat Apr 05 16:46:11 2025 +0200
@@ -74,14 +74,20 @@
 #else
 /* unix/linux */
 
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
 #define DAV_USE_OPENSSL
 
 #define DAV_AES_CTX              EVP_CIPHER_CTX*
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
 #define DAV_SHA_CTX              SHA256_CTX
+#else
+#define DAV_SHA_CTX              EVP_MD_CTX*
+#endif
 #define DAV_SHA256_DIGEST_LENGTH 32
 
-#include <openssl/evp.h>
-#include <openssl/rand.h>
 
 #if defined(__sun) && defined(__SunOS_5_10)
 #include <sha2.h>
@@ -149,9 +155,10 @@
 
 char* dav_create_hash(const char *data, size_t len);
 
-DAV_SHA_CTX* dav_hash_init(void);
-void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
-void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf);
+void dav_sha256_init(DAV_SHA_CTX *ctx);
+DAV_SHA_CTX* dav_sha256_create(void);
+void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
+void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf);
 
 DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc);
 
--- a/libidav/methods.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/libidav/methods.c	Sat Apr 05 16:46:11 2025 +0200
@@ -1102,7 +1102,6 @@
     }
     
     curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL");
-    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
     curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
     
     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
@@ -1140,7 +1139,6 @@
     } else {
         curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MOVE");
     }
-    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
     curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
     
     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
--- a/libidav/resource.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/libidav/resource.c	Sat Apr 05 16:46:11 2025 +0200
@@ -823,11 +823,11 @@
 static size_t dav_read_h(void *buf, size_t size, size_t nelm, void *stream) {
     HashStream *s = stream;
     if(!s->sha) {
-        s->sha = dav_hash_init();
+        s->sha = dav_sha256_create();
     }
      
     size_t r = s->read(buf, size, nelm, s->stream);
-    dav_hash_update(s->sha, buf, r);
+    dav_sha256_update(s->sha, buf, r);
     return r;
 }
 
@@ -835,7 +835,7 @@
     HashStream *s = stream;
     if(offset == 0 && whence == SEEK_SET) {
         unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
-        dav_hash_final(s->sha, buf);
+        dav_sha256_final(s->sha, buf);
         s->sha = NULL;
     } else {
         s->error = 1;
@@ -938,7 +938,7 @@
                     data->length);
             
             if(hstr.sha) {
-                dav_hash_final(hstr.sha, (unsigned char*)data->hash);
+                dav_sha256_final(hstr.sha, (unsigned char*)data->hash);
                 char *hash = util_hexstr((unsigned char*)data->hash, 32);
                 dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
                 free(hash);
@@ -1115,7 +1115,6 @@
     
     curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
     curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
-    curl_easy_setopt(handle, CURLOPT_PUT, 0L);
     curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
     
     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_fnc);
@@ -1662,7 +1661,6 @@
     
     curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL);
     curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL);
-    curl_easy_setopt(in->c, CURLOPT_PUT, 0L);
     curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L);
     
     curl_multi_add_handle(in->m, in->c);
@@ -1800,7 +1798,6 @@
     curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL);
     curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL);
     curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL);
-    curl_easy_setopt(out->c, CURLOPT_PUT, 1L);
     curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L);
     curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc);
     curl_easy_setopt(out->c, CURLOPT_READDATA, stream);
--- a/libidav/utils.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/libidav/utils.c	Sat Apr 05 16:46:11 2025 +0200
@@ -1290,16 +1290,16 @@
         return NULL;
     }
     
-    DAV_SHA_CTX *sha = dav_hash_init();
+    DAV_SHA_CTX *sha = dav_sha256_create();
     char *buf = malloc(16384);
     
     size_t r;
     while((r = fread(buf, 1, 16384, in)) > 0) {
-        dav_hash_update(sha, buf, r);
+        dav_sha256_update(sha, buf, r);
     }
     
     unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
-    dav_hash_final(sha, hash);
+    dav_sha256_final(sha, hash);
     free(buf);
     fclose(in);
     
--- a/make/configure.vm	Tue Feb 25 21:11:00 2025 +0100
+++ b/make/configure.vm	Sat Apr 05 16:46:11 2025 +0200
@@ -1,5 +1,133 @@
 #!/bin/sh
 
+#set( $D = '$' )
+#[[
+# some utility functions
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notisplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+    PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+    PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+    PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+    PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+    PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+]]#
+
+# help text
+printhelp()
+{
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [${D}prefix]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+
+Build Types:
+--debug                 add extra compile flags for debug builds
+--release               add extra compile flags for release builds
+#if( $options.size() > 0 )
+
+Options:
+#foreach( $opt in $options )
+  --${opt.argument}=${opt.valuesString}
+#end
+#end
+#if( $features.size() > 0 )
+
+Optional Features:
+#foreach( $feature in $features )
+${feature.helpText}
+#end
+#end
+
+__EOF__
+}
+
 # create temporary directory
 TEMP_DIR=".tmp-`uname -n`"
 rm -Rf "$TEMP_DIR"
@@ -33,12 +161,23 @@
 mandir=
 
 # custom variables
-#foreach( $var in $vars )
-#if( $var.exec )
-${var.varName}=`${var.value}`
-#else
-${var.varName}="${var.value}"
+#foreach( $cfg in $config )
+if true \
+#if( $cfg.platform )
+    && isplatform "${cfg.platform}" \
+#end
+#foreach( $np in $cfg.notList )
+      && notisplatform "${np}" \
 #end
+      ; then
+    #foreach( $var in $cfg.vars )
+    #if( $var.exec )
+    ${var.varName}=`${var.value}`
+    #else
+    ${var.varName}="${var.value}"
+    #end
+    #end
+fi
 #end
 
 # features
@@ -48,63 +187,10 @@
 #end
 #end
 
-# clean abort
-abort_configure()
-{
-    rm -Rf "$TEMP_DIR"
-    exit 1
-}
-
-# help text
-printhelp()
-{
-    echo "Usage: $0 [OPTIONS]..."
-    cat << __EOF__
-Installation directories:
-  --prefix=PREFIX         path prefix for architecture-independent files
-                          [/usr]
-  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
-                          [PREFIX]
-
-  --bindir=DIR            user executables [EPREFIX/bin]
-  --sbindir=DIR           system admin executables [EPREFIX/sbin]
-  --libexecdir=DIR        program executables [EPREFIX/libexec]
-  --sysconfdir=DIR        system configuration files [PREFIX/etc]
-  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
-  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
-  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
-  --libdir=DIR            object code libraries [EPREFIX/lib]
-  --includedir=DIR        C header files [PREFIX/include]
-  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
-  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
-  --infodir=DIR           info documentation [DATAROOTDIR/info]
-  --mandir=DIR            man documentation [DATAROOTDIR/man]
-  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
-
-#if( $options.size() > 0 )
-Options:
-  --debug                 add extra compile flags for debug builds
-  --release               add extra compile flags for release builds
-#foreach( $opt in $options )
-  --${opt.argument}=${opt.valuesString}
-#end
-
-#end
-#if( $features.size() > 0 )
-Optional Features:
-#foreach( $feature in $features )
-${feature.helpText}
-#end
-
-#end
-__EOF__
-}
-
 #
 # parse arguments
 #
 BUILD_TYPE="default"
-#set( $D = '$' )
 for ARG in "$@"
 do
     case "$ARG" in
@@ -123,11 +209,12 @@
         "--infodir="*)        infodir=${D}{ARG#--infodir=} ;;
         "--mandir"*)          mandir=${D}{ARG#--mandir} ;;
         "--localedir"*)       localedir=${D}{ARG#--localedir} ;;
-        "--help"*) printhelp; abort_configure ;;
-        "--debug")           BUILD_TYPE="debug" ;;
-        "--release")         BUILD_TYPE="release" ;;
+        "--help"*)            printhelp; abort_configure ;;
+        "--debug")            BUILD_TYPE="debug" ;;
+        "--release")          BUILD_TYPE="release" ;;
     #foreach( $opt in $options )
         "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
+        "--${opt.argument}")  echo "option '$ARG' needs a value:"; echo "  $ARG=${opt.valuesString}"; abort_configure ;;
     #end
     #foreach( $feature in $features )
         "--enable-${feature.arg}") ${feature.varName}=on ;;
@@ -174,76 +261,6 @@
     . "$prefix/etc/config.site"
     echo ok
 fi
-
-# Test for availability of pkg-config
-PKG_CONFIG=`command -v pkg-config`
-: ${PKG_CONFIG:="false"}
-
-# Simple uname based platform detection
-# $PLATFORM is used for platform dependent dependency selection
-OS=`uname -s`
-OS_VERSION=`uname -r`
-printf "detect platform... "
-if [ "$OS" = "SunOS" ]; then
-    PLATFORM="solaris sunos unix svr4"
-elif [ "$OS" = "Linux" ]; then
-    PLATFORM="linux unix"
-elif [ "$OS" = "FreeBSD" ]; then
-    PLATFORM="freebsd bsd unix"
-elif [ "$OS" = "OpenBSD" ]; then
-    PLATFORM="openbsd bsd unix"
-elif [ "$OS" = "NetBSD" ]; then
-    PLATFORM="netbsd bsd unix"
-elif [ "$OS" = "Darwin" ]; then
-    PLATFORM="macos osx bsd unix"
-elif echo "$OS" | grep -i "MINGW" > /dev/null; then
-    PLATFORM="windows mingw"
-fi
-: ${PLATFORM:="unix"}
-
-PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
-echo "$PLATFORM_NAME"
-
-isplatform()
-{
-    for p in $PLATFORM
-    do
-        if [ "$p" = "$1" ]; then
-            return 0
-        fi
-    done
-    return 1
-}
-notisplatform()
-{
-    for p in $PLATFORM
-    do
-        if [ "$p" = "$1" ]; then
-            return 1
-        fi
-    done
-    return 0
-}
-istoolchain()
-{
-    for t in $TOOLCHAIN
-    do
-        if [ "$t" = "$1" ]; then
-            return 0
-        fi
-    done
-    return 1
-}
-notistoolchain()
-{
-    for t in $TOOLCHAIN
-    do
-        if [ "$t" = "$1" ]; then
-            return 1
-        fi
-    done
-    return 0
-}
 ]]#
 ## End of unparsed content **
 
@@ -394,9 +411,9 @@
 ERROR=0
 #if( $dependencies.size() > 0 )
 # unnamed dependencies
-TEMP_CFLAGS=
-TEMP_CXXFLAGS=
-TEMP_LDFLAGS=
+TEMP_CFLAGS="$CFLAGS"
+TEMP_CXXFLAGS="$CXXFLAGS"
+TEMP_LDFLAGS="$LDFLAGS"
 #foreach( $dependency in $dependencies )
 while true
 do
@@ -554,6 +571,35 @@
         unset ${feature.varName}
     fi
 fi
+if [ -n "${D}${feature.varName}" ]; then
+    :
+#foreach( $def in $feature.defines )
+    TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}"
+    TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}"
+#end
+#if( $feature.hasMake() )
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+$feature.make
+__EOF__
+#end
+else
+    :
+#foreach( $def in $feature.disabled.defines )
+    TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}"
+    TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}"
+#end
+#if( $feature.disabled.hasMake() )
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+$feature.disabled.make
+__EOF__
+#end
+#foreach( $dependency in $feature.disabled.dependencies )
+    if dependency_error_$dependency ; then
+        DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+        ERROR=1
+    fi
+#end
+fi
 #end
 
 #foreach( $opt in $target.options )
@@ -600,6 +646,11 @@
             DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
         fi
     #end
+    else
+        echo
+        echo "Invalid option value - usage:"
+        echo "  --${opt.argument}=${opt.valuesString}"
+        abort_configure
     fi
 fi
 #end
--- a/make/project.xml	Tue Feb 25 21:11:00 2025 +0100
+++ b/make/project.xml	Sat Apr 05 16:46:11 2025 +0200
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://unixwork.de/uwproj">
+<project version="0.3" xmlns="http://unixwork.de/uwproj">
 	<dependency>
 		<lang>c</lang>
 	</dependency>
@@ -108,11 +108,13 @@
 	<dependency platform="macos">
 		<make>OBJ_EXT = .o</make>
 		<make>LIB_EXT = .a</make>
+		<make>LIB_PREFIX = lib</make>
 		<make>PACKAGE_SCRIPT = package_osx.sh</make>
 	</dependency>
 	<dependency platform="unix" not="macos">
 		<make>OBJ_EXT = .o</make>
 		<make>LIB_EXT = .a</make>
+		<make>LIB_PREFIX = lib</make>
 		<make>PACKAGE_SCRIPT = package_unix.sh</make>
 	</dependency>
 	
--- a/make/uwproj.xsd	Tue Feb 25 21:11:00 2025 +0100
+++ b/make/uwproj.xsd	Sat Apr 05 16:46:11 2025 +0200
@@ -3,7 +3,7 @@
            xmlns="http://unixwork.de/uwproj"
            targetNamespace="http://unixwork.de/uwproj"
            elementFormDefault="qualified"
-           version="0.2"
+           version="0.3"
 >
     <xs:element name="project" type="ProjectType"/>
 
@@ -17,22 +17,33 @@
             </xs:documentation>
         </xs:annotation>
         <xs:sequence>
-            <xs:element name="config" type="ConfigType" minOccurs="0"/>
+            <xs:element name="config" type="ConfigType" minOccurs="0" maxOccurs="unbounded"/>
             <xs:element name="dependency" type="DependencyType" minOccurs="0" maxOccurs="unbounded"/>
             <xs:element name="target" type="TargetType" minOccurs="0" maxOccurs="unbounded"/>
         </xs:sequence>
+        <xs:attribute name="version" type="xs:string" use="required" />
     </xs:complexType>
 
     <xs:complexType name="ConfigType">
         <xs:annotation>
             <xs:documentation>
-                The configuration section.
-                Consists of an arbitrary number of <code>var</code> elements.
+                <p>
+                    The configuration section.
+                    Consists of an arbitrary number of <code>var</code> elements.
+                </p>
+                <p>
+                    The optional <code>platform</code> attribute may specify a <em>single</em> platform identifier and
+                    the optional <code>not</code> attribute may specify a comma-separated list of platform identifiers.
+                    The configure script shall skip this config declaration if the detected platform is not matching
+                    the filter specification of these attributes.
+                </p>
             </xs:documentation>
         </xs:annotation>
         <xs:sequence>
             <xs:element name="var" type="ConfigVarType" minOccurs="0" maxOccurs="unbounded"/>
         </xs:sequence>
+        <xs:attribute name="platform" type="xs:string"/>
+        <xs:attribute name="not" type="xs:string"/>
     </xs:complexType>
 
     <xs:complexType name="ConfigVarType">
@@ -185,6 +196,9 @@
                 <code>dependencies</code> are satisfied.
                 If a feature is enabled, all <code>define</code> and <code>make</code> definitions are
                 supposed to be applied to the config file.
+                If a feature is disabled, an optional <code>disabled</code> element may specify which
+                <code>define</code> and <code>make</code> definitions are supposed to be applied.
+                There might also be <code>dependencies</code> when the feature is disabled (e.g. specifying a fallback).
                 In case the optional <code>default</code> attribute is set to true, the feature is enabled by default
                 and is supposed to be automatically disabled (without error) when the dependencies are not satisfied.
                 The name that is supposed to be used for the --enable and --disable arguments can be optionally
@@ -195,11 +209,18 @@
         </xs:annotation>
         <xs:choice minOccurs="0" maxOccurs="unbounded">
             <xs:group ref="TargetDataGroup"/>
+            <xs:element name="desc" type="xs:string"/>
+            <xs:element name="disabled">
+                <xs:complexType>
+                    <xs:choice minOccurs="0" maxOccurs="unbounded">
+                        <xs:group ref="TargetDataGroup"/>
+                    </xs:choice>
+                </xs:complexType>
+            </xs:element>
         </xs:choice>
         <xs:attribute name="name" type="xs:string" use="required"/>
         <xs:attribute name="arg" type="xs:string"/>
         <xs:attribute name="default" type="xs:boolean" default="false"/>
-        <xs:element name="desc" type="xs:string"/>
     </xs:complexType>
 
     <xs:complexType name="OptionType">
--- a/ui/Makefile	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/Makefile	Sat Apr 05 16:46:11 2025 +0200
@@ -33,7 +33,7 @@
 
 include common/objs.mk
 
-UI_LIB = ../build/lib/libuitk$(LIB_EXT)
+UI_LIB = ../build/lib/$(LIB_PREFIX)uitk$(LIB_EXT)
 
 include $(TOOLKIT)/objs.mk
 OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
@@ -42,6 +42,6 @@
 
 include $(TOOLKIT)/Makefile
 
-$(COMMON_OBJPRE)%.o: common/%.c
+$(COMMON_OBJPRE)uic_%$(OBJ_EXT): common/%.c
 	$(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $<
 
--- a/ui/cocoa/GridLayout.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/GridLayout.m	Sat Apr 05 16:46:11 2025 +0200
@@ -53,7 +53,6 @@
     NSSize s1 = _test.intrinsicContentSize;
     NSEdgeInsets e1 = _test.alignmentRectInsets;
     
-    printf("fuck\n");
 }
  */
 
@@ -80,7 +79,19 @@
             GridDef *row = &rows[y];
             
             NSSize size = elm->view.intrinsicContentSize;
+            NSSize size2 = elm->view.fittingSize;
             NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+            // TODO: remove alignment
+            alignment.left = 0;
+            alignment.right = 0;
+            alignment.top = 0;
+            alignment.bottom = 0;
+            if(size.width == NSViewNoIntrinsicMetric) {
+                size.width = size2.width;
+            }
+            if(size.height == NSViewNoIntrinsicMetric) {
+                size.height = size2.height;
+            }
             if(size.width != NSViewNoIntrinsicMetric) {
                 CGFloat width = size.width + alignment.left + alignment.right;
                 if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) {
@@ -244,12 +255,12 @@
                 for(int c=elm->x;c<end_col;c++) {
                     cwidth += cols[c].size;
                 }
-                frame.size.width = cwidth;
+                frame.size.width = cwidth + + alignment.left + alignment.right;
             } else {
-                frame.size.width = col->size;
+                frame.size.width = col->size + alignment.left + alignment.right;
             }
         } else {
-            frame.size.width = elm->preferred_width;
+            frame.size.width = elm->preferred_width + alignment.left + alignment.right;
         }
         if(elm->vfill) {
             if(elm->rowspan > 1) {
@@ -268,7 +279,8 @@
             frame.size.height = elm->preferred_height;
         }
         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);
+        //frame.origin.y = viewFrame.size.height - row->pos - frame.size.height + ((alignment.top+alignment.right)/2);
+        frame.origin.y = viewFrame.size.height - row->pos - frame.size.height;
         elm->view.frame = frame;
     }
     
@@ -294,10 +306,17 @@
     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;
+    if(_uilayout.fill) {
+        elm.hfill = TRUE;
+        elm.vfill = TRUE;
+        elm.hexpand = TRUE;
+        elm.vexpand = TRUE;
+    } else {
+        elm.hfill = _uilayout.hfill;
+        elm.vfill = _uilayout.vfill;
+        elm.hexpand = _uilayout.hexpand;
+        elm.vexpand = _uilayout.vexpand;
+    }
     elm.view = view;
     cxListAdd(_children, &elm);
     
--- a/ui/cocoa/appdelegate.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/appdelegate.m	Sat Apr 05 16:46:11 2025 +0200
@@ -29,10 +29,12 @@
 #import "AppDelegate.h"
 
 #import "toolkit.h"
+#import "menu.h"
 
 @implementation AppDelegate
 
 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+    ui_menu_init();
     ui_cocoa_onstartup();
 }
 
--- a/ui/cocoa/container.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/container.m	Sat Apr 05 16:46:11 2025 +0200
@@ -67,12 +67,21 @@
     if(self.orientation == NSUserInterfaceLayoutOrientationHorizontal) {
         [view.heightAnchor constraintEqualToAnchor:self.heightAnchor].active = YES;
         if(!fill) {
-            [view.widthAnchor constraintEqualToConstant:view.intrinsicContentSize.width].active = YES;
+            NSSize isize = view.intrinsicContentSize;
+            [view.widthAnchor constraintEqualToConstant:isize.width].active = YES;
         }
     } else {
         [view.widthAnchor constraintEqualToAnchor:self.widthAnchor].active = YES;
         if(!fill) {
-            [view.heightAnchor constraintEqualToConstant:view.intrinsicContentSize.height].active = YES;
+            NSSize isize = view.intrinsicContentSize;
+            NSRect frame = view.frame;
+            CGFloat height = isize.height > 0 ? isize.height : frame.size.height;
+            if(height == 0) {
+                printf("debug");
+            }
+            if(height > 0) {
+                [view.heightAnchor constraintEqualToConstant:height].active = YES;
+            }
         }
     }
     
--- a/ui/cocoa/menu.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/menu.h	Sat Apr 05 16:46:11 2025 +0200
@@ -28,67 +28,17 @@
 
 #import "../ui/menu.h"
 #import "toolkit.h"
-#import <ucx/list.h>
 
-typedef struct UiAbstractMenuItem {
-    int  (*update)(id window, void *item);
-    void *item_data;
-} UiAbstractMenuItem;
-
-typedef struct UiMenuItem {
-    NSMenuItem  *item;
-    int         state;
-} UiMenuItem;
+#import "../common/menu.h"
 
-typedef struct UiStateItem {
-    NSMenuItem  *item;
-    char        *var;
-} UiStateItem;
+void ui_menu_init(void);
 
-typedef struct UiMenuItemList {
-    NSMenu      *menu;
-    NSMenuItem  *first;
-    UiList      *list;
-    int         index;
-    int         oldcount;
-    ui_callback callback;
-    void        *data;
-} UiMenuItemList;
-
-@interface UiMenuDelegate : NSObject <NSMenuDelegate> {
-    UcxList *items; // UiStateItem*
-    UcxList *itemlists; // UiMenuItemList*
-}
+typedef void(*ui_menu_add_f)(NSMenu*, int, UiMenuItemI*);
 
-- (void) menuNeedsUpdate:(NSMenu*) menu;
-
-- (void) addItem:(NSMenuItem*) item var: (char*)name;
-
-- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data;
-
-- (UcxList*) items;
-
-- (UcxList*) lists;
-
-@end
-
-@interface UiGroupMenuItem : NSMenuItem {
-    NSMutableArray *groups;
-}
-
-- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s;
-
-- (void) addGroup:(int)group;
-
-- (void) checkGroups:(int*)g count:(int)n;
-
-@end
-
-void ui_menu_init();
-UiMenuDelegate* ui_menu_delegate();
-
-int ui_menuitem_get(UiInteger *i);
-void ui_menuitem_set(UiInteger *i, int value);
-
-int ui_update_item(id window, void *data);
-int ui_update_item_list(id window, void *data);
+void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item);
+void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item);
--- a/ui/cocoa/menu.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/menu.m	Sat Apr 05 16:46:11 2025 +0200
@@ -33,287 +33,77 @@
 
 #import "menu.h"
 #import "window.h"
-#import "stock.h"
-
-@implementation UiMenuDelegate 
 
-- (UiMenuDelegate*) init {
-    items = NULL;
-    itemlists = NULL;
-    return self;
-}
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
 
-- (void) menuNeedsUpdate:(NSMenu *) menu {
-    NSWindow *activeWindow = [NSApp keyWindow];
-    [(UiCocoaWindow*)activeWindow updateMenu: menu];
+static void add_menu_items(NSMenu *parent, int i, UiMenu *menu) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it);
+        it = it->next;
+        index++;
+    }
 }
 
-- (void) addItem:(NSMenuItem*) item var: (char*)name {
-    UiStateItem *i = malloc(sizeof(UiStateItem));
-    i->item = item;
-    i->var = name;
-    items = ucx_list_append(items, i);
+void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    UiMenu *it = (UiMenu*)item;
+    NSString *str = [[NSString alloc] initWithUTF8String:it->label];
+    NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
+    NSMenuItem *menuItem = [parent addItemWithTitle:str action:nil keyEquivalent:@""];
+    [parent setSubmenu:menu forItem:menuItem];
+    
+    add_menu_items(menu, i, it);
 }
 
-- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data {
-    UiMenuItemList *itemList = malloc(sizeof(UiMenuItemList));
-    itemList->list = list;
-    itemList->menu = menu;
-    itemList->first = NULL;
-    itemList->index = i;
-    itemList->oldcount = 0;
-    itemList->callback = f;
-    itemList->data = data;
-    itemlists = ucx_list_append(itemlists, itemList);
+void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    UiMenuItem *it = (UiMenuItem*)item;
+    NSString *str = [[NSString alloc] initWithUTF8String:it->label];
+    NSMenuItem *menuItem = [parent addItemWithTitle:str action:nil keyEquivalent:@""];
 }
 
-- (UcxList*) items {
-    return items;
-}
-
-- (UcxList*) lists {
-    return itemlists;
+void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item) {
     
 }
 
-@end
-
-
-@implementation UiGroupMenuItem
-
-- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s {
-    [super initWithTitle:title action:action keyEquivalent:s];
-    groups = [[NSMutableArray alloc]initWithCapacity: 8];
-    return self;
-}
-
-- (void) addGroup:(int)group {
-    NSNumber *groupNumber = [NSNumber numberWithInteger:group];
-    [groups addObject:groupNumber];
+void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    
 }
 
-- (void) checkGroups:(int*)g count:(int)n {
-    int c = [groups count];
+void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item) {
     
-    char *check = calloc(1, c);
-    for(int i=0;i<n;i++) {
-        for(int k=0;k<c;k++) {
-            NSNumber *groupNumber = [groups objectAtIndex:k];
-            if([groupNumber intValue] == g[i]) {
-                check[k] = 1;
-                break;
-            }
-        }
-    }
-    
-    for(int j=0;j<c;j++) {
-        if(check[j] == 0) {
-            [self setEnabled:NO];
-            return;
-        }
-    }
-    [self setEnabled:YES];
 }
 
-@end
-
-
-//static NSMenu *currentMenu = NULL;
-
-static UcxList *current;
-
-static int currentItemIndex = 0;
-static UiMenuDelegate *delegate;
-
-void ui_menu_init() {
-    delegate = [[UiMenuDelegate alloc]init];
+void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    
 }
 
-UiMenuDelegate* ui_menu_delegate() {
-    return delegate;
+void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+    
 }
 
 
-void ui_menu(char *title) {
-    NSString *str = [[NSString alloc] initWithUTF8String:title];
-    
-    NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
-    NSMenuItem *menuItem = [[NSApp mainMenu] addItemWithTitle:str
-                                                       action:nil keyEquivalent:@""];
-    [menu setDelegate: delegate];
-    [menu setAutoenablesItems:NO];
-    
-    [[NSApp mainMenu] setSubmenu:menu forItem:menuItem];
-    //currentMenu = menu;
-    currentItemIndex = 0;
-    
-    current = ucx_list_prepend(NULL, menu);
-}
-
-void ui_submenu(char *title) {
-    NSString *str = [[NSString alloc] initWithUTF8String:title];
-    NSMenu *currentMenu = current->data;
-    
-    NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
-    NSMenuItem *menuItem = [currentMenu addItemWithTitle:str
-                                                       action:nil keyEquivalent:@""];
-    [menu setDelegate: delegate];
-    [menu setAutoenablesItems:NO];
-    
-    [currentMenu setSubmenu:menu forItem:menuItem];
-    //currentMenu = menu;
-    currentItemIndex = 0;
-    
-    current = ucx_list_prepend(current, menu);
-}
-
-void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
-        return;
-    }
-    current = ucx_list_remove(current, current);
-}
-
-void ui_menuitem(char *label, ui_callback f, void *data) {
-    ui_menuitem_gr(label, f, data, -1);
-}
-
-void ui_menuitem_st(char *stockid, ui_callback f, void *data) {
-    ui_menuitem_stgr(stockid, f, data, -1);
-}
-
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
-    // create menu item
-    EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
-    NSString *title = [[NSString alloc] initWithUTF8String:label];
-    UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        [item addGroup: group];
-    }
-    va_end(ap);
-    
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem:item];
-    
-    currentItemIndex++;
-}
-
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
-    // create menu item
-    EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
-    UiStockItem *si = ui_get_stock_item(stockid);
-    UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:si->label
-                                action:@selector(handleEvent:)
-                                keyEquivalent:si->keyEquivalent];
-    [item setTarget:event];
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        [item addGroup: group];
+void ui_menu_init(void) {
+    UiMenu *menus_begin = uic_get_menu_list();
+    UiMenu *ls = menus_begin;
+    while(ls) {
+        if(ls->item.type == UI_MENU) {
+            NSString *str = [[NSString alloc] initWithUTF8String:ls->label];
+            NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
+            NSMenuItem *menuItem = [[NSApp mainMenu] addItemWithTitle:str action:nil keyEquivalent:@""];
+            [[NSApp mainMenu] setSubmenu:menu forItem:menuItem];
+            
+            add_menu_items(menu, 0, ls);
+        }
+        ls = (UiMenu*)ls->item.next;
     }
-    va_end(ap);
-    
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem:item];
-    
-    currentItemIndex++;
 }
-
-void ui_checkitem(char *label, ui_callback f, void *data) {
-    EventWrapper *event = [[EventWrapper alloc]initWithData:data callback:f];
-    NSString *str = [[NSString alloc] initWithUTF8String:label];
-    
-    NSMenu *currentMenu = current->data;
-    NSMenuItem *item = [currentMenu addItemWithTitle:str
-                                              action:@selector(handleStateEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    [delegate addItem: item var:NULL];
-    currentItemIndex++;
-}
-
-void ui_checkitem_nv(char *label, char *vname) {
-    EventWrapper *event = [[EventWrapper alloc]initWithData:NULL callback:NULL];
-    NSString *str = [[NSString alloc] initWithUTF8String:label];
-    
-    NSMenu *currentMenu = current->data;
-    NSMenuItem *item = [currentMenu addItemWithTitle:str
-                                              action:@selector(handleStateEvent:) keyEquivalent:@""];
-    [item setTarget:event];
-    
-    [delegate addItem: item var:vname];
-    currentItemIndex++;
-}
-
-void ui_menuseparator() {
-    NSMenu *currentMenu = current->data;
-    [currentMenu addItem: [NSMenuItem separatorItem]];
-    currentItemIndex++;
-}
-
-void ui_menuitem_list (UiList *items, ui_callback f, void *data) {
-    NSMenu *currentMenu = current->data;
-    [delegate addList:items menu:currentMenu index:currentItemIndex callback:f data:data];
-}
-
-
-
-int ui_menuitem_get(UiInteger *i) {
-    UiMenuItem *item = i->obj;
-    i->value = [item->item state];
-    return i->value;
-}
-
-void ui_menuitem_set(UiInteger *i, int value) {
-    UiMenuItem *item = i->obj;
-    [item->item setState: value];
-    i->value = value;
-    item->state = value;
-}
-
-
-int ui_update_item(UiCocoaWindow *window, void *data) {
-    UiMenuItem *item = data;
-    [item->item setState: item->state];
-    return 0;
-}
-
-int ui_update_item_list(UiCocoaWindow *window, void *data) {
-    UiMenuItemList *itemList = data;
-    UiList *list = itemList->list;
-    
-    for(int r=0;r<itemList->oldcount;r++) {
-        [itemList->menu removeItemAtIndex:itemList->index];
-    }
-    
-    char *str = ui_list_first(list);
-    int i = itemList->index;
-    [itemList->menu insertItem: [NSMenuItem separatorItem] atIndex: i];
-    i++;
-    while(str) {
-        EventWrapper *event = [[EventWrapper alloc]initWithData:itemList->data callback:itemList->callback];
-        [event setIntval: i - itemList->index - 1];
-        
-        NSString *title = [[NSString alloc] initWithUTF8String:str];
-        NSMenuItem *item = [[NSMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
-        [item setTarget:event];
-        
-        [itemList->menu insertItem:item atIndex:i];
-        
-        str = ui_list_next(list);
-        i++;
-    }
-    
-    itemList->oldcount = i - itemList->index;
-    
-    return 0;
-}
--- a/ui/cocoa/objs.mk	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/objs.mk	Sat Apr 05 16:46:11 2025 +0200
@@ -39,6 +39,8 @@
 COCOAOBJ += window.o
 COCOAOBJ += Container.o
 COCOAOBJ += button.o
+COCOAOBJ += text.o
+COCOAOBJ += menu.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/text.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/text.h	Sat Apr 05 16:46:11 2025 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,38 +26,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "../ui/text.h"
 #import "toolkit.h"
-#import <ucx/list.h>
-
-@interface TextChangeMgr : NSObject<NSTextViewDelegate> {
-    UiContext *context;
-    UiText    *value;
-    int       last_length;
-}
-
-- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx;
-
-- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview;
-
-@end
 
-#define UI_TEXTBUF_INSERT 0
-#define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp {
-    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
-    int  start;
-    int  end;
-    int  len;
-    char *text;
-} UiTextBufOp;
+#import "../ui/text.h"
 
-
-
-char* ui_textarea_get(UiText *text);
-void ui_textarea_set(UiText *text, char *str);
-char* ui_textarea_getsubstr(UiText *text, int begin, int end);
-void ui_textarea_insert(UiText *text, int pos, char *str);
-int  ui_textarea_position(UiText *text);
-void ui_textarea_selection(UiText *text, int *begin, int *end);
-int ui_textarea_length(UiText *text);
--- a/ui/cocoa/text.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/text.m	Sat Apr 05 16:46:11 2025 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,165 +26,23 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <stdio.h>
-#import <stdlib.h>
-#import <string.h>
-
 #import "text.h"
-#import "container.h"
-
-@implementation TextChangeMgr
-
-- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx {
-    value = text;
-    context = ctx;
-    last_length = 0;
-    return self;
-}
-
-- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview {
-    return (NSUndoManager*)value->undomgr;
-}
-
-- (NSRange)textView:(NSTextView *)textview
-       willChangeSelectionFromCharacterRange:(NSRange)oldrange
-       toCharacterRange:(NSRange)newrange
-{
-    if(newrange.length != last_length) {
-        if(newrange.length == 0) {
-            ui_unset_group(context, UI_GROUP_SELECTION);
-        } else {
-            ui_set_group(context, UI_GROUP_SELECTION);
-        }
-    }
-    
-    last_length = newrange.length;
-    return newrange;
-}
-
-@end
-
+#import "EventData.h"
+#import "Container.h"
+#import <objc/runtime.h>
 
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    NSRect frame = ct->getframe(ct);
-    
-    NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
-    [scrollview setHasVerticalScroller:YES];
-    //[scrollvew setHasHorizontalScroller:YES];
-    [scrollview setBorderType:NSNoBorder];
-    //[scrollview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-    
-    //frame.size.width = frame.size.width - 15;
-    NSTextView *textview = [[NSTextView alloc]initWithFrame:frame];
-    [textview setAllowsUndo:TRUE];
-    [textview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-    
-    [textview setFont:[NSFont fontWithName:@"Menlo" size:12]];
-    
-    [scrollview setDocumentView:textview];
-    
-    ct->add(ct, scrollview);
-    
-    // bind value
-    if(value) {
-        value->get = ui_textarea_get;
-        value->set = ui_textarea_set;
-        value->getsubstr = ui_textarea_getsubstr;
-        value->insert = ui_textarea_insert;
-        value->position = ui_textarea_position;
-        value->selection = ui_textarea_selection;
-        value->length = ui_textarea_length;
-        value->value = NULL;
-        value->obj = textview;
-        
-        TextChangeMgr *delegate = [[TextChangeMgr alloc]initWithValue:value context:obj->ctx];
-        [textview setDelegate:delegate];
-        
-        NSUndoManager *undomgr = [[NSUndoManager alloc]init];
-        value->undomgr = undomgr;
-    }
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+    NSTextView *textview = [[NSTextView alloc] init];
+    textview.autoresizingMask = NSViewWidthSizable;
+    textview.minSize = NSMakeSize(0, 0);
+    textview.maxSize = NSMakeSize(FLT_MAX, FLT_MAX);
     
-    return textview;
-}
-
-char* ui_textarea_get(UiText *text) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *str = [[textview textStorage]string];
-    size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-    const char *cstr = [str UTF8String];
-    char *value = malloc(length + 1);
-    memcpy(value, cstr, length);
-    value[length] = '\0';
-    text->value = value;
-    return value;
-}
-
-void ui_textarea_set(UiText *text, char *str) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *s = [[NSString alloc]initWithUTF8String:str];
-    NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
-    [[textview textStorage] setAttributedString:as];
-    text->value = NULL;
-}
-
-char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *str = [[textview textStorage]string];
-    NSRange range;
-    range.location = begin;
-    range.length = end - begin;
+    NSScrollView *scrollview = [[NSScrollView alloc] init];
+    scrollview.hasVerticalScroller = YES;
+    scrollview.documentView = textview;
     
-    NSString *substr = [str substringWithRange:range];
-    size_t length = [substr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-    const char *cstr = [substr UTF8String];
-    char *value = malloc(length + 1);
-    memcpy(value, cstr, length);
-    value[length] = '\0';
-    text->value = value;
-    return value;
-}
-
-void ui_textarea_insert(UiText *text, int pos, char *str) {
-    if(text->value) {
-        free(text->value);
-    }
-    NSTextView *textview = (NSTextView*)text->obj;
-    NSString *s = [[NSString alloc]initWithUTF8String:str];
-    NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
-    [[textview textStorage] insertAttributedString:as atIndex: pos];
-    text->value = NULL;
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, scrollview, &layout, TRUE);
+    
+    return (__bridge void*)scrollview;
 }
-
-int ui_textarea_position(UiText *text) {
-    return [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue].location;
-}
-
-void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    NSRange range = [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue];
-    *begin = range.location;
-    *end = range.location + range.length;
-}
-
-int ui_textarea_length(UiText *text) {
-    return [[(NSTextView*)text->obj textStorage] length];
-}
-
-void ui_text_undo(UiText *text) {
-    [(NSUndoManager*)text->undomgr undo];
-}
-
-void ui_text_redo(UiText *text) {
-    [(NSUndoManager*)text->undomgr redo];
-}
-
--- a/ui/cocoa/toolkit.m	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/cocoa/toolkit.m	Sat Apr 05 16:46:11 2025 +0200
@@ -34,6 +34,8 @@
 #include "../common/toolbar.h"
 #include "../common/threadpool.h"
 
+#import "menu.h"
+
 #import "AppDelegate.h"
 
 static const char *application_name;
--- a/ui/common/condvar.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/condvar.c	Sat Apr 05 16:46:11 2025 +0200
@@ -26,6 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifndef _WIN32
+
 #include "condvar.h"
 
 #include <stdlib.h>
@@ -68,3 +70,5 @@
     free(p);
     
 }
+
+#endif
\ No newline at end of file
--- a/ui/common/context.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/context.c	Sat Apr 05 16:46:11 2025 +0200
@@ -139,8 +139,8 @@
 
 void uic_context_detach_document2(UiContext *ctx, void *document) {
     // find the document in the documents list
-    ssize_t docIndex = cxListFind(ctx->documents, document);
-    if(docIndex < 0) {
+    size_t docIndex = cxListFind(ctx->documents, document);
+    if(!cxListIndexValid(ctx->documents, docIndex)) {
         return;
     }
     
@@ -292,6 +292,8 @@
         to->from_ctx = from->from_ctx;
     }
     
+    ui_setop_enable(TRUE);
+    
     // copy binding
     // we don't copy the observer, because the from var has never one
     switch(from->type) {
@@ -327,9 +329,7 @@
             UiText *t = to->value;
             if(!f->obj) break;
             uic_text_copy(f, t);
-            char *tvalue = t->value.ptr ? t->value.ptr : "";
-            t->set(t, tvalue);
-            t->setposition(t, t->pos);
+            t->restore(t);
             break;
         }
         case UI_VAR_LIST: {
@@ -349,7 +349,10 @@
             *to = tmp;
 
             UiList* t2 = to->value;
-            ui_notify(t2->observers, NULL);
+            if(t->update) {
+                t->update(t, -1);
+            }
+            ui_notify(t2->observers, NULL); // TODO: why not t?
             
             break;
         }
@@ -372,6 +375,8 @@
             break;
         }
     }
+    
+    ui_setop_enable(FALSE);
 }
 
 void uic_save_var2(UiVar *var) {
--- a/ui/common/context.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/context.h	Sat Apr 05 16:46:11 2025 +0200
@@ -45,9 +45,7 @@
 typedef struct UiListVar     UiListVar;
 typedef struct UiGroupWidget UiGroupWidget;
 
-typedef enum UiVarType UiVarType;
-
-enum UiVarType {
+typedef enum UiVarType {
     UI_VAR_SPECIAL = 0,
     UI_VAR_INTEGER,
     UI_VAR_DOUBLE,
@@ -56,7 +54,7 @@
     UI_VAR_LIST,
     UI_VAR_RANGE,
     UI_VAR_GENERIC
-};
+} UiVarType;
 
 struct UiContext {
     UiContext     *parent;
--- a/ui/common/objs.mk	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/objs.mk	Sat Apr 05 16:46:11 2025 +0200
@@ -29,18 +29,18 @@
 COMMON_SRC_DIR = ui/common/
 COMMON_OBJPRE = $(OBJ_DIR)$(COMMON_SRC_DIR)
 
-COMMON_OBJ = context.o
-COMMON_OBJ += document.o
-COMMON_OBJ += object.o
-COMMON_OBJ += types.o
-COMMON_OBJ += menu.o
-COMMON_OBJ += properties.o
-COMMON_OBJ += menu.o
-COMMON_OBJ += toolbar.o
-COMMON_OBJ += ucx_properties.o
-COMMON_OBJ += threadpool.o
-COMMON_OBJ += condvar.o
+COMMON_OBJ = context$(OBJ_EXT)
+COMMON_OBJ += document$(OBJ_EXT)
+COMMON_OBJ += object$(OBJ_EXT)
+COMMON_OBJ += types$(OBJ_EXT)
+COMMON_OBJ += menu$(OBJ_EXT)
+COMMON_OBJ += properties$(OBJ_EXT)
+COMMON_OBJ += menu$(OBJ_EXT)
+COMMON_OBJ += toolbar$(OBJ_EXT)
+COMMON_OBJ += ucx_properties$(OBJ_EXT)
+COMMON_OBJ += threadpool$(OBJ_EXT)
+COMMON_OBJ += condvar$(OBJ_EXT)
 
-TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
-TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
+TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)uic_%)
+TOOLKITSOURCE += $(COMMON_OBJ:%$(OBJ_EXT)=common/%.c)
 
--- a/ui/common/properties.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/properties.c	Sat Apr 05 16:46:11 2025 +0200
@@ -116,7 +116,7 @@
 
 static int ui_mkdir(char *path) {
 #ifdef _WIN32
-    return mkdir(path);
+    return _mkdir(path);
 #else
     return mkdir(path, S_IRWXU);
 #endif
--- a/ui/common/threadpool.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/threadpool.c	Sat Apr 05 16:46:11 2025 +0200
@@ -26,11 +26,11 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifndef _WIN32
+
 #include "threadpool.h"
 #include "context.h"
 
-#ifndef _WIN32
-
 #include <pthread.h>
 #include <stdio.h>
 #include <string.h>
--- a/ui/common/types.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/common/types.c	Sat Apr 05 16:46:11 2025 +0200
@@ -162,7 +162,7 @@
 
 UIEXPORT void ui_list_update(UiList *list) {
     if(list->update) {
-        list->update(list, 0);
+        list->update(list, -1);
     }
 }
 
@@ -306,7 +306,9 @@
 void ui_int_set(UiInteger* i, int64_t value) {
     if (i) {
         if (i->set) {
+            ui_setop_enable(TRUE);
             i->set(i, value);
+            ui_setop_enable(FALSE);
         } else {
             i->value = value;
         }
@@ -324,7 +326,9 @@
 void ui_double_set(UiDouble* d, double value) {
     if (d) {
         if (d->set) {
+            ui_setop_enable(TRUE);
             d->set(d, value);
+            ui_setop_enable(FALSE);
         } else {
             d->value = value;
         }
@@ -343,7 +347,9 @@
 void ui_string_set(UiString* s, const char* value) {
     if (s) {
         if (s->set) {
+            ui_setop_enable(TRUE);
             s->set(s, value);
+            ui_setop_enable(FALSE);
         } else {
             if(s->value.free) {
                 s->value.free(s->value.ptr);
@@ -371,7 +377,9 @@
 void ui_text_set(UiText* s, const char* value) {
     if (s) {
         if (s->set) {
+            ui_setop_enable(TRUE);
             s->set(s, value);
+            ui_setop_enable(FALSE);
         } else {
             if(s->value.free) {
                 s->value.free(s->value.ptr);
@@ -426,9 +434,12 @@
     to->selection = from->selection;
     to->length = from->length;
     to->remove = from->remove;
+    to->restore = from->restore;
+    to->save = from->save;
+    to->destroy = from->destroy;
     
     to->obj = from->obj;
-    // do not copy the undo manager
+    // do not copy the undo manager, data1, data2
 }
 
 void uic_range_copy(UiRange *from, UiRange *to) {
@@ -468,7 +479,7 @@
 
 void uic_text_save(UiText *t) {
     if(!t->obj) return;
-    t->get(t);
+    t->save(t);
     t->position(t);
 }
 
@@ -512,7 +523,6 @@
     t->length = NULL;
     t->remove = NULL;
     t->obj = NULL;
-    t->undomgr = NULL;
 }
 
 void uic_range_unbind(UiRange *r) {
@@ -614,3 +624,13 @@
     destr->observer = observer;
     cxMempoolSetDestructor(destr, (cx_destructor_func)observer_destructor);
 }
+
+static int ui_set_op = 0;
+
+void ui_setop_enable(int set) {
+    ui_set_op = set;
+}
+
+int ui_get_setop(void) {
+    return ui_set_op;
+}
--- a/ui/gtk/button.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/button.c	Sat Apr 05 16:46:11 2025 +0200
@@ -114,6 +114,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = NULL;
     e.intval = event->value;
+    e.set = ui_get_setop();
     event->callback(&e, event->userdata);
 }
 
@@ -136,7 +137,8 @@
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.eventdata = event->var->value;
-    e.intval = i->get(i);  
+    e.intval = i->get(i);
+    e.set = ui_get_setop();
     
     ui_notify_evt(i->observers, &e);
 }
@@ -148,6 +150,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = NULL;
     e.intval = gtk_toggle_button_get_active(widget);
+    e.set = ui_get_setop();
     event->callback(&e, event->userdata);    
 }
 
@@ -275,7 +278,7 @@
     UiObject* current = uic_current_obj(obj);
     
     ui_setup_togglebutton(
-            current,
+            obj,
             widget,
             args.label,
             args.icon,
@@ -318,6 +321,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = NULL;
     e.intval = gtk_check_button_get_active(widget);
+    e.set = ui_get_setop();
     event->callback(&e, event->userdata);    
 }
 
@@ -388,6 +392,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = NULL;
     e.intval = RADIOBUTTON_GET_ACTIVE(widget);
+    e.set = ui_get_setop();
     event->callback(&e, event->userdata);    
 }
 
--- a/ui/gtk/container.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/container.c	Sat Apr 05 16:46:11 2025 +0200
@@ -52,17 +52,6 @@
     return 1;
 }
 
-UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
-    UiObject* current = uic_current_obj(obj);
-    
-    UIWIDGET widget = create_widget(obj, args, userdata);
-    
-    UI_APPLY_LAYOUT1(current, args);
-    current->container->add(current->container, widget, FALSE);
-    
-    return widget;
-}
-
 GtkWidget* ui_gtk_vbox_new(int spacing) {
 #if GTK_MAJOR_VERSION >= 3
     return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
@@ -1041,9 +1030,11 @@
     int max = args.max_panes == 0 ? 2 : args.max_panes;
     
     UiObject *newobj = uic_object_new(obj, pane0);
-    newobj->container = ui_splitpane_container(obj, pane0, orientation, max);
+    newobj->container = ui_splitpane_container(obj, pane0, orientation, max, args.initial_position);
     uic_obj_add(obj, newobj);
     
+    g_object_set_data(G_OBJECT(pane0), "ui_splitpane", newobj->container);
+    
     return pane0;
 }
 
@@ -1055,13 +1046,15 @@
     return splitpane_create(obj, UI_VERTICAL, args);
 }
 
-UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max) {
+UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init) {
     UiSplitPaneContainer *ct = ui_calloc(obj->ctx, 1, sizeof(UiSplitPaneContainer));
     ct->container.widget = pane;
     ct->container.add = ui_splitpane_container_add;
     ct->current_pane = pane;
     ct->orientation = orientation;
     ct->max = max;
+    ct->initial_position = init;
+    ct->children = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
     return (UiContainer*)ct;
 }
 
@@ -1073,17 +1066,22 @@
         return;
     }
     
+    cxListAdd(s->children, widget);
+    
     if(s->pos == 0) {
-        gtk_paned_set_start_child(GTK_PANED(s->current_pane), widget);
+        PANED_SET_CHILD1(s->current_pane, widget);
+        if(s->initial_position > 0) {
+            gtk_paned_set_position(GTK_PANED(s->current_pane), s->initial_position);
+        }
         s->pos++;
         s->nchildren++;
     } else {
         if(s->nchildren+1 == s->max) {
-            gtk_paned_set_end_child(GTK_PANED(s->current_pane), widget);
+            PANED_SET_CHILD2(s->current_pane, widget);
         } else {
             GtkWidget *pane = create_paned(s->orientation);
-            gtk_paned_set_start_child(GTK_PANED(pane), widget);
-            gtk_paned_set_end_child(GTK_PANED(s->current_pane), pane);
+            PANED_SET_CHILD1(pane, widget);
+            PANED_SET_CHILD2(s->current_pane, pane);
             s->current_pane = pane;
         }
         
@@ -1092,6 +1090,19 @@
     }
 }
 
+UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible) {
+    UiSplitPaneContainer *ct = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane");
+    if(!ct) {
+        fprintf(stderr, "UI Error: not a splitpane\n");
+        return;
+    }
+    
+    GtkWidget *w = cxListAt(ct->children, child_index);
+    if(w) {
+        gtk_widget_set_visible(w, visible);
+    }
+}
+
 /* -------------------- ItemList Container -------------------- */
 
 static void remove_item(void *data, void *item) {
--- a/ui/gtk/container.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/container.h	Sat Apr 05 16:46:11 2025 +0200
@@ -36,11 +36,13 @@
 
 #include <cx/allocator.h>
 #include <cx/hash_map.h>
+#include <cx/list.h>
+#include <cx/array_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
-
+  
 #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)
@@ -49,15 +51,14 @@
 
 typedef struct UiDocumentView UiDocumentView;
 
-typedef struct UiLayout UiLayout;
-typedef enum UiLayoutBool UiLayoutBool;
 
-enum UiLayoutBool {
+typedef enum UiLayoutBool {
     UI_LAYOUT_UNDEFINED = 0,
     UI_LAYOUT_TRUE,
     UI_LAYOUT_FALSE,
-};
+} UiLayoutBool;
 
+typedef struct UiLayout UiLayout;
 struct UiLayout {
     UiLayoutBool fill;
     UiBool       newline;
@@ -127,10 +128,12 @@
 typedef struct UiSplitPaneContainer {
     UiContainer container;
     GtkWidget *current_pane;
+    CxList *children;
     UiOrientation orientation;
     int pos;
     int max;
     int nchildren;
+    int initial_position;
 } UiSplitPaneContainer;
 
 typedef struct UiHeaderbarContainer {
@@ -197,7 +200,7 @@
 UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview);
 void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
-UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max);
+UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init);
 void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
 
--- a/ui/gtk/image.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/image.c	Sat Apr 05 16:46:11 2025 +0200
@@ -33,59 +33,265 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
+static void imageviewer_destroy(UiImageViewer *iv) {
+    if(iv->pixbuf) {
+        g_object_unref(iv->pixbuf);
+    }
+    free(iv);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static void imageviewer_draw(
+        GtkDrawingArea *drawingarea,
+        cairo_t *cr,
+        int width,
+        int height,
+        gpointer userdata)
+{
+    ui_cairo_draw_image(userdata, cr, width, height);
+}
+
+#else
+
+static gboolean imageviewer_draw(GtkWidget *widget, cairo_t *cr, gpointer userdata) {
+    int width = gtk_widget_get_allocated_width(widget);
+    int height = gtk_widget_get_allocated_height(widget);
+    ui_cairo_draw_image(userdata, cr, width, height);
+    return FALSE;
+}
+
+#endif
 
 UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
     UiObject *current = uic_current_obj(obj);
     
-    GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW();
-#if GTK_CHECK_VERSION(4, 0, 0)
-    GtkWidget *image = gtk_picture_new();
-#else
-    GtkWidget *image = gtk_image_new();
-#endif
-    
-    ui_set_name_and_style(image, args.name, args.style_class);
+    GtkWidget *drawingarea = gtk_drawing_area_new();
+    GtkWidget *toplevel;
+    GtkWidget *widget = drawingarea;
+      
+    gtk_widget_set_size_request(drawingarea, 100, 100);
     
 #if GTK_MAJOR_VERSION < 4
     GtkWidget *eventbox = gtk_event_box_new();
-    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
-    gtk_container_add(GTK_CONTAINER(eventbox), image);
-#else
-    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
-    GtkWidget *eventbox = image;
+    gtk_container_add(GTK_CONTAINER(eventbox), drawingarea);
+    widget = eventbox;
 #endif
     
-    UI_APPLY_LAYOUT1(current, args);
-    current->container->add(current->container, scrolledwindow, TRUE);
+    if(args.scrollarea) {
+        toplevel = SCROLLEDWINDOW_NEW();
+        SCROLLEDWINDOW_SET_CHILD(toplevel, widget);
+        args.adjustwidgetsize = TRUE;
+    } else {
+        toplevel = widget;
+    }
+    
+    UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer));
+    memset(imgviewer, 0, sizeof(UiImageViewer));
+    imgviewer->obj = obj;
+    imgviewer->onbuttonpress = args.onbuttonpress;
+    imgviewer->onbuttonpressdata = args.onbuttonpressdata;
+    imgviewer->onbuttonrelease = args.onbuttonrelease;
+    imgviewer->onbuttonreleasedata = args.onbuttonreleasedata;
+    if(args.image_padding > 0) {
+        imgviewer->padding_left = args.image_padding;
+        imgviewer->padding_right = args.image_padding;
+        imgviewer->padding_top = args.image_padding;
+        imgviewer->padding_bottom = args.image_padding;
+    } else {
+        imgviewer->padding_left = args.image_padding_left;
+        imgviewer->padding_right = args.image_padding_right;
+        imgviewer->padding_top = args.image_padding_top;
+        imgviewer->padding_bottom = args.image_padding_bottom;
+    }
+    imgviewer->adjustwidgetsize = args.adjustwidgetsize;
+    imgviewer->autoscale = args.autoscale;
+    imgviewer->useradjustable = args.useradjustable;
+    imgviewer->zoom_scale = 20;
+    
+    g_object_set_data_full(G_OBJECT(drawingarea), "uiimageviewer", imgviewer, (GDestroyNotify)imageviewer_destroy);
     
     UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
+    imgviewer->var = var;
+    imgviewer->widget = drawingarea;
+    
     if(var) {
         UiGeneric *value = var->value;
         value->get = ui_imageviewer_get;
         value->get_type = ui_imageviewer_get_type;
         value->set = ui_imageviewer_set;
-        value->obj = image;
+        value->obj = imgviewer;
         if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) {
             GdkPixbuf *pixbuf = value->value;
             value->value = NULL;
             ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE);
+            g_object_unref(pixbuf);
         }
     }
     
-    if(args.contextmenu) {
-        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
-        ui_widget_set_contextmenu(eventbox, menu);
+#if GTK_MAJOR_VERSION >= 4
+    gtk_drawing_area_set_draw_func(
+            GTK_DRAWING_AREA(drawingarea),
+            imageviewer_draw,
+            imgviewer,
+            NULL);
+    
+    if(args.useradjustable) {
+        gtk_widget_set_focusable(drawingarea, TRUE);
     }
     
-    return scrolledwindow;
+    GtkEventController *scrollcontroller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
+    g_signal_connect(scrollcontroller, "scroll", G_CALLBACK(ui_imageviewer_scroll), imgviewer);
+    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(scrollcontroller));
+    
+    GtkGesture *drag = gtk_gesture_drag_new();
+    g_signal_connect(drag, "drag-begin", G_CALLBACK(ui_imageviewer_drag_begin_cb), imgviewer);
+    g_signal_connect(drag, "drag-end", G_CALLBACK(ui_imageviewer_drag_end_cb), imgviewer);
+    g_signal_connect(drag, "drag-update", G_CALLBACK(ui_imageviewer_drag_update_cb), imgviewer);
+    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(drag));
+    
+    GtkGesture *click = gtk_gesture_click_new();
+    g_signal_connect(click, "pressed", G_CALLBACK(ui_imageviewer_pressed_cb), imgviewer);
+    g_signal_connect(click, "released", G_CALLBACK(ui_imageviewer_released_cb), imgviewer);
+    gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(click));
+    
+#elif GTK_MAJOR_VERSION == 3
+    g_signal_connect(
+            drawingarea,
+            "draw",
+            G_CALLBACK(imageviewer_draw),
+            imgviewer);
+    
+    gtk_widget_add_events(eventbox, GDK_SCROLL_MASK);
+    
+    g_signal_connect(
+            eventbox,
+            "scroll-event",
+            G_CALLBACK(ui_imageviewer_scroll_event),
+            imgviewer);
+    g_signal_connect(
+            eventbox,
+            "button-press-event",
+            G_CALLBACK(ui_imageviewer_button_press_event),
+            imgviewer);
+    g_signal_connect(
+            eventbox,
+            "button-release-event",
+            G_CALLBACK(ui_imageviewer_button_release_event),
+            imgviewer);
+
+#endif
+    
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, widget);
+        ui_widget_set_contextmenu(widget, menu);
+    }
+       
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, toplevel, TRUE);
+    
+    return toplevel;
+}
+
+static void imageviewer_reset(UiImageViewer *imgviewer) {
+    imgviewer->isautoscaled = FALSE;
+    imgviewer->transx = 0;
+    imgviewer->transy;
+    imgviewer->begin_transx = 0;
+    imgviewer->begin_transy = 0;
+    imgviewer->scale = 1;
+    imgviewer->user_scale = 1;
+}
+
+UIWIDGET ui_imageviewer_reset(UIWIDGET w) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imageviewer_reset(imgviewer);
+        gtk_widget_queue_draw(w);
+    }
+}
+
+UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->autoscale = set;
+    }
+}
+
+UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->adjustwidgetsize = set;
+    }
+}
+
+UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set) {
+    UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer");
+    if(imgviewer) {
+        imgviewer->useradjustable = set;
+    }
+}
+
+void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height) {
+    if(!imgviewer->pixbuf) {
+        return;
+    }
+    
+    GdkPixbuf *pixbuf = imgviewer->pixbuf;
+    double dpixwidth = (double)gdk_pixbuf_get_width(pixbuf);
+    double dpixheight = (double)gdk_pixbuf_get_height(pixbuf);
+    
+    double dwidth = width;
+    double dheight = height;
+    double scale = 1;
+    // if autoscale is enabled, scale the image to fill available space
+    // if useradjustable is also enabled, the autoscaling is only done once
+    if(imgviewer->autoscale && imgviewer->scale != 0) {
+        if(!imgviewer->isautoscaled) {
+            scale = dwidth / dpixwidth;
+            if(dpixheight * scale > dheight) {
+                scale = dheight / dpixheight;
+            }
+
+            if(imgviewer->useradjustable) {
+                imgviewer->isautoscaled = TRUE;
+            }
+            
+            imgviewer->scale = scale;
+        } else {
+            scale = imgviewer->scale;
+        }
+        
+        imgviewer->user_scale = scale;
+    } else {
+        // user-adjusted scaling
+        //scale = 1 + ((double)imgviewer->zoom / (double)imgviewer->zoom_scale);
+        scale = imgviewer->user_scale;
+    }
+
+    dpixwidth *= scale;
+    dpixheight *= scale;
+    double x = (dwidth - dpixwidth) / 2;
+    double y = (dheight - dpixheight) / 2;
+    
+    x += imgviewer->transx;
+    y += imgviewer->transy;
+    
+    cairo_translate(cr, x, y);
+    cairo_scale(cr, scale, scale);
+    
+    gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+    cairo_paint(cr);
 }
 
 void* ui_imageviewer_get(UiGeneric *g) {
+    UiImageViewer *imgviewer = g->obj;
+    g->value = imgviewer->pixbuf;
     return g->value;
 }
 
 const char* ui_imageviewer_get_type(UiGeneric *g) {
-    
+    return UI_IMAGE_OBJECT_TYPE;
 }
 
 int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
@@ -93,25 +299,25 @@
         return 1;
     }
     
-    // TODO: do we need to free the previous value here?
+    GdkPixbuf *pixbuf = value;
+    g_object_ref(pixbuf);
+    
+    UiImageViewer *imgviewer = g->obj;
+    g->value = pixbuf;
     
-    g->value = value;
-    g->type = type;
-    GdkPixbuf *pixbuf = value;
+    imageviewer_reset(imgviewer);
     
-    if(pixbuf) {
+    if(imgviewer->pixbuf) {
+        g_object_unref(imgviewer->pixbuf);
+    }
+    imgviewer->pixbuf = pixbuf;
+    
+    if(imgviewer->adjustwidgetsize && !imgviewer->autoscale) {
         int width = gdk_pixbuf_get_width(pixbuf);
         int height = gdk_pixbuf_get_height(pixbuf);
-        
-#if GTK_CHECK_VERSION(4, 0, 0)
-        GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
-        gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
-#else
-        gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf);
-#endif
-        gtk_widget_set_size_request(g->obj, width, height);
+        gtk_widget_set_size_request(imgviewer->widget, width, height);
     }
-
+    gtk_widget_queue_draw(imgviewer->widget);
     
     return 0;
 }
@@ -127,9 +333,147 @@
     
     if(obj->set) {
         obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
+        g_object_unref(pixbuf);
     } else {
         obj->value = pixbuf;
     }
-    
+          
     return 0;
 }
+
+void ui_image_ref(UIIMAGE img) {
+    g_object_ref(img);
+}
+
+void ui_image_unref(UIIMAGE img) {
+    g_object_unref(img);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+gboolean ui_imageviewer_scroll(
+        GtkEventControllerScroll *widget,
+        gdouble dx,
+        gdouble dy,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->useradjustable) {
+        double step = dy / imgviewer->zoom_scale;
+        if(imgviewer->user_scale - step > 0) {
+            imgviewer->user_scale -= step;
+        }
+        
+        imgviewer->scale = 0; // disable autoscale
+        gtk_widget_queue_draw(imgviewer->widget);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+void ui_imageviewer_drag_begin_cb(
+        GtkGestureDrag *self,
+        gdouble start_x,
+        gdouble start_y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    imgviewer->begin_transx = imgviewer->transx;
+    imgviewer->begin_transy = imgviewer->transy;
+}
+
+void ui_imageviewer_drag_end_cb(
+        GtkGestureDrag* self,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    
+}
+
+void ui_imageviewer_drag_update_cb(
+        GtkGestureDrag *self,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->useradjustable) {
+        imgviewer->transx = imgviewer->begin_transx + x;
+        imgviewer->transy = imgviewer->begin_transy + y;
+        gtk_widget_queue_draw(imgviewer->widget);
+    }
+}
+
+static void imgviewer_button_event(
+        GtkGestureClick *gesture,
+        UiImageViewer *imgviewer,
+        ui_callback callback,
+        void *userdata)
+{
+    UiEvent event;
+    event.obj = imgviewer->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture));
+    event.set = 0;
+    callback(&event, userdata);
+}
+
+void ui_imageviewer_pressed_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->onbuttonpress) {
+        imgviewer_button_event(self, imgviewer, imgviewer->onbuttonpress, imgviewer->onbuttonpressdata);
+    }
+}
+
+void ui_imageviewer_released_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata)
+{
+    UiImageViewer *imgviewer = userdata;
+    if(imgviewer->onbuttonrelease) {
+        imgviewer_button_event(self, imgviewer, imgviewer->onbuttonrelease, imgviewer->onbuttonreleasedata);
+    }
+}
+
+#else
+
+gboolean ui_imageviewer_scroll_event(
+        GtkWidget *widget,
+        GdkEventScroll event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+gboolean ui_imageviewer_button_press_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+gboolean ui_imageviewer_button_release_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata)
+{
+    // TODO
+    return FALSE;
+}
+
+#endif
--- a/ui/gtk/image.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/image.h	Sat Apr 05 16:46:11 2025 +0200
@@ -36,11 +36,99 @@
 extern "C" {
 #endif
 
+typedef struct UiImageViewer {
+    UiObject *obj;
+    GtkWidget *widget;
+    UiVar *var;
+    int padding_left;
+    int padding_right;
+    int padding_top;
+    int padding_bottom;
+    UiBool autoscale;
+    UiBool adjustwidgetsize;
+    UiBool useradjustable;
+    GdkPixbuf *pixbuf;
+    
+    double zoom_scale;
+    int transx;
+    int transy;
+    int begin_transx;
+    int begin_transy;
+    UiBool isautoscaled;
+    double user_scale;
+    double scale;
+    
+    ui_callback onbuttonpress;
+    void *onbuttonpressdata;
+    ui_callback onbuttonrelease;
+    void *onbuttonreleasedata;
+} UiImageViewer;
+
+void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height);
 
 void* ui_imageviewer_get(UiGeneric *g);
 const char* ui_imageviewer_get_type(UiGeneric *g);
 int ui_imageviewer_set(UiGeneric *g, void *value, const char *type);
 
+#if GTK_MAJOR_VERSION >= 4
+
+gboolean ui_imageviewer_scroll(
+        GtkEventControllerScroll *widget,
+        gdouble dx,
+        gdouble dy,
+        gpointer userdata);
+
+void ui_imageviewer_drag_begin_cb(
+        GtkGestureDrag* self,
+        gdouble start_x,
+        gdouble start_y,
+        gpointer userdata);
+
+void ui_imageviewer_drag_end_cb(
+        GtkGestureDrag* self,
+        gdouble x,
+        gdouble y,
+        gpointer userdata);
+
+void ui_imageviewer_drag_update_cb(
+        GtkGestureDrag* self,
+        gdouble x,
+        gdouble y,
+        gpointer userdata);
+
+void ui_imageviewer_pressed_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata);
+
+void ui_imageviewer_released_cb(
+        GtkGestureClick *self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        gpointer userdata);
+
+#else
+
+gboolean ui_imageviewer_scroll_event(
+        GtkWidget *widget,
+        GdkEventScroll event,
+        gpointer userdata);
+
+gboolean ui_imageviewer_button_press_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata);
+
+gboolean ui_imageviewer_button_release_event(
+        GtkWidget *widget,
+        GdkEventButton event,
+        gpointer userdata);
+
+#endif
+
 #ifdef __cplusplus
 }
 #endif
--- a/ui/gtk/list.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/list.c	Sat Apr 05 16:46:11 2025 +0200
@@ -57,6 +57,14 @@
     };
 */
 
+static void listview_copy_static_elements(UiListView *listview, char **elm, size_t nelm) {
+    listview->elements = calloc(nelm, sizeof(char*));
+    listview->nelm = nelm;
+    for(int i=0;i<nelm;i++) {
+        listview->elements[i] = strdup(elm[i]);
+    }
+}
+
 #if GTK_CHECK_VERSION(4, 10, 0)
 
 
@@ -248,6 +256,10 @@
         list->setselection = ui_listview_setselection2;
         
         ui_update_liststore(ls, list);
+    } else if (args.static_elements && args.static_nelm > 0) {
+        listview_copy_static_elements(listview, args.static_elements, args.static_nelm);
+        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
     // event handling
@@ -323,11 +335,15 @@
         list->setselection = ui_combobox_setselection;
         
         ui_update_liststore(ls, list);
+    } else if (args.static_elements && args.static_nelm > 0) {
+        listview_copy_static_elements(listview, args.static_elements, args.static_nelm);
+        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
     // event handling
     if(args.onactivate) {
-        g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview);
+        g_signal_connect(view, "notify::selected", G_CALLBACK(ui_dropdown_notify), listview);
     }
     
     // add widget to parent 
@@ -336,6 +352,15 @@
     return view;
 }
 
+void ui_listview_select(UIWIDGET listview, int index) {
+    GtkSelectionModel *model = gtk_list_view_get_model(GTK_LIST_VIEW(listview));
+    gtk_selection_model_select_item(model, index, TRUE);
+}
+    
+void ui_combobox_select(UIWIDGET dropdown, int index) {
+    gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index);
+}
+
 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
     UiObject* current = uic_current_obj(obj);
     
@@ -449,6 +474,7 @@
     event.window = event.obj->window;
     event.intval = view->selection.count;
     event.eventdata = &view->selection;
+    event.set = ui_get_setop();
     if(cb) {
         cb(&event, cbdata);
     }
@@ -478,6 +504,24 @@
         free(newselection);
     }
 }
+
+void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata) {
+    UiListView *view = userdata;
+    guint index = gtk_drop_down_get_selected(GTK_DROP_DOWN(dropdown));
+    GObject *item = gtk_drop_down_get_selected_item(GTK_DROP_DOWN(dropdown));
+    if(item && view->onactivate) {
+        ObjWrapper *eventdata = (ObjWrapper*)item;
+        UiEvent event;
+        event.obj = view->obj;
+        event.document = event.obj->ctx->document;
+        event.window = event.obj->window;
+        event.intval = index;
+        event.eventdata = eventdata->data;
+        event.set = ui_get_setop();
+        view->onactivate(&event, view->onactivatedata);
+    }
+}
+    
     
 void ui_columnview_activate(void *ignore, guint position, gpointer userdata) {
     UiListView *view = userdata;
@@ -510,6 +554,7 @@
         event.window = event.obj->window;
         event.intval = view->selection.count;
         event.eventdata = &view->selection;
+        event.set = ui_get_setop();
         view->onactivate(&event, view->onactivatedata);
     }
 }
@@ -524,9 +569,27 @@
     }
 }
 
+void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm) {
+    g_list_store_remove_all(liststore);
+    for(int i=0;i<nelm;i++) {
+        ObjWrapper *obj = obj_wrapper_new(elm[i]);
+        g_list_store_append(liststore, obj);
+    }
+}
+
 void ui_listview_update2(UiList *list, int i) {
     UiListView *view = list->obj;
-    ui_update_liststore(view->liststore, list);
+    if(i < 0) {
+        ui_update_liststore(view->liststore, list);
+    } else {
+        void *value = list->get(list, i);
+        if(value) {
+            ObjWrapper *obj = obj_wrapper_new(value);
+            // TODO: if index i is selected, the selection is lost
+            // is it possible to update the item without removing it?
+            g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1);
+        }
+    }
 }
 
 UiListSelection ui_listview_getselection2(UiList *list) {
@@ -539,6 +602,7 @@
 }
 
 void ui_listview_setselection2(UiList *list, UiListSelection selection) {
+    ui_setop_enable(TRUE);
     UiListView *view = list->obj;
     UiListSelection newselection;
     newselection.count = view->selection.count;
@@ -557,6 +621,7 @@
             gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE);
         }
     }
+    ui_setop_enable(FALSE);
 }
 
 UiListSelection ui_combobox_getselection(UiList *list) {
@@ -572,16 +637,98 @@
 }
 
 void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    ui_setop_enable(TRUE);
     UiListView *view = list->obj;
     if(selection.count > 0) {
         gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]);
     } else {
         gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION);
     }
+    ui_setop_enable(FALSE);
 }
 
 #else
 
+static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, void *elm) {
+    // set column values
+    int c = 0;
+    for(int i=0;i<model->columns;i++,c++) {
+        void *data = model->getvalue(elm, c);
+
+        GValue value = G_VALUE_INIT;
+        switch(model->types[i]) {
+            case UI_STRING: 
+            case UI_STRING_FREE: {
+                g_value_init(&value, G_TYPE_STRING);
+                g_value_set_string(&value, data);
+                if(model->types[i] == UI_STRING_FREE) {
+                    free(data);
+                }
+                break;
+            }
+            case UI_INTEGER: {
+                g_value_init(&value, G_TYPE_INT);
+                intptr_t intptr = (intptr_t)data;
+                g_value_set_int(&value, (int)intptr);
+                break;
+            }
+            case UI_ICON: {
+                g_value_init(&value, G_TYPE_OBJECT);
+                UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                g_value_set_object(&value, icon->info); // TODO: does this work?
+#else
+                if(!icon->pixbuf && icon->info) {
+                    GError *error = NULL;
+                    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                    icon->pixbuf = pixbuf;
+                }
+
+                if(icon->pixbuf) {
+                    g_value_set_object(&value, icon->pixbuf);
+                }
+#endif
+                break;
+            }
+            case UI_ICON_TEXT:
+            case UI_ICON_TEXT_FREE: {
+                UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                if(icon) {
+                    GValue iconvalue = G_VALUE_INIT;
+                    g_value_init(&iconvalue, G_TYPE_OBJECT);
+                    g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
+                    gtk_list_store_set_value(store, &iter, c, &iconvalue);
+                }
+#else
+                GValue pixbufvalue = G_VALUE_INIT;
+                if(icon) {
+                    if(!icon->pixbuf && icon->info) {
+                        GError *error = NULL;
+                        GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                        icon->pixbuf = pixbuf;
+                    }
+                    g_value_init(&pixbufvalue, G_TYPE_OBJECT);
+                    g_value_set_object(&pixbufvalue, icon->pixbuf);
+                    gtk_list_store_set_value(store, iter, c, &pixbufvalue);
+                }
+#endif
+                c++;
+
+                char *str = model->getvalue(elm, c);
+                g_value_init(&value, G_TYPE_STRING);
+                g_value_set_string(&value, str);
+                if(model->types[i] == UI_ICON_TEXT_FREE) {
+                    free(str);
+                }
+                break;
+            }
+        }
+
+        gtk_list_store_set_value(store, iter, c, &value);
+    }
+}
+
 static GtkListStore* create_list_store(UiList *list, UiModel *model) {
     int columns = model->columns;
     GType types[2*columns];
@@ -609,83 +756,7 @@
             GtkTreeIter iter;
             gtk_list_store_insert (store, &iter, -1);
             
-            // set column values
-            int c = 0;
-            for(int i=0;i<columns;i++,c++) {
-                void *data = model->getvalue(elm, c);
-                
-                GValue value = G_VALUE_INIT;
-                switch(model->types[i]) {
-                    case UI_STRING: 
-                    case UI_STRING_FREE: {
-                        g_value_init(&value, G_TYPE_STRING);
-                        g_value_set_string(&value, data);
-                        if(model->types[i] == UI_STRING_FREE) {
-                            free(data);
-                        }
-                        break;
-                    }
-                    case UI_INTEGER: {
-                        g_value_init(&value, G_TYPE_INT);
-                        intptr_t intptr = (intptr_t)data;
-                        g_value_set_int(&value, (int)intptr);
-                        break;
-                    }
-                    case UI_ICON: {
-                        g_value_init(&value, G_TYPE_OBJECT);
-                        UiIcon *icon = data;
-#if GTK_MAJOR_VERSION >= 4
-                        g_value_set_object(&value, icon->info); // TODO: does this work?
-#else
-                        if(!icon->pixbuf && icon->info) {
-                            GError *error = NULL;
-                            GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
-                            icon->pixbuf = pixbuf;
-                        }
-                        
-                        if(icon->pixbuf) {
-                            g_value_set_object(&value, icon->pixbuf);
-                        }
-#endif
-                        break;
-                    }
-                    case UI_ICON_TEXT:
-                    case UI_ICON_TEXT_FREE: {
-                        UiIcon *icon = data;
-#if GTK_MAJOR_VERSION >= 4
-                        if(icon) {
-                            GValue iconvalue = G_VALUE_INIT;
-                            g_value_init(&iconvalue, G_TYPE_OBJECT);
-                            g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
-                            gtk_list_store_set_value(store, &iter, c, &iconvalue);
-                        }
-#else
-                        GValue pixbufvalue = G_VALUE_INIT;
-                        if(icon) {
-                            if(!icon->pixbuf && icon->info) {
-                                GError *error = NULL;
-                                GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
-                                icon->pixbuf = pixbuf;
-                            }
-                            g_value_init(&pixbufvalue, G_TYPE_OBJECT);
-                            g_value_set_object(&pixbufvalue, icon->pixbuf);
-                            gtk_list_store_set_value(store, &iter, c, &pixbufvalue);
-                        }
-#endif
-                        c++;
-                        
-                        char *str = model->getvalue(elm, c);
-                        g_value_init(&value, G_TYPE_STRING);
-                        g_value_set_string(&value, str);
-                        if(model->types[i] == UI_ICON_TEXT_FREE) {
-                            free(str);
-                        }
-                        break;
-                    }
-                }
-                
-                gtk_list_store_set_value(store, &iter, c, &value);
-            }
+            update_list_row(store, &iter, model, elm);
             
             // next row
             elm = list->next(list);
@@ -729,6 +800,7 @@
     g_object_unref(listmodel);
     
     UiListView *listview = malloc(sizeof(UiListView));
+    memset(listview, 0, sizeof(UiListView));
     listview->obj = obj;
     listview->widget = view;
     listview->var = var;
@@ -800,6 +872,17 @@
     return scroll_area;
 }
 
+void ui_listview_select(UIWIDGET listview, int index) {
+    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
+    GtkTreePath *path = gtk_tree_path_new_from_indicesv(&index, 1);
+    gtk_tree_selection_select_path(sel, path);
+    //g_object_unref(path);
+}
+    
+void ui_combobox_select(UIWIDGET dropdown, int index) {
+    gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index);
+}
+
 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
     UiObject* current = uic_current_obj(obj);
     
@@ -876,6 +959,7 @@
     // add TreeView as observer to the UiList to update the TreeView if the
     // data changes
     UiListView *tableview = malloc(sizeof(UiListView));
+    memset(tableview, 0, sizeof(UiListView));
     tableview->obj = obj;
     tableview->widget = view;
     tableview->var = var;
@@ -969,9 +1053,18 @@
 
 void ui_listview_update(UiList *list, int i) {
     UiListView *view = list->obj;
-    GtkListStore *store = create_list_store(list, view->model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
-    g_object_unref(G_OBJECT(store));
+    if(i < 0) {
+        GtkListStore *store = create_list_store(list, view->model);
+        gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
+        g_object_unref(G_OBJECT(store));
+    } else {
+        void *elm = list->get(list, i);
+        GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget));
+        GtkTreeIter iter;
+        if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) {
+            update_list_row(GTK_LIST_STORE(store), &iter, view->model, elm);
+        }
+    }
 }
 
 UiListSelection ui_listview_getselection(UiList *list) {
@@ -983,11 +1076,13 @@
 }
 
 void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    ui_setop_enable(TRUE);
     UiListView *view = list->obj;
     GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
     GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
     gtk_tree_selection_select_path(sel, path);
     //g_object_unref(path);
+    ui_setop_enable(FALSE);
 }
 
 
@@ -1002,7 +1097,7 @@
     
     UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
     
-    GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
+    GtkWidget *combobox = ui_create_combobox(obj, model, var, args.static_elements, args.static_nelm, args.onactivate, args.onactivatedata);
     ui_set_name_and_style(combobox, args.name, args.style_class);
     ui_set_widget_groups(obj->ctx, combobox, args.groups);
     UI_APPLY_LAYOUT1(current, args);
@@ -1011,16 +1106,29 @@
     return combobox;
 }
 
-GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata) {
     GtkWidget *combobox = gtk_combo_box_new();
        
     UiListView *uicbox = malloc(sizeof(UiListView));
+    memset(uicbox, 0, sizeof(UiListView));
     uicbox->obj = obj;
     uicbox->widget = combobox;
     
     UiList *list = var ? var->value : NULL;
     GtkListStore *listmodel = create_list_store(list, model);
     
+    if(!list && elm && nelm > 0) {
+        listview_copy_static_elements(uicbox, elm, nelm);
+        for(int i=0;i<nelm;i++) {
+            GtkTreeIter iter;
+            GValue value = G_VALUE_INIT;
+            gtk_list_store_insert(listmodel, &iter, -1);
+            g_value_init(&value, G_TYPE_STRING);
+            g_value_set_string(&value, uicbox->elements[i]);
+            gtk_list_store_set_value(listmodel, &iter, 0, &value);
+        }
+    }
+    
     if(listmodel) {
         gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
         g_object_unref(listmodel);
@@ -1060,7 +1168,7 @@
         event->userdata = udata;
         event->callback = f;
         event->value = 0;
-        event->customdata = NULL;
+        event->customdata = uicbox;
 
         g_signal_connect(
                 combobox,
@@ -1073,12 +1181,23 @@
 }
 
 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+    int index = gtk_combo_box_get_active(widget);
+    UiListView *listview = e->customdata;
+    void *eventdata = NULL;
+    if(listview->var && listview->var->value) {
+        UiList *list = listview->var->value;
+        eventdata = ui_list_get(list, index);
+    } else if(listview->elements && listview->nelm > index) {
+        eventdata = listview->elements[index];
+    } 
+    
     UiEvent event;
     event.obj = e->obj;
     event.window = event.obj->window;
     event.document = event.obj->ctx->document;
-    event.eventdata = NULL;
-    event.intval = gtk_combo_box_get_active(widget);
+    event.eventdata = eventdata;
+    event.intval = index;
+    event.set = ui_get_setop();
     e->callback(&event, e->userdata);
 }
 
@@ -1099,10 +1218,12 @@
 }
 
 void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    ui_setop_enable(TRUE);
     UiListView *combobox = list->obj;
     if(selection.count > 0) {
         gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
     }
+    ui_setop_enable(FALSE);
 }
 
 
@@ -1124,6 +1245,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = &selection;
     e.intval = selection.count > 0 ? selection.rows[0] : -1;
+    e.set = ui_get_setop();
     event->activate(&e, event->activatedata);
     
     if(selection.count > 0) {
@@ -1143,6 +1265,7 @@
     e.document = event->obj->ctx->document;
     e.eventdata = &selection;
     e.intval = selection.count > 0 ? selection.rows[0] : -1;
+    e.set = ui_get_setop();
     event->selection(&e, event->selectiondata);
     
     if(selection.count > 0) {
@@ -1201,6 +1324,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondragstart(&event, listview->ondragstartdata);
     }
     
@@ -1236,6 +1360,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondragcomplete(&event, listview->ondragcompletedata);
     }
 }
@@ -1264,6 +1389,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondrop(&event, listview->ondropdata);
     }
     
@@ -1326,6 +1452,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondragstart(&event, listview->ondragstartdata);
     }
 }
@@ -1350,6 +1477,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondragcomplete(&event, listview->ondragcompletedata);
     }
 }
@@ -1395,6 +1523,7 @@
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
         event.intval = 0;
+        event.set = ui_get_setop();
         listview->ondrop(&event, listview->ondropdata);
     }
 }
@@ -1496,7 +1625,15 @@
 
 void ui_listview_destroy(GtkWidget *w, UiListView *v) {
     //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
-    ui_destroy_boundvar(v->obj->ctx, v->var);
+    if(v->var) {
+        ui_destroy_boundvar(v->obj->ctx, v->var);
+    }
+    if(v->elements) {
+        for(int i=0;i<v->nelm;i++) {
+            free(v->elements[i]);
+        }
+        free(v->elements);
+    }
 #if GTK_CHECK_VERSION(4, 10, 0)
     free(v->columns);
 #endif
@@ -1505,7 +1642,15 @@
 }
 
 void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
-    ui_destroy_boundvar(v->obj->ctx, v->var);
+    if(v->var) {
+        ui_destroy_boundvar(v->obj->ctx, v->var);
+    }
+    if(v->elements) {
+        for(int i=0;i<v->nelm;i++) {
+            free(v->elements[i]);
+        }
+        free(v->elements);
+    }
     free(v);
 }
 
@@ -1851,6 +1996,7 @@
     event.document = event.obj->ctx->document;
     event.eventdata = &eventdata;
     event.intval = data->value0;
+    event.set = ui_get_setop();
     
     if(data->callback) {
         data->callback(&event, data->userdata);
--- a/ui/gtk/list.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/list.h	Sat Apr 05 16:46:11 2025 +0200
@@ -45,6 +45,8 @@
     GtkWidget         *widget;
     UiVar             *var;
     UiModel           *model;
+    char              **elements;
+    size_t            nelm;
 #if GTK_CHECK_VERSION(4, 10, 0)
     GListStore        *liststore;
     GtkSelectionModel *selectionmodel;
@@ -100,7 +102,6 @@
     void                     *onactivatedata;
     ui_callback              onbuttonclick;
     void                     *onbuttonclickdata;
-    
     GtkListBoxRow            *first_row;
 };
 
@@ -108,11 +109,13 @@
 #if GTK_CHECK_VERSION(4, 10, 0)
 
 void ui_update_liststore(GListStore *liststore, UiList *list);
+void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm);
 
 void ui_listview_update2(UiList *list, int i);
 UiListSelection ui_listview_getselection2(UiList *list);
 void ui_listview_setselection2(UiList *list, UiListSelection selection);
 
+void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata);
 void ui_columnview_activate(void *ignore, guint position, gpointer userdata);
 void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data);
 
@@ -155,7 +158,7 @@
 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
 
 UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
-GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata);
 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
 void ui_combobox_modelupdate(UiList *list, int i);
 UiListSelection ui_combobox_getselection(UiList *list);
--- a/ui/gtk/objs.mk	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/objs.mk	Sat Apr 05 16:46:11 2025 +0200
@@ -47,6 +47,7 @@
 GTKOBJ += dnd.o
 GTKOBJ += headerbar.o
 GTKOBJ += webview.o
+GTKOBJ += widget.o
 
 TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
 TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- a/ui/gtk/text.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/text.c	Sat Apr 05 16:46:11 2025 +0200
@@ -62,6 +62,51 @@
     }
 }
 
+static void textarea_set_text_funcs(UiText *value) {
+    
+}
+
+#if GTK_MAJOR_VERSION == 2
+static void textarea_set_undomgr(GtkWidget *text_area, UiText *value) {
+    GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
+
+    if(!value->data2) {
+        value->data2 = ui_create_undomgr();
+    }
+
+    // register undo manager
+    g_signal_connect(
+            buf,
+            "insert-text",
+            G_CALLBACK(ui_textbuf_insert),
+            var);
+    g_signal_connect(
+            buf,
+            "delete-range",
+            G_CALLBACK(ui_textbuf_delete),
+            var); 
+    g_signal_connect(
+            buf,
+            "mark-set",
+            G_CALLBACK(selection_handler),
+            uitext);
+}
+#endif
+
+static GtkTextBuffer* create_textbuffer(UiTextArea *textarea) {
+    GtkTextBuffer *buf = gtk_text_buffer_new(NULL);
+    if(textarea) {
+        g_signal_connect(
+                buf,
+                "changed",
+                G_CALLBACK(ui_textbuf_changed),
+                textarea);
+    } else {
+        fprintf(stderr, "Error: create_textbuffer: textarea == NULL\n");
+    }
+    return buf;
+}
+
 UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
     UiObject* current = uic_current_obj(obj);
     UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);
@@ -85,6 +130,8 @@
     uitext->onchange = args.onchange;
     uitext->onchangedata = args.onchangedata;
     
+    g_object_set_data(G_OBJECT(text_area), "ui_textarea", uitext);
+    
     g_signal_connect(
                 text_area,
                 "destroy",
@@ -114,13 +161,21 @@
     // bind value
     if(var) {
         UiText *value = var->value;
-        GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
-        
-        if(value->value.ptr) {
-            gtk_text_buffer_set_text(buf, value->value.ptr, -1);
-            value->value.free(value->value.ptr);
+        GtkTextBuffer *buf;
+        if(value->data1 && value->datatype == UI_TEXT_TYPE_BUFFER) {
+            buf = value->data1;
+        } else {
+            buf = create_textbuffer(uitext);
+            if(value->value.ptr) {
+                gtk_text_buffer_set_text(buf, value->value.ptr, -1);
+                value->value.free(value->value.ptr);
+            }
         }
-        
+        gtk_text_view_set_buffer(GTK_TEXT_VIEW(text_area), buf);
+        value->obj = text_area;
+        value->save = ui_textarea_save;
+        value->restore = ui_textarea_restore;
+        value->destroy = ui_textarea_text_destroy;
         value->get = ui_textarea_get;
         value->set = ui_textarea_set;
         value->getsubstr = ui_textarea_getsubstr;
@@ -130,35 +185,15 @@
         value->selection = ui_textarea_selection;
         value->length = ui_textarea_length;
         value->remove = ui_textarea_remove;
+        value->data1 = buf;
+        value->data2 = NULL;
+        value->datatype == UI_TEXT_TYPE_BUFFER;
         value->value.ptr = NULL;
         value->value.free = NULL;
-        value->obj = buf;
-        if(!value->undomgr) {
-            value->undomgr = ui_create_undomgr();
-        }
         
-        g_signal_connect(
-                buf,
-                "changed",
-                G_CALLBACK(ui_textbuf_changed),
-                uitext);
-        
-        // register undo manager
-        g_signal_connect(
-                buf,
-                "insert-text",
-                G_CALLBACK(ui_textbuf_insert),
-                var);
-        g_signal_connect(
-                buf,
-                "delete-range",
-                G_CALLBACK(ui_textbuf_delete),
-                var); 
-        g_signal_connect(
-                buf,
-                "mark-set",
-                G_CALLBACK(selection_handler),
-                uitext);
+#if GTK_MAJOR_VERSION == 2
+        textarea_set_undomgr(text_area, value);
+#endif
     }
     
     return scroll_area;
@@ -175,11 +210,29 @@
     return SCROLLEDWINDOW_GET_CHILD(textarea);
 }
 
+void ui_textarea_save(UiText *text) {
+    // NOOP
+}
+
+void ui_textarea_restore(UiText *text) {
+    GtkWidget *textarea = text->obj;
+    if(!text->data1) {
+        text->data1 = create_textbuffer(g_object_get_data(G_OBJECT(textarea), "ui_textarea"));
+        text->datatype = UI_TEXT_TYPE_BUFFER;
+    }
+    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textarea), text->data1);
+}
+
+void ui_textarea_text_destroy(UiText *text) {
+    GtkTextBuffer *buf = text->data1;
+    g_object_unref(buf);
+}
+
 char* ui_textarea_get(UiText *text) {
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
     }
-    GtkTextBuffer *buf = text->obj;
+    GtkTextBuffer *buf = text->data1;
     GtkTextIter start;
     GtkTextIter end;
     gtk_text_buffer_get_bounds(buf, &start, &end);
@@ -190,7 +243,7 @@
 }
 
 void ui_textarea_set(UiText *text, const char *str) {
-    gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
+    gtk_text_buffer_set_text((GtkTextBuffer*)text->data1, str, -1);
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
     }
@@ -202,11 +255,11 @@
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
     }
-    GtkTextBuffer *buf = text->obj;
+    GtkTextBuffer *buf = text->data1;
     GtkTextIter ib;
     GtkTextIter ie;
-    gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
-    gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
+    gtk_text_buffer_get_iter_at_offset(text->data1, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(text->data1, &ie, end);
     char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
     text->value.ptr = g_strdup(str);
     text->value.free = (ui_freefunc)g_free;
@@ -215,8 +268,8 @@
 
 void ui_textarea_insert(UiText *text, int pos, char *str) {
     GtkTextIter offset;
-    gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
-    gtk_text_buffer_insert(text->obj, &offset, str, -1);
+    gtk_text_buffer_get_iter_at_offset(text->data1, &offset, pos);
+    gtk_text_buffer_insert(text->data1, &offset, str, -1);
     if(text->value.ptr) {
         text->value.free(text->value.ptr);
     }
@@ -226,14 +279,14 @@
 
 void ui_textarea_setposition(UiText *text, int pos) {
     GtkTextIter iter;
-    gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
-    gtk_text_buffer_place_cursor(text->obj, &iter);
+    gtk_text_buffer_get_iter_at_offset(text->data1, &iter, pos);
+    gtk_text_buffer_place_cursor(text->data1, &iter);
 }
 
 int ui_textarea_position(UiText *text) {
     GtkTextIter begin;
     GtkTextIter end;
-    gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
+    gtk_text_buffer_get_selection_bounds(text->data1, &begin, &end);
     text->pos = gtk_text_iter_get_offset(&begin);
     return text->pos;
 }
@@ -241,13 +294,13 @@
 void ui_textarea_selection(UiText *text, int *begin, int *end) {
     GtkTextIter b;
     GtkTextIter e;
-    gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
+    gtk_text_buffer_get_selection_bounds(text->data1, &b, &e);
     *begin = gtk_text_iter_get_offset(&b);
     *end = gtk_text_iter_get_offset(&e);
 }
 
 int ui_textarea_length(UiText *text) {
-    GtkTextBuffer *buf = text->obj;
+    GtkTextBuffer *buf = text->data1;
     GtkTextIter start;
     GtkTextIter end;
     gtk_text_buffer_get_bounds(buf, &start, &end);
@@ -255,7 +308,7 @@
 }
 
 void ui_textarea_remove(UiText *text, int begin, int end) {
-    GtkTextBuffer *buf = text->obj;
+    GtkTextBuffer *buf = text->data1;
     GtkTextIter ib;
     GtkTextIter ie;
     gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
@@ -278,6 +331,7 @@
     e.document = textarea->ctx->document;
     e.eventdata = value;
     e.intval = 0;
+    e.set = ui_get_setop();
     
     if(textarea->onchange) {
         textarea->onchange(&e, textarea->onchangedata);
@@ -299,10 +353,10 @@
 {
     UiVar *var = data;
     UiText *value = var->value;
-    if(!value->undomgr) {
-        value->undomgr = ui_create_undomgr();
+    if(!value->data2) {
+        value->data2 = ui_create_undomgr();
     }
-    UiUndoMgr *mgr = value->undomgr;
+    UiUndoMgr *mgr = value->data2;
     if(!mgr->event) {
         return;
     }
@@ -371,10 +425,10 @@
 {
     UiVar *var = data;
     UiText *value = var->value;
-    if(!value->undomgr) {
-        value->undomgr = ui_create_undomgr();
+    if(!value->data2) {
+        value->data2 = ui_create_undomgr();
     }
-    UiUndoMgr *mgr = value->undomgr;
+    UiUndoMgr *mgr = value->data2;
     if(!mgr->event) {
         return;
     }
@@ -469,7 +523,7 @@
 }
 
 void ui_text_undo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
+    UiUndoMgr *mgr = value->data2;
     
     if(mgr->cur) {
         UiTextBufOp *op = mgr->cur;
@@ -498,7 +552,7 @@
 }
 
 void ui_text_redo(UiText *value) {
-    UiUndoMgr *mgr = value->undomgr;
+    UiUndoMgr *mgr = value->data2;
     
     UiTextBufOp *elm = NULL;
     if(mgr->cur) {
@@ -638,6 +692,7 @@
     e.document = textfield->obj->ctx->document;
     e.eventdata = value;
     e.intval = 0;
+    e.set = ui_get_setop();
     
     if(textfield->onchange) {
         textfield->onchange(&e, textfield->onchangedata);
@@ -656,6 +711,7 @@
         e.document = textfield->obj->ctx->document;
         e.eventdata = NULL;
         e.intval = 0;
+        e.set = ui_get_setop();
         textfield->onactivate(&e, textfield->onactivatedata);
     }
 }
@@ -752,6 +808,7 @@
     evt.document = evt.obj->ctx->document;
     evt.eventdata = elm->path;
     evt.intval = event->value0;
+    evt.set = ui_get_setop();
     event->callback(&evt, event->userdata);
     free(path.ptr);
 }
--- a/ui/gtk/text.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/text.h	Sat Apr 05 16:46:11 2025 +0200
@@ -111,6 +111,9 @@
 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
 
+void ui_textarea_save(UiText *text);
+void ui_textarea_restore(UiText *text);
+void ui_textarea_text_destroy(UiText *text);
 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);
--- a/ui/gtk/toolkit.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/toolkit.c	Sat Apr 05 16:46:11 2025 +0200
@@ -248,8 +248,9 @@
 }
 
 void ui_set_visible(UIWIDGET widget, int visible) {
-    // TODO: gtk4
-#if GTK_MAJOR_VERSION <= 3
+#if GTK_MAJOR_VERSION >= 4
+    gtk_widget_set_visible(widget, visible);
+#else
     if(visible) {
         gtk_widget_set_no_show_all(widget, FALSE);
         gtk_widget_show_all(widget);
@@ -327,8 +328,6 @@
 
 #if GTK_MAJOR_VERSION >= 3
 
-static GtkCssProvider* ui_gtk_css_provider;
-
 #if GTK_MAJOR_VERSION == 4
 static const char *ui_gtk_css = 
 "#path-textfield-box {\n"
@@ -415,15 +414,19 @@
 #endif
 
 void ui_css_init(void) {
-    ui_gtk_css_provider = gtk_css_provider_new();
+    ui_add_styledata(ui_gtk_css, -1);
+}
+
+void ui_add_styledata(const char *styledata, int len) {
+    GtkCssProvider *css = gtk_css_provider_new();
     
 #ifdef UI_GTK3
-    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL);
+    gtk_css_provider_load_from_data(css, styledata, len, NULL);
     
     GdkScreen *screen = gdk_screen_get_default();
     gtk_style_context_add_provider_for_screen(
             screen,
-            GTK_STYLE_PROVIDER(ui_gtk_css_provider),
+            GTK_STYLE_PROVIDER(css),
             GTK_STYLE_PROVIDER_PRIORITY_USER);
 #endif /* UI_GTK3 */
     
@@ -431,13 +434,20 @@
     
     
 #if GTK_MINOR_VERSION < 12
-    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1);
+    gtk_css_provider_load_from_data(css, styledata, len);
 #else
-    gtk_css_provider_load_from_string(ui_gtk_css_provider, ui_gtk_css);
+    if(len < 0) {
+        gtk_css_provider_load_from_string(css, ui_gtk_css);
+    } else {
+        GBytes *style_data = g_bytes_new(styledata, len);
+        gtk_css_provider_load_from_bytes(css, style_data);
+        g_bytes_unref(style_data);
+        
+    }
 #endif /* GTK_MINOR_VERSION < 12 */
     
     GdkDisplay *display = gdk_display_get_default();
-    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
     
 #endif /* UI_GTK4 */
 }
--- a/ui/gtk/toolkit.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/toolkit.h	Sat Apr 05 16:46:11 2025 +0200
@@ -74,6 +74,8 @@
 #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon)
 #define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row)
 #define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child)
+#define PANED_SET_CHILD1(paned, child) gtk_paned_set_start_child(GTK_PANED(paned), child)
+#define PANED_SET_CHILD2(paned, child) gtk_paned_set_end_child(GTK_PANED(paned), child)
 #else
 #define WINDOW_SHOW(window) gtk_widget_show_all(window)
 #define WINDOW_DESTROY(window) gtk_widget_destroy(window)
@@ -94,6 +96,8 @@
 #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON)
 #define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row)
 #define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child)
+#define PANED_SET_CHILD1(paned, child) gtk_paned_pack1(GTK_PANED(paned), child, TRUE, TRUE)
+#define PANED_SET_CHILD2(paned, child) gtk_paned_pack2(GTK_PANED(paned), child, TRUE, TRUE)
 #endif
     
 #ifdef UI_GTK2
--- a/ui/gtk/webview.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/gtk/webview.h	Sat Apr 05 16:46:11 2025 +0200
@@ -31,7 +31,12 @@
 #ifdef UI_WEBVIEW
 
 #include "../ui/webview.h"
+
+#if GTK_MAJOR_VERSION >= 4
 #include <webkit/webkit.h>
+#else
+#include <webkit2/webkit2.h>
+#endif
 
 #ifdef __cplusplus
 extern "C" {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/widget.c	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,60 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "widget.h"
+#include "container.h"
+
+#include "../common/object.h"
+
+UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UIWIDGET widget = create_widget(obj, args, userdata);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
+}
+
+UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+    ui_set_name_and_style(widget, args->name, args->style_class);
+    UI_APPLY_LAYOUT1(current, (*args));
+    current->container->add(current->container, widget, FALSE);
+    return widget;
+}
+
+void ui_widget_set_size(UIWIDGET w, int width, int height) {
+    gtk_widget_set_size_request(w, width, height);
+}
+
+void ui_widget_redraw(UIWIDGET w) {
+    gtk_widget_queue_draw(w);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/widget.h	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include "../ui/widget.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WIDGET_H */
+
--- a/ui/motif/button.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/button.c	Sat Apr 05 16:46:11 2025 +0200
@@ -92,6 +92,7 @@
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.intval = event->value;
+    e.set = 0;
     event->callback(&e, event->userdata);
 }
 
@@ -173,6 +174,7 @@
     e.document = e.obj->ctx->document;
     e.eventdata = NULL;
     e.intval = XmToggleButtonGetState(w);
+    e.set = ui_get_setop();
     
     if(event->callback) {
         event->callback(&e, event->userdata);
@@ -281,6 +283,7 @@
     e.document = e.obj->ctx->document;
     e.eventdata = value;
     e.intval = v;
+    e.set = ui_get_setop();
     
     if(event->callback) {
         event->callback(&e, event->userdata);
--- a/ui/motif/container.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/container.c	Sat Apr 05 16:46:11 2025 +0200
@@ -39,20 +39,7 @@
 #include "Grid.h"
 
 
-UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
-    Arg xargs[64];
-    int n = 0;
-    
-    UiContainerPrivate *ctn = ui_obj_container(obj);
-    UI_APPLY_LAYOUT(ctn->layout, args);
-    
-    Widget parent = ctn->prepare(ctn, xargs, &n);
-    Widget widget = create_widget(obj, args, userdata, parent, xargs, n);
-    XtManageChild(widget);
-    ctn->add(ctn, widget);
-    
-    return widget;
-}
+
 
 /* ---------------------------- Box Container ---------------------------- */
 
@@ -156,13 +143,20 @@
     Widget grid = XtCreateManagedWidget(args.name ? args.name : "gridcontainer", gridClass, parent, xargs, n);
     ctn->add(ctn, grid);
     
-    UiContainerX *container = ui_grid_container(obj, grid);
+    UiContainerX *container = ui_grid_container(obj, grid, args.def_hexpand, args.def_vexpand, args.def_hfill, args.def_vfill);
     uic_object_push_container(obj, container);
     
     return grid;
 }
 
-UiContainerX* ui_grid_container(UiObject *obj, Widget grid) {
+UiContainerX* ui_grid_container(
+        UiObject *obj,
+        Widget grid,
+        UiBool def_hexpand,
+        UiBool def_vexpand,
+        UiBool def_hfill,
+        UiBool def_vfill)
+{
     UiGridContainer *ctn = ui_malloc(obj->ctx, sizeof(UiGridContainer));
     memset(ctn, 0, sizeof(UiBoxContainer));
     ctn->container.prepare = ui_grid_container_prepare;
@@ -170,6 +164,10 @@
     ctn->container.widget = grid;
     ctn->x = 0;
     ctn->y = 0;
+    ctn->def_hexpand = def_hexpand;
+    ctn->def_vexpand = def_vexpand;
+    ctn->def_hfill = def_hfill;
+    ctn->def_vfill = def_vfill;
     return (UiContainerX*)ctn;
 }
 
@@ -190,23 +188,54 @@
         XtSetArg(args[a], gridRowspan, ctn->layout.rowspan); a++;
     }
     
-    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;
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    int hfill = FALSE;
+    int vfill = FALSE;
+    if(!ctn->layout.override_defaults) {
+        if(grid->def_hexpand) {
+            hexpand = TRUE;
+            hfill = TRUE;
+        } else if(grid->def_hfill) {
+            hfill = TRUE;
+        }
+        if(grid->def_vexpand) {
+            vexpand = TRUE;
+            vfill = TRUE;
+        } else if(grid->def_vfill) {
+            vfill = TRUE;
+        }
     }
     
-    if(grid->container.layout.hfill) {
+    if(ctn->layout.hexpand) {
+        hexpand = TRUE;
+        hfill = TRUE;
+    } else if(ctn->layout.hfill) {
+        hfill = TRUE;
+    }
+    if(ctn->layout.vexpand) {
+        vexpand = TRUE;
+        vfill = TRUE;
+    } else if(ctn->layout.vfill) {
+        vfill = TRUE;
+    }
+    if(ctn->layout.fill == UI_ON) {
+        hexpand = TRUE;
+        vexpand = TRUE;
+        hfill = TRUE;
+        vfill = TRUE;
+    }
+    
+    if(hfill) {
         XtSetArg(args[a], gridHFill, TRUE); a++;
     }
-    if(grid->container.layout.vfill) {
+    if(vfill) {
         XtSetArg(args[a], gridVFill, TRUE); a++;
     }
-    if(grid->container.layout.hexpand) {
+    if(hexpand) {
         XtSetArg(args[a], gridHExpand, TRUE); a++;
     }
-    if(grid->container.layout.vexpand) {
+    if(vexpand) {
         XtSetArg(args[a], gridVExpand, TRUE); a++;
     }
     
@@ -557,7 +586,43 @@
     ui_reset_layout(ctn->layout);
 }
 
+/* -------------------- ScrolledWindow -------------------- */
 
+Widget ui_scrolledwindow_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+    return ctn->widget;
+}
+
+void ui_scrolledwindow_add(UiContainerPrivate *ctn, Widget widget) {
+    
+}
+
+static UiContainerX* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
+    UiContainerPrivate *ctn = ui_malloc(obj->ctx, sizeof(UiContainerPrivate));
+    memset(ctn, 0, sizeof(UiContainerPrivate));
+    ctn->prepare = ui_scrolledwindow_prepare;
+    ctn->add = ui_scrolledwindow_add;
+    ctn->widget = scrolledwindow;
+    return (UiContainerX*)ctn;
+}
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Arg xargs[16];
+    int n = 0;
+    
+    XtSetArg(xargs[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", xargs, n);
+    ctn->add(ctn, scrolledwindow);
+    
+    UiContainerX *container = ui_scrolledwindow_container(obj, scrolledwindow);
+    uic_object_push_container(obj, container);
+    
+    return scrolledwindow;
+}
 
 /* -------------------- Container Helper Functions -------------------- */
 
--- a/ui/motif/container.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/container.h	Sat Apr 05 16:46:11 2025 +0200
@@ -44,6 +44,7 @@
     layout.vexpand = args.vexpand; \
     layout.hfill = args.hfill; \
     layout.vfill = args.vfill; \
+    layout.override_defaults = args.override_defaults; \
     layout.colspan = args.colspan; \
     layout.rowspan = args.rowspan
     
@@ -71,6 +72,7 @@
     UiBool       vexpand;
     UiBool       hfill;
     UiBool       vfill;
+    UiBool       override_defaults;
     int          width;
     int          colspan;
     int          rowspan;
@@ -102,6 +104,10 @@
     UiContainerPrivate container;
     Dimension x;
     Dimension y;
+    UiBool def_hexpand;
+    UiBool def_vexpand;
+    UiBool def_hfill;
+    UiBool def_vfill;
 } UiGridContainer;
 
 typedef struct UiTab {
@@ -153,7 +159,13 @@
 void ui_box_container_add(UiContainerPrivate *ctn, Widget widget);
 
 
-UiContainerX* ui_grid_container(UiObject *obj, Widget grid);
+UiContainerX* ui_grid_container(
+        UiObject *obj,
+        Widget grid,
+        UiBool def_hexpand,
+        UiBool def_vexpand,
+        UiBool def_hfill,
+        UiBool def_vfill);
 Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
 void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget);
 
--- a/ui/motif/list.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/list.c	Sat Apr 05 16:46:11 2025 +0200
@@ -186,11 +186,13 @@
 }
 
 void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    ui_setop_enable(TRUE);
     UiListView *listview = list->obj;
     XmListDeselectAllItems(listview->widget);
     for(int i=0;i<selection.count;i++) {
         XmListSelectPos(listview->widget, selection.rows[i]+1, False);
     }
+    ui_setop_enable(FALSE);
 }
 
 void* ui_strmodel_getvalue(void *elm, int column) {
--- a/ui/motif/objs.mk	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/objs.mk	Sat Apr 05 16:46:11 2025 +0200
@@ -32,6 +32,7 @@
 MOTIFOBJ = toolkit.o
 MOTIFOBJ += stock.o
 MOTIFOBJ += window.o
+MOTIFOBJ += widget.o
 MOTIFOBJ += container.o
 MOTIFOBJ += menu.o
 MOTIFOBJ += toolbar.o
--- a/ui/motif/text.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/text.c	Sat Apr 05 16:46:11 2025 +0200
@@ -793,11 +793,12 @@
     }
     
     UiPathElm elm = bar->current_pathelms[i];
-    cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len));
+    cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len));
     if(bar->updateDir) {
-        bar->updateDir(bar->updateDirData, name.ptr, i);
+        XNETextSetString(bar->textfield, path.ptr);
+        bar->updateDir(bar->updateDirData, path.ptr, i);
     }
-    free(name.ptr);
+    free(path.ptr);
 }
 
 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
--- a/ui/motif/toolkit.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/toolkit.c	Sat Apr 05 16:46:11 2025 +0200
@@ -164,7 +164,10 @@
 
 void ui_show(UiObject *obj) {
     uic_check_group_widgets(obj->ctx);
-    XtRealizeWidget(obj->widget);
+    if(!XtIsRealized(obj->widget)) {
+        XtRealizeWidget(obj->widget);
+        obj->ref++;
+    }
 }
 
 void ui_close(UiObject *obj) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/widget.c	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,71 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "../ui/widget.h"
+
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+    Arg xargs[64];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget widget = create_widget(obj, args, userdata, parent, xargs, n);
+    XtManageChild(widget);
+    ctn->add(ctn, widget);
+    
+    return widget;
+}
+
+
+UIEXPORT UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) {
+    Arg xargs[64];
+    int n = 0;
+    UiWidgetArgs a = *args;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, a);
+    
+    char *name = a.name ? (char*)a.name : "separator";
+    Widget parent = ctn->prepare(ctn, xargs, &n);
+    Widget widget = XmCreateSeparator(parent, name, xargs, n);
+    XtManageChild(widget);
+    ctn->add(ctn, widget);
+    
+    return widget;
+}
--- a/ui/motif/window.c	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/motif/window.c	Sat Apr 05 16:46:11 2025 +0200
@@ -45,16 +45,27 @@
 static int window_default_width = 600;
 static int window_default_height = 500;
 
-static void window_close_handler(Widget window, void *udata, void *cdata) {
-    UiObject *obj = udata;
+void ui_window_widget_destroy(UiObject *obj) {
+    XtDestroyWidget(obj->widget);
     uic_object_destroy(obj);
-    
     nwindows--;
     if(nwindows == 0) {
         ui_exit_mainloop();
     }
 }
 
+static void window_close_handler(Widget window, void *udata, void *cdata) {
+    UiObject *obj = udata;
+     
+    uic_context_prepare_close(obj->ctx);
+    obj->ref--;
+    if(obj->ref > 0) {
+        XtUnmapWidget(obj->widget);
+    } else {
+        ui_window_widget_destroy(obj);
+    }
+}
+
 
 static UiObject* create_window(const char *title, void *window_data, Boolean simple) {
     CxMempool *mp = cxMempoolCreateSimple(256);
@@ -62,6 +73,7 @@
     UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
+    obj->destroy = ui_window_widget_destroy;
     
     Arg args[16];
     int n = 0;
--- a/ui/qt/Makefile	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/Makefile	Sat Apr 05 16:46:11 2025 +0200
@@ -30,8 +30,8 @@
 
 UI_QT_LIB = ../build/ui/qt/
 
-$(QT_MAKEFILE): qt/qt4.pro
-	qmake-qt4 -o - qt/qt4.pro > $(QT_MAKEFILE)
+$(QT_MAKEFILE): qt/$(QT_PRO_FILE)
+	$(QMAKE) -o - $< > $(QT_MAKEFILE)
 
 $(UI_LIB): $(QT_MAKEFILE) $(OBJ) FORCE
 	$(MAKE) -f $(QT_MAKEFILE)
--- a/ui/qt/button.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/button.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -30,57 +30,193 @@
 #include "container.h"
 #include "toolkit.h"
 
-UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
-    QString str = QString::fromUtf8(label);
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QString str = QString::fromUtf8(args.label);
     QPushButton *button = new QPushButton(str);
     
-    if(f) {
-        UiEventWrapper *event = new UiEventWrapper(obj, f, data);
+    if(args.onclick) {
+        UiEventWrapper *event = new UiEventWrapper(obj, args.onclick, args.onclickdata);
         button->connect(button, SIGNAL(clicked()), event, SLOT(slot()));
+        button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy()));
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(button, false);
+    ctn->add(button, false);
+    
+    return button;
+}
+
+static void togglebutton_event(UiEvent *event, UiEventWrapper *wrapper) {
+    QPushButton *button = (QPushButton*)wrapper->customdata1;
+    event->intval = button->isChecked();
+    if(wrapper->var) {
+        event->eventdata = wrapper->var->value;
+    }
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QString str = QString::fromUtf8(args.label);
+    QPushButton *button = new QPushButton(str);
+    button->setCheckable(true);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    
+    if(args.onchange) {
+        UiEventWrapper *event = new UiEventWrapper(obj, args.onchange, args.onchangedata);
+        event->var = var;
+        event->customdata1 = button;
+        event->prepare_event = togglebutton_event;
+        button->connect(button, SIGNAL(clicked()), event, SLOT(slot()));
+        button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy()));
+    }
+    
+    if(var) {
+        UiInteger *i = (UiInteger*)var->value;
+        
+        if(i->value) {
+            button->setChecked(true);
+        }
+        
+        i->obj = button;
+        i->get = ui_togglebutton_get;
+        i->set = ui_togglebutton_set;
+    }
+    
+    ctn->add(button, false);
     
     return button;
 }
 
+int64_t ui_togglebutton_get(UiInteger *value) {
+    QPushButton *button = (QPushButton*)value->obj;
+    value->value = button->isChecked();
+    return value->value;
+}
+
+void ui_togglebutton_set(UiInteger *value, int64_t i) {
+    QPushButton *button = (QPushButton*)value->obj;
+    value->value = i;
+    if(i != 0) {
+        button->setChecked(true);
+    }
+}
+
+static void checkbox_event(UiEvent *event, UiEventWrapper *wrapper) {
+    QPushButton *button = (QPushButton*)wrapper->customdata1;
+    event->intval = button->isChecked();
+    if(wrapper->var) {
+        event->eventdata = wrapper->var->value;
+    }
+}
 
 
-// TODO: checkbox
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QString str = QString::fromUtf8(args.label);
+    QCheckBox *checkbox = new QCheckBox(str);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    
+    if(args.onchange) {
+        UiEventWrapper *event = new UiEventWrapper(obj, args.onchange, args.onchangedata);
+        event->var = var;
+        event->customdata1 = checkbox;
+        event->prepare_event = checkbox_event;
+        checkbox->connect(checkbox, SIGNAL(clicked()), event, SLOT(slot()));
+        checkbox->connect(checkbox, SIGNAL(destroyed()), event, SLOT(destroy()));
+    }
+    
+    if(var) {
+        UiInteger *i = (UiInteger*)var->value;
+        
+        if(i->value) {
+            checkbox->setChecked(true);
+        }
+        
+        i->obj = checkbox;
+        i->get = ui_checkbox_get;
+        i->set = ui_checkbox_set;
+    }
+    
+    ctn->add(checkbox, false);
+    
+    return checkbox;
+}
+
+int64_t ui_checkbox_get(UiInteger *value) {
+    QPushButton *button = (QPushButton*)value->obj;
+    value->value = button->isChecked();
+    return value->value;
+}
+
+void ui_checkbox_set(UiInteger *value, int64_t i) {
+    QPushButton *button = (QPushButton*)value->obj;
+    value->value = i;
+    if(i != 0) {
+        button->setChecked(true);
+    }
+}
 
 
-UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup)  {
-    QString str = QString::fromUtf8(label);
+static void radiobutton_event(UiEvent *event, UiEventWrapper *wrapper) {
+    if(wrapper->var) {
+        UiInteger *value = wrapper->var->value;
+        event->eventdata = value;
+        event->intval = ui_get(value);
+    }
+}
+
+UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QString str = QString::fromUtf8(args.label);
     QRadioButton *button = new QRadioButton(str);
     button->setAutoExclusive(false);
     
-    if(rgroup) {
-        QButtonGroup *buttonGroup = (QButtonGroup*)rgroup->obj;
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = (UiInteger*)var->value;
+        QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
         if(!buttonGroup) {
             buttonGroup = new QButtonGroup();
-            rgroup->obj = buttonGroup;
+            value->obj = buttonGroup;
+        }
+        int id = buttonGroup->buttons().size()+1;
+        buttonGroup->addButton(button, id);
+        if(value->value == id) {
             button->setChecked(true);
         }
-        buttonGroup->addButton(button, buttonGroup->buttons().size());
-        
-        rgroup->get = ui_radiobutton_get;
-        rgroup->set = ui_radiobutton_set;
+        value->get = ui_radiobutton_get;
+        value->set = ui_radiobutton_set;
     }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(button, false);
+    UiEventWrapper *event = new UiEventWrapper(obj, args.onchange, args.onchangedata);
+    event->var = var;
+    event->customdata1 = button;
+    event->prepare_event = togglebutton_event;
+    button->connect(button, SIGNAL(clicked()), event, SLOT(slot()));
+    button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy()));
+    
+    ctn->add(button, false);
     
     return button;
 }
 
-int ui_radiobutton_get(UiInteger *value) {
+int64_t ui_radiobutton_get(UiInteger *value) {
     QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
     value->value = buttonGroup->checkedId();
     return value->value;
 }
 
-void ui_radiobutton_set(UiInteger *value, int i) {
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
     QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
     QAbstractButton *button = buttonGroup->button(i);
     if(button) {
--- a/ui/qt/button.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/button.h	Sat Apr 05 16:46:11 2025 +0200
@@ -34,12 +34,19 @@
 #include <QPushButton>
 #include <QRadioButton>
 #include <QButtonGroup>
+#include <QCheckBox>
 
 extern "C" {
     
-int ui_radiobutton_get(UiInteger *value);
+   
+int64_t ui_togglebutton_get(UiInteger *value);
+void ui_togglebutton_set(UiInteger *value, int64_t i);
 
-void ui_radiobutton_set(UiInteger *value, int i);
+int64_t ui_checkbox_get(UiInteger *value);
+void ui_checkbox_set(UiInteger *value, int64_t i);
+    
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
 
 }
 
--- a/ui/qt/container.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/container.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -28,16 +28,31 @@
 
 #include <stdio.h>
 #include "container.h"
+#include "../common/object.h"
+
+#include <cx/mempool.h>
 
 #include <QSpacerItem>
 #include <QStackedWidget>
+#include <QLabel>
 
+static void delete_container(UiContainerPrivate *ct) {
+    delete ct;
+}
+
+void ui_container_add(UiObject *obj, UiContainerPrivate *ct) {
+    UiContainerX *container = (UiContainerX*)ui_malloc(obj->ctx, sizeof(UiContainerX));
+    container->close = 0;
+    container->container = ct;
+    container->prev = NULL;
+    container->next = NULL;
+    cxMempoolRegister(obj->ctx->mp, ct, (cx_destructor_func)delete_container);
+    uic_object_push_container(obj, container);
+}
 
 /* -------------------- UiBoxContainer -------------------- */
 
 UiBoxContainer::UiBoxContainer(QBoxLayout* box) {
-    this->current = NULL;
-    this->menu = NULL;
     this->box = box;
     box->setContentsMargins(QMargins(0,0,0,0));
     box->setSpacing(0);
@@ -71,36 +86,47 @@
     current = widget;
 }
 
-UIWIDGET ui_box(UiObject *obj, QBoxLayout::Direction dir) {
-    UiContainer *ct = uic_get_current_container(obj);
+UIWIDGET ui_box(UiObject *obj, UiContainerArgs args, QBoxLayout::Direction dir) {
+    UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
     QWidget *widget = new QWidget();
     QBoxLayout *box = new QBoxLayout(dir);
     widget->setLayout(box);
-    ct->add(widget, true);
+    ctn->add(widget, true);
     
-    UiObject *newobj = uic_object_new(obj, widget);
-    newobj->container = new UiBoxContainer(box);
-    uic_obj_add(obj, newobj);
+    ui_container_add(obj, new UiBoxContainer(box));
     
     return widget;
 }
 
-UIWIDGET ui_vbox(UiObject *obj) {
-    return ui_box(obj, QBoxLayout::TopToBottom);
+UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box(obj, args, QBoxLayout::TopToBottom);
 }
 
-UIWIDGET ui_hbox(UiObject *obj) {
-    return ui_box(obj, QBoxLayout::LeftToRight);
+UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box(obj, args, QBoxLayout::LeftToRight);
 }
 
 
-
 /* -------------------- UiGridContainer -------------------- */
 
-UiGridContainer::UiGridContainer(QGridLayout* grid, int margin, int columnspacing, int rowspacing) {
+UiGridContainer::UiGridContainer(
+        QGridLayout *grid,
+        int margin,
+        int columnspacing,
+        int rowspacing,
+        bool def_hexpand,
+        bool def_vexpand,
+        bool def_hfill,
+        bool def_vfill)
+{
     this->current = NULL;
-    this->menu = NULL;
     this->grid = grid;
+    this->def_hexpand = def_hexpand;
+    this->def_vexpand = def_vexpand;
+    this->def_hfill = def_hfill;
+    this->def_vfill = def_vfill;
     grid->setContentsMargins(QMargins(margin, margin, margin, margin));
     grid->setHorizontalSpacing(columnspacing);
     grid->setVerticalSpacing(rowspacing);
@@ -113,144 +139,149 @@
         y++;
     }
     
-    Qt::Alignment alignment = Qt::AlignTop;
-    grid->setColumnStretch(x, layout.hexpand ? 1 : 0);
-    if(layout.vexpand) {
-        grid->setRowStretch(y, 1);
-        alignment = 0;
-    } else {
-        grid->setRowStretch(y, 0);
+    int hexpand = false;
+    int vexpand = false;
+    int hfill = false;
+    int vfill = false;
+    if(!layout.override_defaults) {
+        if(def_hexpand) {
+            hexpand = true;
+            hfill = true;
+        } else if(def_hfill) {
+            hfill = true;
+        }
+        if(def_vexpand) {
+            vexpand = true;
+            vfill = true;
+        } else if(def_vfill) {
+            vfill = true;
+        }
     }
     
-    int gwidth = layout.gridwidth > 0 ? layout.gridwidth : 1;
+    if(layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(layout.fill);
+    }
+    if(layout.hexpand) {
+        hexpand = true;
+        //hfill = true;
+    } else if(layout.hfill) {
+        hfill = true;
+    }
+    if(layout.vexpand) {
+        vexpand = true;
+        //vfill = true;
+    } else if(layout.vfill) {
+        vfill = true;
+    }
+    if(fill) {
+        hfill = true;
+        vfill = true;
+    }
+    
+    if(hexpand) {
+        col_expanding = true;
+    }
+    if(vexpand) {
+        row_expanding = true;
+    }
     
-    grid->addWidget(widget, y, x, 1, gwidth, alignment);
-    x += gwidth;
+    if(hexpand) {
+        grid->setColumnStretch(x, 1);
+    }
+    if(vexpand) {
+        grid->setRowStretch(y, 1);
+    }
+    
+    Qt::Alignment alignment = 0;
+    if(!hfill) {
+        alignment = Qt::AlignLeft;
+    }
+    if(!vfill) {
+        alignment = Qt::AlignTop;
+    }
+    
+    int colspan = layout.colspan > 0 ? layout.colspan : 1;
+    int rowspan = layout.rowspan > 0 ? layout.rowspan : 1;
+    
+    grid->addWidget(widget, y, x, rowspan, colspan, alignment);
+    
+    if(x > max_x) {
+        max_x = x;
+    }
+    if(y > max_y) {
+        max_y = y;
+    }
+    
+    x += colspan;
     
     ui_reset_layout(layout);
     current = widget;
 }
 
-UIWIDGET ui_grid(UiObject *obj) {
-    return ui_grid_sp(obj, 0, 0, 0);
+void UiGridContainer::end() {
+    if(!col_expanding) {
+        QSpacerItem *filler = new QSpacerItem(0, 0);
+        x = max_x + 1;
+        grid->setColumnStretch(x, 1);
+        grid->addItem(filler, 0, x, 1, 1, 0);
+    }
+    if(!row_expanding) {
+        QSpacerItem *filler = new QSpacerItem(0, 0);
+        y++;
+        grid->setRowStretch(y, 1);
+        grid->addItem(filler, y, 0, 1, 1, 0);
+    }
 }
 
-UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
-    UiContainer *ct = uic_get_current_container(obj);
+UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
     QWidget *widget = new QWidget();
     QGridLayout *grid = new QGridLayout();
     widget->setLayout(grid);
-    ct->add(widget, true);
+    ctn->add(widget, true);
     
-    UiObject *newobj = uic_object_new(obj, widget);
-    newobj->container = new UiGridContainer(grid, margin, columnspacing, rowspacing);
-    uic_obj_add(obj, newobj);
+    ui_container_add(obj, new UiGridContainer(
+            grid,
+            args.margin,
+            args.columnspacing,
+            args.rowspacing,
+            args.def_hexpand,
+            args.def_vexpand,
+            args.def_hfill,
+            args.def_vfill));
     
     return widget;
 }
 
 
-/* -------------------- UiTabViewContainer -------------------- */
 
-UiTabViewContainer::UiTabViewContainer(QTabWidget* tabwidget) {
-    this->current = NULL;
-    this->menu = NULL;
-    this->tabwidget = tabwidget;
-}
+/* -------------------- Container Helper Functions -------------------- */
 
-void UiTabViewContainer::add(QWidget* widget, bool fill) {
-    QString str = QString::fromUtf8(layout.label);
-    tabwidget->addTab(widget, str);
-}
-
-
-/* -------------------- UiStackContainer -------------------- */
-
-UiStackContainer::UiStackContainer(QStackedWidget *stack) {
-    this->stack = stack;
+void ui_container_begin_close(UiObject *obj) {
+    obj->container_end->close = true;
 }
 
-void UiStackContainer::add(QWidget* widget, bool fill) {
-    stack->addWidget(widget);
-    current = widget;
-}
-
-UIWIDGET ui_tabview(UiObject *obj) {
-    QStackedWidget *tabwidget = new QStackedWidget();
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(tabwidget, true);
-    
-    UiObject *tabviewobj = uic_object_new(obj, tabwidget);
-    tabviewobj->container = new UiStackContainer(tabwidget);
-    uic_obj_add(obj, tabviewobj);
-    
-    return tabwidget;
-}
-
-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) {
-    QStackedWidget *w = (QStackedWidget*)tabview;
-    w->setCurrentIndex(tab);
+int ui_container_finish(UiObject *obj) {
+    if(obj->container_end->close) {
+        UiContainerPrivate *ctn = (UiContainerPrivate*)obj->container_end->container;
+        ctn->end();
+        ui_end_new(obj);
+        return 0;
+    }
+    return 1;
 }
 
 
-/* -------------------- UiSidebarContainer -------------------- */
-
-UiSidebarContainer::UiSidebarContainer(QSplitter *splitter) {
-    this->splitter = splitter;
-}
-
-UIWIDGET ui_sidebar(UiObject *obj) {
-    QSplitter *splitter = new QSplitter(Qt::Horizontal);
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(splitter, true);
-    
-    UiObject *left = uic_object_new(obj, splitter);
-    left->container = new UiSidebarContainer(splitter);
-    
-    UiObject *right = uic_object_new(obj, splitter);
-    right->container = new UiSidebarContainer(splitter);
-    
-    uic_obj_add(obj, right);
-    uic_obj_add(obj, left);
-    
-    return splitter;
-}
-
-void UiSidebarContainer::add(QWidget *widget, bool fill) {
-    splitter->addWidget(widget);
-}
-
-
-/* -------------------- layout functions -------------------- */
-
-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;
-}
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
 
 void ui_newline(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
+    UiContainerPrivate *ct = ui_obj_container(obj);
     ct->layout.newline = TRUE;
 }
--- a/ui/qt/container.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/container.h	Sat Apr 05 16:46:11 2025 +0200
@@ -30,6 +30,7 @@
 #define	CONTAINER_H
 
 #include "toolkit.h"
+#include "../ui/container.h"
 #include "window.h"
 
 #include <string.h>
@@ -39,37 +40,52 @@
 #include <QStackedWidget>
 #include <QSplitter>
 
+#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.override_defaults = args.override_defaults; \
+    layout.colspan = args.colspan; \
+    layout.rowspan = args.rowspan
+
+#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)
-#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
 
-typedef struct UiLayout UiLayout;
+#define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container
 
-enum UiLayoutBool {
+typedef enum UiLayoutBool {
     UI_LAYOUT_UNDEFINED = 0,
     UI_LAYOUT_TRUE,
     UI_LAYOUT_FALSE,
-};
-typedef enum UiLayoutBool UiLayoutBool;
+} UiLayoutBool;
 
+typedef struct UiLayout UiLayout;
 struct UiLayout {
-    UiLayoutBool fill;
-    bool newline;
-    char *label;
-    bool hexpand;
-    bool vexpand;
-    int  gridwidth;
+    UiTri        fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    UiBool       hfill;
+    UiBool       vfill;
+    UiBool       override_defaults;
+    int          width;
+    int          colspan;
+    int          rowspan;
 };
 
-struct UiContainer {
+struct UiContainerPrivate {
     UiLayout layout; 
     UIWIDGET current;
-    QMenu    *menu;
 
     virtual void add(QWidget *widget, bool fill) = 0;
+    virtual void end() {}
 };
 
-class UiBoxContainer : public UiContainer {
+class UiBoxContainer : public UiContainerPrivate {
 public:
     QBoxLayout  *box;
     bool        hasStretchedWidget = false;
@@ -80,40 +96,36 @@
     virtual void add(QWidget *widget, bool fill);
 };
 
-class UiGridContainer : public UiContainer {
+class UiGridContainer : public UiContainerPrivate {
 public:
     QGridLayout *grid;
     int x = 0;
     int y = 0;
+    bool def_hexpand;
+    bool def_vexpand;
+    bool def_hfill;
+    bool def_vfill;
+    bool col_expanding = false;
+    bool row_expanding = false;
+    int max_x;
+    int max_y;
     
-    UiGridContainer(QGridLayout *grid, int margin, int columnspacing, int rowspacing);
+    UiGridContainer(
+            QGridLayout *grid,
+            int margin,
+            int columnspacing,
+            int rowspacing,
+            bool def_hexpand,
+            bool def_vexpand,
+            bool def_hfill,
+            bool def_vfill);
     
     virtual void add(QWidget *widget, bool fill);
-};
-
-class UiTabViewContainer : public UiContainer {
-public:
-    QTabWidget *tabwidget;
-    
-    UiTabViewContainer(QTabWidget *tabwidget);
-    virtual void add(QWidget *widget, bool fill);
+    virtual void end();
 };
 
-class UiStackContainer : public UiContainer {
-public:
-    QStackedWidget *stack;
-    
-    UiStackContainer(QStackedWidget *stack);
-    virtual void add(QWidget *widget, bool fill);
-};
+void ui_container_add(UiObject *obj, UiContainerPrivate *ct);
 
-class UiSidebarContainer : public UiContainer {
-public:
-    QSplitter *splitter;
-    
-    UiSidebarContainer(QSplitter *splitter);
-    virtual void add(QWidget *widget, bool fill);
-};
 
 #endif	/* CONTAINER_H */
 
--- a/ui/qt/graphics.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/graphics.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -30,122 +30,4 @@
 #include "container.h"
 
 
-DrawingArea::DrawingArea(UiObject *obj, ui_drawfunc cb, void *data) {
-    object = obj;
-    drawCallback = cb;
-    userdata = data;
-}
 
-DrawingArea::~DrawingArea() {
-    
-}
-
-void DrawingArea::paintEvent(QPaintEvent *event) {
-    QPainter painter(this);
-    
-    UiQtGraphics g;
-    g.g.width = this->width();
-    g.g.height = this->height();
-    g.painter = &painter;
-    
-    UiEvent ev;
-    ev.obj = object;
-    ev.window = object->window;
-    ev.document = object->ctx->document;
-    ev.eventdata = NULL;
-    ev.intval = 0;
-    
-    drawCallback(&ev, &g.g, userdata);
-}
-
-
-
-UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
-    DrawingArea *widget = new DrawingArea(obj, f, userdata);
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(widget, true);
-    
-    return widget;
-}
-
-void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
-    
-}
-
-void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
-    
-}
-
-void ui_drawingarea_redraw(UIWIDGET drawingarea) {
-    
-}
-
-
-/* -------------------- text layout functions -------------------- */
-
-UiTextLayout* ui_text(UiGraphics *g) {
-    UiTextLayout *textlayout = new UiTextLayout();
-    return textlayout;
-}
-
-void ui_text_free(UiTextLayout *text) {
-    delete text;
-}
-
-void ui_text_setstring(UiTextLayout *layout, char *str) {
-    layout->text.setText(QString::fromUtf8(str));
-}
-
-void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
-    layout->text.setText(QString::fromUtf8(str, len));
-}
-
-void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
-    layout->font = QFont(QString::fromUtf8(font), size);
-}
-
-void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
-    QSizeF size = layout->text.size();
-    *width = (int)size.width();
-    *height = (int)size.height();
-}
-
-void ui_text_setwidth(UiTextLayout *layout, int width) {
-    layout->text.setTextWidth((qreal)width);
-}
-
-
-/* -------------------- drawing functions -------------------- */
-
-void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
-    UiQtGraphics *gr = (UiQtGraphics*)g;
-    gr->color = QColor(red, green, blue);
-    gr->painter->setPen(gr->color);
-}
-
-void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
-    UiQtGraphics *gr = (UiQtGraphics*)g;
-    
-    gr->painter->drawLine(x1, y1, x2, y2);
-}
-
-void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
-    UiQtGraphics *gr = (UiQtGraphics*)g;
-    
-    QRect rect(x, y, w, h);
-    if(fill) {
-        gr->painter->fillRect(rect, gr->color);
-        
-    } else {
-        gr->painter->drawRect(rect);
-    }
-}
-
-void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
-    UiQtGraphics *gr = (UiQtGraphics*)g;
-    
-    gr->painter->setFont(text->font);
-    gr->painter->drawStaticText(x, y, text->text);
-}
-
--- a/ui/qt/graphics.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/graphics.h	Sat Apr 05 16:46:11 2025 +0200
@@ -37,31 +37,6 @@
 #include <QColor>
 #include <QStaticText>
 
-typedef struct UiQtGraphics {
-    UiGraphics g;
-    QPainter   *painter;
-    QColor     color;
-} UiXlibGraphics;
-
-struct UiTextLayout {
-    QStaticText text;
-    QFont font;
-};
-
-
-class DrawingArea : public QWidget {
-    Q_OBJECT
-    
-    UiObject    *object;
-    ui_drawfunc drawCallback;
-    void        *userdata;
-    
-public:
-    DrawingArea(UiObject *obj, ui_drawfunc cb, void *data);
-    ~DrawingArea();
-    
-    virtual void paintEvent(QPaintEvent * event);
-};
 
 #endif /* GRAPHICS_H */
 
--- a/ui/qt/label.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/label.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2015 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -29,23 +29,35 @@
 #include "label.h"
 #include "container.h"
 #include "toolkit.h"
+#include "ui/display.h"
 
-UIWIDGET ui_label(UiObject *obj, char *label) {
-    QString str = QString::fromUtf8(label);
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QString str = QString::fromUtf8(args.label);
     QLabel *widget = new QLabel(str);
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(widget, false);
+    Qt::AlignmentFlag align = Qt::AlignCenter;
+    if(args.align == UI_ALIGN_LEFT) {
+        align = Qt::AlignLeft;
+    } else if(args.align == UI_ALIGN_RIGHT) {
+        align = Qt::AlignRight;
+    }
+    widget->setAlignment(align);
+    
+    ctn->add(widget, false);
     
     return widget;
 }
 
-UIWIDGET ui_space(UiObject *obj) {
-    // TODO: maybe there is a better widget for this purpose
-    QLabel *widget = new QLabel();
-    
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(widget, true);
-    
-    return widget;
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_LEFT;
+    return ui_label_create(obj, args);
 }
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+    args.align = UI_ALIGN_RIGHT;
+    return ui_label_create(obj, args);
+}
--- a/ui/qt/menu.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/menu.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -35,391 +35,203 @@
 #include "menu.h"
 #include "toolkit.h"
 #include "../common/context.h"
+#include "../common/menu.h"
 #include "../ui/properties.h"
 #include "../ui/window.h"
 #include "stock.h"
 #include "container.h"
 
-static UcxList *menus;
-static UcxList *current;
-
-/* -------------------------- UiMenu -------------------------- */
-
-UiMenu::UiMenu(char* label) {
-    this->items = NULL;
-    this->label = label;
-}
 
-void UiMenu::addMenuItem(UiMenuItemI* item) {
-    items = ucx_list_append(items, item);
-}
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
 
-void UiMenu::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
-    QMenu *m = NULL;
-    if(menubar) {
-        m = menubar->addMenu(label);
-    } else {
-        m = menu->addMenu(label);
-    }
-    
-    UCX_FOREACH(elm, items) {
-        UiMenuItemI *item = (UiMenuItemI*)elm->data;
-        item->addTo(obj, NULL, m);
+/*
+ * create all menu child items
+ */
+static void add_menu_items(QMenu *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it, obj);
+        it = it->next;
+        index++;
     }
 }
 
-
-/* -------------------------- UiMenuItem -------------------------- */
-
-UiMenuItem::UiMenuItem(char* label, ui_callback f, void* userdata) {
-    this->label = label;
-    this->callback = f;
-    this->userdata = userdata;
-    this->groups = NULL;
+void add_menu_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *m = (UiMenu*)item;
+    QMenu *menu = parent->addMenu(m->label);
+    add_menu_items(menu, i, m, obj);
 }
 
-void UiMenuItem::addGroup(int group) {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-void UiMenuItem::setCheckable(bool c) {
-    checkable = c;
-}
-
-void UiMenuItem::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
+static UiAction* create_action(
+        UiObject *obj,
+        const char *icon,
+        const char *label,
+        ui_callback callback,
+        void *userdata,
+        int *states)
+{
     QString str = QString::fromUtf8(label);
     UiAction *action = new UiAction(obj, str, callback, userdata);
-    action->setCheckable(checkable);
-    if(checkable) {
-        action->setChecked(false);
+    if(icon) {
+        action->setIcon(QIcon::fromTheme(icon));
+        action->setIconVisibleInMenu(true);
     }
-    menu->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+    
+    if(states) {
+        size_t nstates = uic_group_array_size(states);
+        uic_add_group_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates);
+        action->setEnabled(false);
+    }
+    
+    return action;
 }
 
-
-/* -------------------------- UiStMenuItem -------------------------- */
-
-UiStMenuItem::UiStMenuItem(char* stockid, ui_callback f, void* userdata) {
-    this->stockid = stockid;
-    this->callback = f;
-    this->userdata = userdata;
-    this->groups = NULL;
-}
-
-void UiStMenuItem::addGroup(int group) {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-void UiStMenuItem::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
-    UiStockItem *stockItem = ui_get_stock_item(stockid);
-    
-    QString str = QString::fromUtf8(stockItem->label);
-    UiAction *action = new UiAction(obj, str, callback, userdata);
-    action->setIcon(QIcon::fromTheme(stockItem->icon_name));
-    action->setIconVisibleInMenu(true);
-    menu->addAction(action);
-    //UiEventWrapper *ev = new UiEventWrapper(callback, userdata);
+void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *it = (UiMenuItem*)item;
+    UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups);
+    parent->addAction(action);
     QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
 }
 
-
-/* -------------------------- UiMenuSeparator -------------------------- */
-
-void UiMenuSeparator::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
-    menu->addSeparator();
+void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    parent->addSeparator();
 }
 
-
-/* -------------------------- UiCheckItemNV -------------------------- */
-
-UiCheckItemNV::UiCheckItemNV(char* label, char* varname) {
-    this->label = label;
-    this->varname = varname;
+void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenuCheckItem *it = (UiMenuCheckItem*)item;
+    
+    UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups);
+    parent->addAction(action);
+    action->setCheckable(true);
+    action->prepare_event = ui_checkableaction_prepare_event;
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = (UiInteger*)var->value;
+        value->obj = action;
+        value->get = ui_checkableaction_get;
+        value->set = ui_checkableaction_set;
+        
+        action->setChecked((bool)value->value);
+    }
+    action->var = var;
 }
 
-void UiCheckItemNV::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
-    QString str = QString::fromUtf8(label);
-    UiAction *action = new UiAction(obj, str, NULL, NULL);
+void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuRadioItem *it = (UiMenuRadioItem*)item;
+    
+    UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups);
+    parent->addAction(action);
     action->setCheckable(true);
-    menu->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+    action->prepare_event = ui_actiongroup_prepare_event;
     
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_INTEGER);
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER);
     if(var) {
         UiInteger *value = (UiInteger*)var->value;
-        action->setChecked(value->value);
-        value->obj = action;
-        value->get = ui_checkitem_get;
-        value->set = ui_checkitem_set;
-        value = 0;
-    } else {
-        // TODO: error
+        QActionGroup *group = (QActionGroup*)value->obj;
+        if(!group) {
+            group = new QActionGroup(parent);
+            value->obj = group;
+        }
+        group->addAction(action);
+        value->get = ui_actiongroup_get;
+        value->set = ui_actiongroup_set;
+        if(value->value != 0) {
+            ui_actiongroup_set(value, value->value);
+        }
+    }
+    action->var;
+}
+
+void ui_actiongroup_prepare_event(UiEvent *event, UiAction *action) {
+    if(action->var) {
+        UiInteger *value = (UiInteger*)action->var->value;
+        event->eventdata = value;
+        event->intval = value->get(value);
     }
 }
 
-
-/* -------------------------- UiAction -------------------------- */
-
-UiAction::UiAction(UiObject *obj, QString &label, ui_callback f, void* userdata) : QAction(label, NULL) {
-    //QAction(label, NULL);
-    this->obj = obj;
-    this->callback = f;
-    this->userdata = userdata;
-}
-
-void UiAction::trigger() {
-    if(!callback) {
-        return;
+int64_t ui_actiongroup_get(UiInteger *value) {
+    QActionGroup *group = (QActionGroup*)value->obj;
+    auto actions = group->actions();
+    int i = 1;
+    foreach(const QAction *action, actions) {
+        if(action->isChecked()) {
+            value->value = i;
+            break;
+        }
+        i++;
     }
-    
-    UiEvent e;
-    e.obj = obj;
-    e.window = obj->window;
-    e.document = obj->ctx->document;
-    e.eventdata = NULL;
-    
-    if(isCheckable()) {
-        e.intval = isChecked();
-    } else {
-        e.intval = 0;
-    }
-    
-    callback(&e, userdata);
+    return value->value;
 }
 
-
-void ui_menu(char *label) {
-    // free current menu hierarchy
-    ucx_list_free(current);
-    
-    // create menu
-    UiMenu *menu = new UiMenu(label);
-    
-    current = ucx_list_prepend(NULL, menu);
-    menus = ucx_list_append(menus, menu);
+void ui_actiongroup_set(UiInteger *value, int64_t i) {
+    QActionGroup *group = (QActionGroup*)value->obj;
+    auto actions = group->actions();
+    if(i > 0) {
+        if(i-1 < actions.size()) {
+            actions[i]->setEnabled(true);
+        }
+        value->value = i;
+    } else {
+        foreach(QAction *action, actions) {
+            action->setEnabled(false);
+        }
+        value->value = 0;
+    }
 }
 
-void ui_submenu(char *label) {
-    UiMenu *menu = new UiMenu(label);
-    
-    // add submenu to current menu
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(menu);
+void add_menuitem_list_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) {
     
-    // set the submenu to current menu
-    current = ucx_list_prepend(current, menu);
-}
-
-void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
-        return;
-    }
-    current = ucx_list_remove(current, current);
-    //UcxList *c = current;
 }
 
 
-void ui_menuitem(char *label, ui_callback f, void *userdata) {
-    ui_menuitem_gr(label, f, userdata, -1);
-}
-
-void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) {
-    ui_menuitem_stgr(stockid, f, userdata, -1);
-}
-
-void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItem *item = new UiMenuItem(label, f, userdata);
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->addGroup(group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(item);
-}
-
-void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
-    if(!current) {
+void ui_add_menus(UiObject *obj, QMainWindow *window) {
+    UiMenu *menus_begin = uic_get_menu_list();
+    if(menus_begin == NULL) {
         return;
     }
     
-    UiStMenuItem *item = new UiStMenuItem(stockid, f, userdata);
-    
-    // add groups
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->addGroup(group);
-    }
-    va_end(ap);
-    
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(item);
-}
-
-void ui_menuseparator() {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuSeparator *item = new UiMenuSeparator();
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(item);
-}
-
-void ui_checkitem(char *label, ui_callback f, void *userdata) {
-    if(!current) {
-        return;
-    }
-    
-    UiMenuItem *item = new UiMenuItem(label, f, userdata);
-    item->setCheckable(true);
-    
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(item);
-}
-
-void ui_checkitem_nv(char *label, char *vname) {
-    if(!current) {
-        return;
-    }
-    
-    UiCheckItemNV *item = new UiCheckItemNV(label, vname);
-    
-    UiMenu *cm = (UiMenu*)current->data;
-    cm->addMenuItem(item);
-}
-
-void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
-    
-}
-
-void ui_add_menus(UiObject *obj, QMainWindow *window) {
     QMenuBar *mb = window->menuBar();
     
-    UCX_FOREACH(elm, menus) {
-        UiMenu *menu = (UiMenu*)elm->data;
-        menu->addTo(obj, mb, NULL);        
+    UiMenu *ls = menus_begin;
+    while(ls) {
+        if(ls->item.type == UI_MENU) {
+            QMenu *menu = mb->addMenu(ls->label);
+            add_menu_items(menu, 0, ls, obj);
+        }        
+        ls = (UiMenu*)ls->item.next;
     }
 }
 
-int ui_checkitem_get(UiInteger *i) {
-    QAction *action = (QAction*)i->obj;
-    i->value = action->isChecked();
-    return i->value;
-}
-
-void ui_checkitem_set(UiInteger *i, int value) {
-    QAction *action = (QAction*)i->obj;
-    i->value = value;
-    action->setChecked(value);
-}
-
-
-/*
- * widget menu functions
- */
-
-UiContextMenuHandler::UiContextMenuHandler(QWidget *widget, QMenu* menu) {
-    this->widget = widget;
-    this->menu = menu;
-}
-
-void UiContextMenuHandler::contextMenuEvent(const QPoint & pos) {
-    menu->popup(widget->mapToGlobal(pos));
-}
-UIMENU ui_contextmenu(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    return ui_contextmenu_w(obj, ct->current);
-}
-
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    QMenu *menu = new QMenu(widget);
-    widget->setContextMenuPolicy(Qt::CustomContextMenu);
-    
-    UiContextMenuHandler *handler = new UiContextMenuHandler(widget, menu);
-    QObject::connect(
-            widget,
-            SIGNAL(customContextMenuRequested(QPoint)),
-            handler,
-            SLOT(contextMenuEvent(QPoint)));
-    
-    ct->menu = menu;
-    
-    return menu;
-}
-
-void ui_contextmenu_popup(UIMENU menu) {
-    
+void ui_checkableaction_prepare_event(UiEvent *event, UiAction *action) {
+    if(action->var) {
+        event->eventdata = action->var->value;
+    }
+    event->intval = action->isChecked();
 }
 
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
-    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
-}
-
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    QString str = QString::fromUtf8(label);
-    UiAction *action = new UiAction(obj, str, f, userdata);
-    ct->menu->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+int64_t ui_checkableaction_get(UiInteger *value) {
+    UiAction *action= (UiAction*)value->obj;
+    value->value = action->isChecked();
+    return value->value;
 }
 
-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_checkableaction_set(UiInteger *value, int64_t i) {
+    UiAction *action = (UiAction*)value->obj;
+    value->value = i;
+    if(i != 0) {
+        action->setChecked((bool)i);
+    }
 }
 
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    UcxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    UiStockItem *stockItem = ui_get_stock_item(stockid);
-    
-    QString str = QString::fromUtf8(stockItem->label);
-    UiAction *action = new UiAction(obj, str, f, userdata);
-    action->setIcon(QIcon::fromTheme(stockItem->icon_name));
-    action->setIconVisibleInMenu(true);
-    ct->menu->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
-}
--- a/ui/qt/menu.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/menu.h	Sat Apr 05 16:46:11 2025 +0200
@@ -30,105 +30,34 @@
 #define	MENU_H
 
 #include "../ui/menu.h"
-#include <ucx/list.h>
+#include "../common/menu.h"
+
+#include "toolkit.h"
 
 #include <QMainWindow>
 #include <QMenu>
 #include <QMenuBar>
 #include <QContextMenuEvent>
 
-class UiMenuItemI {
-public:
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) = 0;
-};
-
-class UiMenu : public UiMenuItemI {
-public:
-    
-    UcxList *items;
-    char *label;
-    
-    UiMenu(char *label);
-    
-    void addMenuItem(UiMenuItemI *item);
-    
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
-};
-
-class UiMenuItem : public UiMenuItemI {
-    char *label;
-    ui_callback callback;
-    void *userdata;
-    UcxList *groups;
-    bool checkable = false;
-    
-public:
-    UiMenuItem(char *label, ui_callback f, void *userdata);
-    void addGroup(int group);
-    void setCheckable(bool c);
-    
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
-};
-
-class UiStMenuItem : public UiMenuItemI {
-    char *stockid;
-    ui_callback callback;
-    void *userdata;
-    UcxList *groups;
-    
-public:
-    UiStMenuItem(char *stockid, ui_callback f, void *userdata);
-    void addGroup(int group);
-    
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
-};
-
-class UiMenuSeparator : public UiMenuItemI {
-public:
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
-};
-
-class UiCheckItemNV : public UiMenuItemI {
-    char *label;
-    char *varname;
-    
-public:
-    UiCheckItemNV(char *label, char *varname);
-    virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
-};
-
-
-class UiAction : public QAction {
-    Q_OBJECT
-    
-    UiObject *obj;
-    ui_callback callback;
-    void *userdata;
-    
-public:
-    UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata);
-
-private slots:
-    void trigger();
-};
-
 void ui_add_menus(UiObject *obj, QMainWindow *window);
 
-extern "C" int ui_checkitem_get(UiInteger *i);
-extern "C" void ui_checkitem_set(UiInteger *i, int value);
+typedef void(*ui_menu_add_f)(QMenu*, int, UiMenuItemI*, UiObject*);
 
-class UiContextMenuHandler : public QObject {
-    Q_OBJECT
-    
-    QWidget *widget;
-    QMenu *menu;
-    
-public:
-    UiContextMenuHandler(QWidget *widget, QMenu *menu);
-    
-public slots:
-    void contextMenuEvent(const QPoint & pos);
-};
+void add_menu_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void add_checkitemnv_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_list_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_checkableaction_prepare_event(UiEvent *event, UiAction *action);
+int64_t ui_checkableaction_get(UiInteger *value);
+void ui_checkableaction_set(UiInteger *value, int64_t i);
+
+void ui_actiongroup_prepare_event(UiEvent *event, UiAction *action);
+int64_t ui_actiongroup_get(UiInteger *value);
+void ui_actiongroup_set(UiInteger *value, int64_t i);
 
 #endif	/* MENU_H */
 
--- a/ui/qt/model.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/model.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -28,143 +28,3 @@
 
 #include "model.h"
 
-UiListSelection* listSelection(QItemSelectionModel *s) {
-    UiListSelection *selection = new UiListSelection();
-    
-    QModelIndexList list = s->selectedRows();
-    selection->count = list.count();
-    if(selection->count > 0) {
-        selection->rows = new int[selection->count];
-    }
-    
-    QModelIndex index;
-    int i=0;
-    foreach(index, list) {
-        selection->rows[i] = index.row();
-        i++;
-    }
-    return selection;
-}
-
-ListModel::ListModel(UiObject* obj, QListView* view, UiListPtr* list, ui_model_getvalue_f getvalue, ui_callback f, void* userdata) {
-    this->obj = obj;
-    this->view = view;
-    this->list = list;
-    this->getvalue = getvalue;
-    this->callback = f;
-    this->userdata = userdata;
-}
-
-int ListModel::rowCount(const QModelIndex& parent) const {
-    return list->list->count(list->list);
-}
-
-QVariant ListModel::data(const QModelIndex &index, int role) const {
-    if(role == Qt::DisplayRole) {
-        UiList *ls = list->list;
-        void *rowData = ls->get(ls, index.row());
-        if(rowData && getvalue) {
-            void *value = getvalue(rowData, 0);
-            return QString::fromUtf8((char*)value); 
-        }
-    }
-    return QVariant();
-}
-
-void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
-    UiListSelection *selection = listSelection(view->selectionModel());
-    
-    UiEvent e;
-    e.obj = obj;
-    e.window = obj->window;
-    e.document = obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    callback(&e, userdata);
-    
-    if(selection->count > 0) {
-        delete selection->rows;
-    }
-    delete selection;
-}
-
-TableModel::TableModel(UiObject *obj, QTreeView *view, UiListPtr *list, UiModelInfo *info) {
-    this->obj = obj;
-    this->list = list;
-    this->info = info;
-    this->view = view;
-}
-
-int TableModel::rowCount(const QModelIndex &parent) const {
-    return list->list->count(list->list);
-}
-
-int TableModel::columnCount(const QModelIndex &parent) const {
-    return info->columns;
-}
-
-QVariant TableModel::data(const QModelIndex &index, int role) const {
-    if(role == Qt::DisplayRole) {
-        UiList *ls = list->list;
-        void *rowData = ls->get(ls, index.row());
-        if(rowData && info->getvalue) {
-            void *value = info->getvalue(rowData, index.column());
-            switch(info->types[index.column()]) {
-                case UI_STRING: {
-                    return QString::fromUtf8((char*)value); 
-                }
-                case UI_INTEGER: {
-                    int *intptr = (int*)value;
-                    return QVariant(*intptr);
-                }
-            }
-        }
-    }
-    return QVariant();
-}
-
-QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
-    if(role == Qt::DisplayRole) {
-        char *label = info->titles[section];
-        return QString::fromUtf8(label);
-    }
-    return QVariant();
-}
-
-void TableModel::update() {
-    emit dataChanged(QModelIndex(),QModelIndex());
-}
-
-void TableModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
-    UiListSelection *selection = listSelection(view->selectionModel());
-    
-    UiEvent e;
-    e.obj = obj;
-    e.window = obj->window;
-    e.document = obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    info->selection(&e, info->userdata);
-    
-    if(selection->count > 0) {
-        delete selection->rows;
-    }
-    delete selection;
-}
-
-void TableModel::activate(const QModelIndex &) {
-    UiListSelection *selection = listSelection(view->selectionModel());
-    
-    UiEvent e;
-    e.obj = obj;
-    e.window = obj->window;
-    e.document = obj->ctx->document;
-    e.eventdata = selection;
-    e.intval = selection->count > 0 ? selection->rows[0] : -1;
-    info->activate(&e, info->userdata);
-    
-    if(selection->count > 0) {
-        delete selection->rows;
-    }
-    delete selection;
-}
--- a/ui/qt/model.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/model.h	Sat Apr 05 16:46:11 2025 +0200
@@ -39,53 +39,6 @@
 #include <QAbstractItemModel>
 #include <QItemSelectionModel>
 
-class ListModel : public QAbstractListModel {
-    Q_OBJECT
-    
-    UiObject    *obj;
-    UiListPtr   *list;
-    ui_model_getvalue_f getvalue;
-    ui_callback callback;
-    void        *userdata;
-    QListView   *view;
-    
-public:
-    ListModel(UiObject *obj, QListView *view, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *userdata);
-    
-    int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
-    
-public slots:
-    void selectionChanged(
-        const QItemSelection & selected,
-        const QItemSelection & deselected);
-};
-
-class TableModel : public QAbstractTableModel {
-    Q_OBJECT
-    
-    UiObject    *obj;
-    UiListPtr   *list;
-    UiModelInfo *info;
-    QTreeView   *view;
-public:
-    TableModel(UiObject *obj, QTreeView *view, UiListPtr *list, UiModelInfo *info);
-    
-    int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    int columnCount(const QModelIndex &parent = QModelIndex()) const;
-    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
-    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
-    
-    void update();
-    
-public slots:
-    void selectionChanged(
-        const QItemSelection & selected,
-        const QItemSelection & deselected);
-    void activate(const QModelIndex &);
-};
-
-UiListSelection* listSelection(QItemSelectionModel *s);
 
 #endif	/* MODEL_H */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/qt5.pro	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,67 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2025 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+TARGET = uitk
+TEMPLATE = lib
+CONFIG += staticlib warn_off debug
+DESTDIR = ../build/lib
+MOC_DIR = ../build/ui/qt
+OBJECTS_DIR = ../build/ui/qt
+
+QT += core gui widgets
+
+DEFINES += UI_QT5
+
+SOURCES += toolkit.cpp
+SOURCES += window.cpp
+SOURCES += menu.cpp
+SOURCES += toolbar.cpp
+SOURCES += stock.cpp
+SOURCES += container.cpp
+SOURCES += text.cpp
+SOURCES += model.cpp
+SOURCES += tree.cpp
+SOURCES += button.cpp
+SOURCES += label.cpp
+SOURCES += graphics.cpp
+SOURCES += widget.cpp
+
+HEADERS += toolkit.h
+HEADERS += window.h
+HEADERS += menu.h
+HEADERS += toolbar.h
+HEADERS += stock.h
+HEADERS += container.h
+HEADERS += text.h
+HEADERS += model.h
+HEADERS += tree.h
+HEADERS += button.h
+HEADERS += label.h
+HEADERS += graphics.h
+HEADERS += widget.h
+
--- a/ui/qt/stock.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/stock.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -26,52 +26,10 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <ucx/map.h>
 
 #include "stock.h"
 #include "../ui/properties.h"
 
-static UcxMap *stock_items;
-
-void ui_stock_init() {
-    stock_items = ucx_map_new(64);
-    
-    ui_add_stock_item(UI_STOCK_NEW, "New", "document-new");
-    ui_add_stock_item(UI_STOCK_OPEN, "Open", "document-open");
-    ui_add_stock_item(UI_STOCK_SAVE, "Save", "document-save");
-    ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", "document-save-as");
-    ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", "document-revert");
-    ui_add_stock_item(UI_STOCK_CLOSE, "Close", "window-close");
-    ui_add_stock_item(UI_STOCK_UNDO, "Undo", "edit-undo");
-    ui_add_stock_item(UI_STOCK_REDO, "Redo", "edit-redo");
-    ui_add_stock_item(UI_STOCK_GO_BACK, "Back", "go-previous");
-    ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", "go-next");
-    ui_add_stock_item(UI_STOCK_CUT, "Cut", "edit-cut");
-    ui_add_stock_item(UI_STOCK_COPY, "Copy", "edit-copy");
-    ui_add_stock_item(UI_STOCK_PASTE, "Paste", "edit-paste");
-    ui_add_stock_item(UI_STOCK_DELETE, "Delete", "edit-delete");
-}
-
-void ui_add_stock_item(char *id, char *label, char *icon) {
-    UiStockItem *item = new UiStockItem(label, icon);
-    ucx_map_cstr_put(stock_items, id, item);
-}
-
-UiStockItem* ui_get_stock_item(char *id) {
-    UiStockItem *item = (UiStockItem*)ucx_map_cstr_get(stock_items, id);
-    if(item) {
-        char *label = uistr_n(id);
-        if(label) {
-            item->label = label;
-        }
-    }
-    return item;
-}
 
 
-UiStockItem::UiStockItem(char* label, char* icon_name) {
-    this->label = label;
-    this->icon_name = icon_name;
-}
 
-
--- a/ui/qt/stock.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/stock.h	Sat Apr 05 16:46:11 2025 +0200
@@ -31,19 +31,7 @@
 
 #include "../ui/stock.h"
 
-class UiStockItem {
-public:
-        
-    char *label;
-    char *icon_name;
-    
-    UiStockItem(char *label, char *icon_name);
-};
 
 
-void ui_stock_init();
-void ui_add_stock_item(char *id, char *label, char *icon);
-UiStockItem* ui_get_stock_item(char *id);
-
 #endif	/* STOCK_H */
 
--- a/ui/qt/text.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/text.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -32,124 +32,203 @@
 #include "../common/context.h"
 #include "../common/document.h"
 
-UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
-    QTextDocument *txtdoc = value && value->obj ? (QTextDocument*)value->obj : new QTextDocument();
+/*
+ * Gets or creates a QTextDocument for the UiText value and initializes it
+ * with the UiText string value
+ */
+static QTextDocument* get_or_create_doc(UiText *value) {
+    QTextDocument *document = nullptr;
+    if(value->data1) {
+        document = (QTextDocument*)value->data1;
+    } else {
+        document = new QTextDocument();
+        if(value->value.ptr) {
+            QString str = QString::fromUtf8(value->value.ptr);
+            document->setPlainText(str);
+        }
+    }
+    
+    if(value->value.free) {
+        value->value.free(value->value.ptr);
+    }
+    value->value.ptr = NULL;
+    value->value.free = NULL;
     
-    if(value) {
-        if(value->value && value->obj) {
-            QString str = QString::fromUtf8(value->value);
-            txtdoc->setPlainText(str);
-        }
+    return document;
+}
+
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
+    QTextEdit *textarea = new QTextEdit();
+    ctn->add(textarea, true);
+    
+    QTextDocument *document = nullptr;
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING);
+    if(var) {
+        UiText *value = (UiText*)var->value;
         
+        document = get_or_create_doc(value);
+        
+        value->save = ui_textarea_save;
+        value->restore = ui_textarea_restore;
+        value->destroy = ui_textarea_text_destroy;
         value->get = ui_textarea_get;
         value->set = ui_textarea_set;
         value->getsubstr = ui_textarea_getsubstr;
         value->insert = ui_textarea_insert;
         value->setposition = ui_textarea_setposition;
         value->position = ui_textarea_position;
+        value->setselection = ui_textarea_setselection;
         value->selection = ui_textarea_selection;
         value->length = ui_textarea_length;
         value->remove = ui_textarea_remove;
-        value->obj = txtdoc;
-        value->value = NULL;
+        value->obj = textarea;
+        value->data1 = document;
+    } else {
+        document = new QTextDocument();
     }
     
-    UiContainer *ct = uic_get_current_container(obj); 
-    QTextEdit *textedit = new QTextEdit();
-    textedit->setDocument(txtdoc);
-    ct->add(textedit, true);
+    textarea->setDocument(document);
     
-    return textedit;
+    return textarea;
 }
 
-UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_TEXT);
-    if(var) {
-        UiText *value = (UiText*)var->value;
-        return ui_textarea(obj, value);
-    } else {
-        // TODO: error
-    }
-    return NULL;
+void ui_textarea_save(UiText *text) {
+    // NOOP
 }
 
+void ui_textarea_restore(UiText *text) {
+    QTextEdit *textarea = (QTextEdit*)text->obj;
+    QTextDocument *document = get_or_create_doc(text);
+    textarea->setDocument(document);
+}
+
+void ui_textarea_text_destroy(UiText *text) {
+    QTextDocument *document = (QTextDocument*)text->data1;
+    if(document) {
+        delete document;
+    }
+}
 
 char* ui_textarea_get(UiText *text) {
-    if(text->value) {
-        free(text->value);
+    // clean previous value
+    if(text->value.free) {
+        text->value.free(text->value.ptr);
     }
     
-    QTextDocument *doc = (QTextDocument*)text->obj;
+    // get string
+    QTextDocument *doc = (QTextDocument*)text->data1;
     QString str = doc->toPlainText();
-    QByteArray array = str.toLocal8Bit();
+    QByteArray array = str.toUtf8();
     const char *cstr = array.constData();
     
-    if(text->value) {
-        free(text->value);
-    }
-    text->value = strdup(cstr);
-    return text->value;
+    // store a copy of the string in the UiText value
+    text->value.ptr = strdup(cstr);
+    text->value.free = free;
+    return text->value.ptr;
 }
 
-void ui_textarea_set(UiText *text, char *str) {
-    // set text
-    QTextDocument *doc = (QTextDocument*)text->obj;
+void ui_textarea_set(UiText *text, const char *str) {
+    if(text->value.free) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+    
+    QTextDocument *doc = (QTextDocument*)text->data1;
     QString qstr = QString::fromUtf8(str);
     doc->setPlainText(qstr);
-    // cleanup
-    if(text->value) {
-        free(text->value);
-    }
-    text->value = NULL;
 }
 
 char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
-    return NULL; // TODO
+    QTextDocument *doc = (QTextDocument*)text->data1;
+    QTextCursor cursor(doc);
+    cursor.setPosition(begin, QTextCursor::MoveAnchor);
+    cursor.setPosition(end, QTextCursor::KeepAnchor);
+    QString str = cursor.selectedText();
+    QByteArray bytes = str.toUtf8();
+    const char *cstr = bytes.constData();
+    return cstr ? strdup(cstr) : NULL;
 }
 
 void ui_textarea_insert(UiText *text, int pos, char *str) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
+    QTextDocument *doc = (QTextDocument*)text->data1;
+    QTextCursor cursor(doc);
+    cursor.setPosition(pos);
+    cursor.insertText(str);
 }
 
 void ui_textarea_setposition(UiText *text, int pos) {
-    // TODO
+    QTextEdit *textview = (QTextEdit*)text->obj;
+    QTextCursor cursor = textview->textCursor();
+    cursor.setPosition(pos);
+    textview->setTextCursor(cursor); 
 }
 
 int ui_textarea_position(UiText *text) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
-    return 0; // TODO
+    QTextEdit *textview = (QTextEdit*)text->obj;
+    QTextCursor cursor = textview->textCursor();
+    return cursor.position();
 }
 
 void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
+    QTextEdit *textview = (QTextEdit*)text->obj;
+    QTextCursor cursor = textview->textCursor();
+    if(cursor.hasSelection()) {
+        if(begin) {
+            *begin = cursor.selectionStart();
+        }
+        if(end) {
+            *end = cursor.selectionEnd();
+        }
+    }
+}
+
+void ui_textarea_setselection(UiText *text, int begin, int end) {
+    QTextEdit *textview = (QTextEdit*)text->obj;
+    QTextCursor cursor = textview->textCursor();
+    cursor.setPosition(begin, QTextCursor::MoveAnchor);
+    cursor.setPosition(end, QTextCursor::KeepAnchor);
+    textview->setTextCursor(cursor); 
 }
 
 int ui_textarea_length(UiText *text) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
-    return 0; // TODO
+    QTextDocument *doc = (QTextDocument*)text->data1;
+    return doc->characterCount();
 }
 
 void ui_textarea_remove(UiText *text, int begin, int end) {
-    QTextDocument *doc = (QTextDocument*)text->obj;
+    // TODO
 }
 
-
-/* ------------------- TextField ------------------- */
+/* ------------------------------ TextField ------------------------------ */
 
-UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs args, bool password, bool frameless) {
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    
     QLineEdit *textfield = new QLineEdit();
+    ctn->add(textfield, false);
+    
+    if(password) {
+        textfield->setEchoMode(QLineEdit::Password);
+    }
     
-    UiContainer *ct = uic_get_current_container(obj);
-    ct->add(textfield, false);
-    
-    if(value) {
-        if(value->value) {
-            QString str = QString::fromUtf8(value->value);
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING);
+    if(var) {
+        UiString *value = (UiString*)var->value;
+        if(value->value.ptr) {
+            QString str = QString::fromUtf8(value->value.ptr);
             textfield->setText(str);
-            free(value->value);
-            value->value = NULL;
+            if(value->value.free) {
+                value->value.free(value->value.ptr);
+            }
+            value->value.ptr = NULL;
         }
+        
         value->set = ui_textfield_set;
         value->get = ui_textfield_get;
         value->obj = textfield;
@@ -158,38 +237,40 @@
     return textfield;
 }
 
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_STRING);
-    if(var) {
-        UiString *value = (UiString*)var->value;
-        return ui_textfield(obj, value);
-    } else {
-        // TODO: error
-    }
-    return NULL;
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, false, false);
+}
+
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, false, true);
+}
+
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, args, true, false);
 }
 
 char* ui_textfield_get(UiString *str) {
     QLineEdit *textfield = (QLineEdit*)str->obj;
     QString qstr = textfield->text();
     
-    if(str->value) {
-        free(str->value);
+    if(str->value.free) {
+        str->value.free(str->value.ptr);
     }
-    QByteArray array = qstr.toLocal8Bit();
+    QByteArray array = qstr.toUtf8();
     const char *cstr = array.constData();
-    str->value = strdup(cstr);
+    str->value.ptr = strdup(cstr);
+    str->value.free = free;
     
-    return str->value;
+    return str->value.ptr;
 }
 
-void ui_textfield_set(UiString *str, char *value) {
+void ui_textfield_set(UiString *str, const char *value) {
     QLineEdit *textfield = (QLineEdit*)str->obj;
     QString qstr = QString::fromUtf8(value);
     textfield->setText(qstr);
     
-    if(str->value) {
-        free(str->value);
+    if(str->value.free) {
+        str->value.free(str->value.ptr);
     }
-    str->value = NULL;
+    str->value.ptr = NULL;
 }
--- a/ui/qt/text.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/text.h	Sat Apr 05 16:46:11 2025 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -36,18 +36,24 @@
 
 // value implementations
 extern "C" {    
-    char* ui_textarea_get(UiText *text);
-    void ui_textarea_set(UiText *text, char *str);
-    char* ui_textarea_getsubstr(UiText *text, int begin, int end);
-    void ui_textarea_insert(UiText *text, int pos, char *str);
-    void ui_textarea_setposition(UiText *text, int pos);
-    int ui_textarea_position(UiText *text);
-    void ui_textarea_selection(UiText *text, int *begin, int *end);
-    int ui_textarea_length(UiText *text);
-    void ui_textarea_remove(UiText *text, int begin, int end);
     
-    char* ui_textfield_get(UiString *str);
-    void ui_textfield_set(UiString *str, char *value);
+void ui_textarea_save(UiText *text);
+void ui_textarea_restore(UiText *text);
+void ui_textarea_text_destroy(UiText *text);
+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_setselection(UiText *text, int begin, int end);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+void ui_textarea_remove(UiText *text, int begin, int end);
+    
+char* ui_textfield_get(UiString *str) ;
+void ui_textfield_set(UiString *str, const char *value);
+
 }
 
 
--- a/ui/qt/toolbar.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/toolbar.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -26,140 +26,9 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <ucx/map.h>
 #include <inttypes.h>
 
 #include "toolbar.h"
 #include "menu.h"
 #include "stock.h"
 
-static UcxMap *toolbar_items = ucx_map_new(16);
-static UcxList *defaults;
-
-/* ------------------------- UiToolItem ------------------------- */
-
-UiToolItem::UiToolItem(char *label, ui_callback f, void *userdata) {
-    this->label = label;
-    this->image = NULL;
-    this->callback = f;
-    this->userdata = userdata;
-    this->isimportant = false;
-    this->groups = NULL;
-}
-
-void UiToolItem::addGroup(int group) {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-void UiToolItem::addTo(UiObject *obj, QToolBar *toolbar) {
-    QString str = QString::fromUtf8(label);
-    UiAction *action = new UiAction(obj, str, callback, userdata);
-    action->setIcon(QIcon::fromTheme(image));
-    toolbar->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
-}
-
-
-/* ------------------------- UiStockToolItem ------------------------- */
-
-UiStockToolItem::UiStockToolItem(char *stockid, ui_callback f, void *userdata) {
-    this->stockid = stockid;
-    this->callback = f;
-    this->userdata = userdata;
-    this->isimportant = false;
-    this->groups = NULL;
-}
-
-void UiStockToolItem::addGroup(int group) {
-    groups = ucx_list_append(groups, (void*)(intptr_t)group);
-}
-
-void UiStockToolItem::addTo(UiObject *obj, QToolBar *toolbar) {
-    UiStockItem *stockItem = ui_get_stock_item(stockid);
-    QString str = QString::fromUtf8(stockItem->label);
-    
-    UiAction *action = new UiAction(obj, str, callback, userdata);
-    action->setIcon(QIcon::fromTheme(stockItem->icon_name));
-    toolbar->addAction(action);
-    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
-}
-
-
-
-void ui_toolitem_vstgr(
-        char *name,
-        char *stockid,
-        int isimportant,
-        ui_callback f,
-        void *userdata,
-        va_list ap)
-{
-    UiStockToolItem *item = new UiStockToolItem(stockid, f, userdata);
-    item->isimportant = isimportant;
-    
-    // add groups
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        item->addGroup(group);
-    }
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
-    UiToolItem *item = new UiToolItem(label, f, udata);
-    item->image = img;
-    item->isimportant = false;
-    
-    ucx_map_cstr_put(toolbar_items, name, item);
-}
-
-void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
-    ui_toolitem_img(name, label, NULL, f, udata);
-}
-
-void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgr(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *userdata) {
-    ui_toolitem_stgri(name, stockid, f, userdata, -1);
-}
-
-void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    va_list ap;
-    va_start(ap, userdata);
-    ui_toolitem_vstgr(name, stockid, 0, f, userdata, ap);
-    va_end(ap);
-}
-
-void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...) {
-    va_list ap;
-    va_start(ap, userdata);
-    ui_toolitem_vstgr(name, stockid, 1, f, userdata, ap);
-    va_end(ap);
-}
-
-
-void ui_toolbar_add_default(char *name) {
-    char *s = strdup(name);
-    defaults = ucx_list_append(defaults, s);
-}
-
-
-QToolBar* ui_create_toolbar(UiObject *obj) {
-    QToolBar *toolbar = new QToolBar();
-    
-    UCX_FOREACH(elm, defaults) {
-        UiToolItemI *item = (UiToolItemI*)ucx_map_cstr_get(toolbar_items, (char*)elm->data);
-        if(item) {
-            item->addTo(obj, toolbar);
-        } else if(!strcmp((char*)elm->data, "@separator")) {
-            
-        } else {
-            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
-        }
-    }
-    
-    return toolbar;
-}
--- a/ui/qt/toolbar.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/toolbar.h	Sat Apr 05 16:46:11 2025 +0200
@@ -31,52 +31,9 @@
 
 #include "toolkit.h"
 #include "../ui/toolbar.h"
-#include <ucx/list.h>
 #include <QToolBar>
 
-class UiToolItemI {
-public:
-    virtual void addTo(UiObject *obj, QToolBar *toolbar) = 0;
-};
 
-class UiToolItem : public UiToolItemI {
-public:
-    char *label;
-    char *image;
-    ui_callback callback;
-    void *userdata;
-    UcxList *groups;
-    bool isimportant;
-    
-    UiToolItem(char *label, ui_callback f, void *userdata);
-    void addGroup(int group);
-    virtual void addTo(UiObject *obj, QToolBar *toolbar);
-};
-
-class UiStockToolItem : public UiToolItemI {
-public:
-    char *stockid;
-    ui_callback callback;
-    void *userdata;
-    UcxList *groups;
-    bool isimportant;
-    
-    UiStockToolItem(char *stockid, ui_callback f, void *userdata);
-    void addGroup(int group);
-    virtual void addTo(UiObject *obj, QToolBar *toolbar);
-};
-
-
-void ui_toolitem_vstgr(
-        char *name,
-        char *stockid,
-        int isimportant,
-        ui_callback f,
-        void *userdata,
-        va_list ap);
-
-
-QToolBar* ui_create_toolbar(UiObject *obj);
 
 
 #endif	/* TOOLBAR_H */
--- a/ui/qt/toolkit.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/toolkit.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -35,18 +35,25 @@
 
 #include "../common/document.h"
 #include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
 
-static char *application_name;
+static const char *application_name;
 
-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 QApplication app(qargc, qargv);
+static int is_toplevel_realized = 0;
+
 int app_argc;
 char **app_argv;
 QApplication *application = NULL;
 
-void ui_init(char *appname, int argc, char **argv) {
+void ui_init(const char *appname, int argc, char **argv) {
     application_name = appname;
     
     app_argc = argc;
@@ -54,30 +61,39 @@
     application = new QApplication(app_argc, app_argv);
     
     uic_docmgr_init();
+    uic_menu_init();
+    uic_toolbar_init();
     
     uic_load_app_properties();
-    
-    ui_stock_init();
+     
 }
 
-char* ui_appname() {
+const char* ui_appname() {
     return application_name;
 }
 
-void ui_exitfunc(ui_callback f, void *udata) {
-    appclose_fnc = f;
-    appclose_udata = udata;
+void ui_onstartup(ui_callback f, void *userdata) {
+    startup_func = f;
+    startup_data = userdata;
 }
 
-void ui_openfilefunc(ui_callback f, void *userdata) {
-    // OS X only
+void ui_onopen(ui_callback f, void *userdata) {
+    open_func = f;
+    open_data = userdata;
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+    exit_func = f;
+    exit_data = userdata;
 }
 
 void ui_main() {
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
     application->exec();
-    
-    if(appclose_fnc) {
-        appclose_fnc(NULL, appclose_udata);
+    if(exit_func) {
+        exit_func(NULL, exit_data);
     }
     uic_store_app_properties();
     
@@ -103,6 +119,7 @@
 
 
 
+/* --------------------- Implemtation UiEventWrapper --------------------- */
 
 UiEventWrapper::UiEventWrapper(UiObject *obj, ui_callback f, void* userdata) {
     this->obj = obj;
@@ -111,11 +128,64 @@
 }
 
 void UiEventWrapper::slot() {
+    if(!callback) {
+        return;
+    }
+    
     UiEvent e;
     e.obj = obj;
     e.window = obj->window;
     e.document = obj->ctx->document;
     e.eventdata = NULL;
     e.intval = 0;
+    e.set = ui_get_setop();
+    if(prepare_event) {
+        prepare_event(&e, this);
+    }
     callback(&e, userdata);
+    
+    // TODO: notify var observers
 }
+
+void UiEventWrapper::destroy() {
+    delete this;
+}
+
+
+/* --------------------- Implemtation UiAction --------------------- */
+
+UiAction::UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata) : QAction(label, NULL) {
+    this->obj = obj;
+    this->callback = f;
+    this->userdata = userdata;
+}
+
+UiAction::~UiAction() {
+    // TODO: unbind var
+}
+
+void UiAction::trigger() {
+    if(!callback) {
+        return;
+    }
+    
+    UiEvent e;
+    e.obj = obj;
+    e.window = obj->window;
+    e.document = obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = 0;
+    e.set = ui_get_setop();
+    if(prepare_event) {
+        prepare_event(&e, this);
+    }
+    callback(&e, userdata);
+    
+    // TODO: notify var observers
+}
+
+// ui_enablefunc for UiAction
+void ui_action_enable(UiAction *action, int enable) {
+    action->setEnabled((bool)enable);
+}
+
--- a/ui/qt/toolkit.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/toolkit.h	Sat Apr 05 16:46:11 2025 +0200
@@ -35,20 +35,62 @@
 
 #include <QApplication>
 
+class UiEventWrapper;
+
+typedef void (*ui_prepare_event_func)(UiEvent *event, UiEventWrapper *wrapper);
+
 class UiEventWrapper : public QObject {
     Q_OBJECT
     
     UiObject *obj;
     ui_callback callback;
     void *userdata;
-
+    
 public:
+    UiVar *var;
+    
+    void *customdata1 = nullptr;
+    void *customdata2 = nullptr;
+    int customvalue1 = 0;
+    int customvalue2 = 0;
+    
+    ui_prepare_event_func prepare_event = nullptr;
+    
     UiEventWrapper(UiObject *obj, ui_callback f, void *userdata);
     
 public slots:
     void slot();
+    void destroy();
 };
 
+class UiAction;
+
+typedef void (*ui_prepare_action_event_func)(UiEvent *event, UiAction *action);
+
+class UiAction : public QAction {
+    Q_OBJECT
+            
+    UiObject *obj;
+    ui_callback callback;
+    void *userdata;
+    
+public:
+    UiVar *var;
+    
+    ui_prepare_action_event_func prepare_event = nullptr;
+    void *customdata1 = nullptr;
+    void *customdata2 = nullptr;
+    int customvalue1 = 0;
+    int customvalue2 = 0;
+    
+    UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata);
+    ~UiAction();
+    
+private slots:
+    void trigger();
+};
+
+void ui_action_enable(UiAction *action, int enable);
 
 #endif	/* TOOLKIT_H */
 
--- a/ui/qt/tree.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/tree.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -34,109 +34,3 @@
 #include <QListView>
 
 
-extern "C" 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, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    QListView *view = new QListView();
-    ListModel *model = new ListModel(obj, view, list, getvalue, f, udata);
-    view->setModel(model);
-    
-    // TODO: observer update
-    
-    QItemSelectionModel *s = view->selectionModel();
-    QObject::connect(
-            s,
-            SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
-            model,
-            SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
-    
-    UiContainer *ct = uic_get_current_container(obj); 
-    ct->add(view, true);
-    return view;
-}
-
-UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    UiListPtr *listptr = (UiListPtr*)ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
-    listptr->list = list;
-    return ui_listview_var(obj, listptr, getvalue, f, udata);
-}
-
-UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = (UiListVar*)var->value;
-        return ui_listview_var(obj, value->listptr, getvalue, f, udata);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
-
-UIWIDGET ui_table_var(UiObject *obj, UiListPtr *list, UiModelInfo *modelinfo) {
-    QTreeView *view = new QTreeView();
-    TableModel *model = new TableModel(obj, view, list, modelinfo);
-    view->setModel(model);
-    
-    view->setItemsExpandable(false);
-    view->setRootIsDecorated(false);   
-    
-    // TODO: observer update
-    UiTableView *u = new UiTableView();
-    u->widget = view;
-    u->model = model;
-    list->list->observers = ui_add_observer(
-            list->list->observers,
-            (ui_callback)ui_table_update,
-            u);
-    
-    view->setSelectionMode(QAbstractItemView::ExtendedSelection);
-    QItemSelectionModel *s = view->selectionModel();
-    QObject::connect(
-            s,
-            SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
-            model,
-            SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
-    QObject::connect(
-            view,
-            SIGNAL(doubleClicked(const QModelIndex &)),
-            model,
-            SLOT(activate(const QModelIndex &)));
-    
-    
-    UiContainer *ct = uic_get_current_container(obj); 
-    ct->add(view, true);
-    return view;
-}
-
-void ui_table_update(UiEvent *event, UiTableView *view) {
-    // TODO
-    printf("update\n");
-    
-    //view->model->update();
-    view->widget->setModel(NULL);
-    view->widget->setModel(view->model);
-}
-
-UIWIDGET ui_table(UiObject *obj, UiList *list, UiModelInfo *modelinfo) {
-    UiListPtr *listptr = (UiListPtr*)ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
-    listptr->list = list;
-    return ui_table_var(obj, listptr, modelinfo);
-}
-
-UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModelInfo *modelinfo) {
-    UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
-    if(var) {
-        UiListVar *value = (UiListVar*)var->value;
-        return ui_table_var(obj, value->listptr, modelinfo);
-    } else {
-        // TODO: error
-    }
-    return NULL;
-}
-
--- a/ui/qt/tree.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/tree.h	Sat Apr 05 16:46:11 2025 +0200
@@ -34,13 +34,7 @@
 
 #include <QTableView>
 
-class UiTableView {
-public:
-    QTreeView *widget;
-    TableModel *model;
-};
 
-extern "C" void ui_table_update(UiEvent *event, UiTableView *view);
 
 #endif	/* TREE_H */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/widget.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "widget.h"
+
+#include "container.h"
+#include "../common/context.h"
+
+UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+    UIWIDGET widget = create_widget(obj, args, userdata);
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, args);
+    ctn->add(widget, false);
+    return widget;
+}
+
+UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) {
+    QFrame *separator = new QFrame();
+    separator->setFrameShape(QFrame::HLine);
+    separator->setFrameShadow(QFrame::Sunken);
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UI_APPLY_LAYOUT(ctn->layout, (*args));
+    
+    ctn->add(separator, false);
+    
+    return separator;
+}
+
+void ui_widget_set_size(UIWIDGET w, int width, int height) {
+    w->resize(width >= 0 ? width : w->width(), height >= 0 ? height : w->height());
+}
+
+void ui_widget_redraw(UIWIDGET w) {
+    w->repaint();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/widget.h	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,38 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include "toolkit.h"
+
+#include "../ui/widget.h"
+
+
+#endif /* WIDGET_H */
+
--- a/ui/qt/window.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/qt/window.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -26,7 +26,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <ucx/mempool.h>
+#include <cx/mempool.h>
 #include "../common/context.h"
 
 #include "window.h"
@@ -36,34 +36,36 @@
 
 #include <QVBoxLayout>
 #include <QFileDialog>
+#include <QPushButton>
 
-static UiObject* create_window(char *title, void *window_data, bool simple) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = (UiObject*)ucx_mempool_calloc(mp, 1, sizeof(UiObject));
+static UiObject* create_window(const char *title, void *window_data, bool simple) {
+    CxMempool *mp = cxMempoolCreateSimple(256);
+    UiObject *obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
     obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     obj->next = NULL;
     
     QMainWindow *window = new QMainWindow();
+    window->setWindowTitle(title);
     obj->widget = window;
     
     if(!simple) {
         ui_add_menus(obj, window);
-        QToolBar *toolbar = ui_create_toolbar(obj);
-        window->addToolBar(Qt::TopToolBarArea, toolbar);
+        //QToolBar *toolbar = ui_create_toolbar(obj);
+        //window->addToolBar(Qt::TopToolBarArea, toolbar);
     }
     
     QBoxLayout *box = new QVBoxLayout();
     QWidget *boxWidget = new QWidget();
     boxWidget->setLayout(box);
     window->setCentralWidget(boxWidget);
-    obj->container = new UiBoxContainer(box);
+    ui_container_add(obj, new UiBoxContainer(box));
     
     obj->widget = window;
     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);
 }
 
--- a/ui/ui/container.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/container.h	Sat Apr 05 16:46:11 2025 +0200
@@ -57,18 +57,6 @@
     UI_HEADERBAR_ALTERNATIVE_BOX
 } UiHeaderbarAlternative;
 
-typedef struct UiWidgetArgs {
-    UiTri fill;
-    UiBool hexpand;
-    UiBool vexpand;
-    UiBool hfill;
-    UiBool vfill;
-    UiBool override_defaults;
-    int colspan;
-    int rowspan;
-    const char *name;
-    const char *style_class;
-} UiWidgetArgs;
 
 typedef struct UiContainerArgs {
     UiTri fill;
@@ -267,13 +255,20 @@
 #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_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid_w(obj, w, ...) for(w = ui_grid_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_tabview_w(obj, w, ...) for(w = ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow_w(obj, w, ...) for(w = ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 
 #define ui_hsplitpane(obj, ...) for(ui_hsplitpane_create(obj, (UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_vsplitpane(obj, ...) for(ui_vsplitpane_create(obj, (UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_hsplitpane0(obj) for(ui_hsplitpane_create(obj, (UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 #define ui_vsplitpane0(obj) for(ui_vsplitpane_create(obj, (UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
 
+#define ui_hsplitpane_w(obj, w, ...) for(w = ui_hsplitpane_create(obj, (UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_vsplitpane_w(obj, w, ...) for(w = ui_vsplitpane_create(obj, (UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+
 #define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
 
 #define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
@@ -310,6 +305,7 @@
 UIEXPORT UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs args);
 UIEXPORT UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs args);
 
+UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible);
 
 // box container layout functions
 UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
@@ -330,17 +326,6 @@
 UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
 
 
-#ifdef UI_GTK
-typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
-#elif defined(UI_MOTIF)
-typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata, Widget parent, Arg *a, int n);
-#elif defined(UI_COCOA)
-typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
-#endif
-UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args);
-
-#define ui_customwidget(obj, create_widget, userdata, ...) ui_customwidget_create(obj, create_widget, userdata, (UiWidgetArgs) { __VA_ARGS__ })
-
 
 /* used for macro */
 UIEXPORT void ui_container_begin_close(UiObject *obj);
--- a/ui/ui/display.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/display.h	Sat Apr 05 16:46:11 2025 +0200
@@ -123,7 +123,6 @@
 UIEXPORT UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args);
 
 UIWIDGET ui_space_deprecated(UiObject *obj);
-UIWIDGET ui_separator_deprecated(UiObject *obj);
 
 /* progress bar/spinner */
 
--- a/ui/ui/image.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/image.h	Sat Apr 05 16:46:11 2025 +0200
@@ -37,6 +37,13 @@
 
 #define UI_IMAGE_OBJECT_TYPE "image"
     
+#ifdef UI_GTK
+#define UIIMAGE GdkPixbuf*
+#else
+#define UIIMAGE void*
+#endif
+    
+    
 typedef struct UiImageViewerArgs {
     UiTri fill;
     UiBool hexpand;
@@ -51,17 +58,37 @@
 
     UiBool scrollarea;
     UiBool autoscale;
+    UiBool adjustwidgetsize;
+    UiBool useradjustable;
+    int image_padding;
+    int image_padding_left;
+    int image_padding_right;
+    int image_padding_top;
+    int image_padding_bottom;
     UiGeneric *value;
     const char *varname;
     UiMenuBuilder *contextmenu;
+    
+    ui_callback onbuttonpress;
+    void *onbuttonpressdata;
+    ui_callback onbuttonrelease;
+    void *onbuttonreleasedata;
 } UiImageViewerArgs;
     
 #define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
     
 UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args);
 
+UIEXPORT UIWIDGET ui_imageviewer_reset(UIWIDGET w);
+UIEXPORT UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set);
+UIEXPORT UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set);
+UIEXPORT UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set);
+
 UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path);
 
+UIEXPORT void ui_image_ref(UIIMAGE img);
+UIEXPORT void ui_image_unref(UIIMAGE img);
+
 #ifdef __cplusplus
 }
 #endif
--- a/ui/ui/toolkit.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/toolkit.h	Sat Apr 05 16:46:11 2025 +0200
@@ -30,6 +30,7 @@
 #define	UI_TOOLKIT_H
 
 #include <inttypes.h>
+#include <stdlib.h>
 
 #ifdef UI_COCOA
 
@@ -60,23 +61,40 @@
 #include <adwaita.h>
 #endif
 
+#elif defined(UI_QT4) || defined(UI_QT5)
+#define UI_QT
+
+#ifdef	__cplusplus
+
+#include <QApplication>
+#include <QWidget>
+#include <QMenu>
+
+#define UIWIDGET QWidget*
+#define UIWINDOW QWidget*
+#define UIMENU   QMenu*
+#else /* __cplusplus */
+#define UIWIDGET void*
+#define UIWINDOW void*
+#define UIMENU   void*
+#endif
+
 #elif UI_MOTIF
 
 #include <Xm/XmAll.h> 
 #define UIWIDGET Widget
 #define UIMENU   Widget
 
-#elif defined(UI_QT4) || defined(UI_QT5)
-#ifdef	__cplusplus
-#include <QApplication>
-#include <QWidget>
-#include <QMenu>
-#define UIWIDGET QWidget*
-#define UIMENU   QMenu*
-#else /* __cplusplus */
+
+#elif UI_WIN32
+
+#include <Windows.h>
+
+#define UIEXPORT __declspec(dllexport)
+
 #define UIWIDGET void*
+#define UIWINDOW void*
 #define UIMENU   void*
-#endif
 
 #elif UI_WINUI
 
@@ -152,7 +170,11 @@
 #define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 }
     
 /* public types */
+#ifndef __cplusplus
 typedef _Bool UiBool;
+#else
+typedef bool UiBool;
+#endif
 
 typedef struct UiObject     UiObject;
 typedef struct UiContainerX UiContainerX;
@@ -189,16 +211,29 @@
 
 typedef struct UiTabbedPane UiTabbedPane;
 
-typedef enum UiTri UiTri;
-typedef enum UiLabelType UiLabelType;
+typedef enum UiTri {
+    UI_DEFAULT = 0,
+    UI_ON,
+    UI_OFF
+} UiTri;
 
-typedef enum UiDnDAction UiDnDAction;
 
 enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
 
-enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON };
+typedef enum UiLabelType {
+    UI_LABEL_DEFAULT,
+    UI_LABEL_TEXT,
+    UI_LABEL_ICON,
+    UI_LABEL_TEXT_ICON
+} UiLabelType;
 
-enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM };
+typedef enum UiDnDAction {
+    UI_DND_ACTION_NONE,
+    UI_DND_ACTION_COPY,
+    UI_DND_ACTION_MOVE,
+    UI_DND_ACTION_LINK,
+    UI_DND_ACTION_CUSTOM
+} UiDnDAction;
   
 typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
 
@@ -284,6 +319,7 @@
     void     *window;
     void     *eventdata;
     int      intval;
+    int      set;
 };
 
 struct UiMouseEvent {
@@ -332,23 +368,32 @@
 };
 
 struct UiText {
+    void  (*save)(UiText*);
+    void  (*destroy)(UiText*);
+    void  (*restore)(UiText*);
     void  (*set)(UiText*, const char*);
     char* (*get)(UiText*);
     char* (*getsubstr)(UiText*, int, int); /* text, begin, end */
     void  (*insert)(UiText*, int, char*);
     void  (*setposition)(UiText*,int);
     int   (*position)(UiText*);
+    void  (*setselection)(UiText*, int, int); /* text, begin, end */
     void  (*selection)(UiText*, int*, int*); /* text, begin, end */
     int   (*length)(UiText*);
     void  (*remove)(UiText*, int, int); /* text, begin, end */
     UiStr value;
     int   pos;
     void  *obj;
-    void  *undomgr;
+    int   datatype;
+    void  *data1;
+    void  *data2;
     // TODO: replacefunc, ...
     UiObserver *observers;
 };
 
+/* UiText.datatype */
+#define UI_TEXT_TYPE_BUFFER 1
+
 struct UiGeneric {
     void* (*get)(UiGeneric*);
     const char* (*get_type)(UiGeneric*);
@@ -415,12 +460,6 @@
     UiObserver *observers;
 };
 
-enum UiTri {
-    UI_DEFAULT = 0,
-    UI_ON,
-    UI_OFF
-};
-
 struct UiFileList {
     char **files;
     size_t nfiles;
@@ -435,6 +474,8 @@
 UIEXPORT void ui_init(const char *appname, int argc, char **argv);
 UIEXPORT const char* ui_appname();
 
+UIEXPORT void ui_add_styledata(const char *styledata, int len);
+
 UIEXPORT UiContext* ui_global_context(void);
 
 UIEXPORT void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
@@ -580,6 +621,9 @@
 UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata);
 UIEXPORT void ui_condvar_destroy(UiCondVar *var);
 
+UIEXPORT void ui_setop_enable(int set);
+UIEXPORT int ui_get_setop(void);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/ui/tree.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/tree.h	Sat Apr 05 16:46:11 2025 +0200
@@ -118,6 +118,8 @@
     UiList* list;
     const char* varname;
     UiModel* model;
+    char **static_elements;
+    size_t static_nelm;
     ui_getvaluefunc getvalue;
     ui_callback onactivate;
     void* onactivatedata;
@@ -249,6 +251,9 @@
 UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args);
 UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args);
 
+UIEXPORT void ui_listview_select(UIWIDGET listview, int index);
+UIEXPORT void ui_combobox_select(UIWIDGET dropdown, int index);
+
 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args);
 
 
--- a/ui/ui/ui.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/ui/ui.h	Sat Apr 05 16:46:11 2025 +0200
@@ -30,6 +30,7 @@
 #define	UI_H
 
 #include "toolkit.h"
+#include "widget.h"
 #include "container.h"
 #include "menu.h"
 #include "toolbar.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/widget.h	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,81 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef UI_WIDGET_H
+#define UI_WIDGET_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiWidgetArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    UiBool override_defaults;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+} UiWidgetArgs;
+
+#ifdef UI_GTK
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#elif defined(UI_QT)
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#elif defined(UI_MOTIF)
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata, Widget parent, Arg *a, int n);
+#elif defined(UI_COCOA)
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#elif defined(UI_WINUI)
+typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#elif defined(UI_WIN32)
+typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#endif
+UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args);
+
+#define ui_customwidget(obj, create_widget, userdata, ...) ui_customwidget_create(obj, create_widget, userdata, (UiWidgetArgs) { __VA_ARGS__ })
+
+
+UIEXPORT UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args);
+
+#define ui_separator(obj, ...) ui_separator_create(obj, &(UiWidgetArgs){ __VA_ARGS__ } )
+
+UIEXPORT void ui_widget_set_size(UIWIDGET w, int width, int height);
+UIEXPORT void ui_widget_redraw(UIWIDGET w);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_WIDGET_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/Makefile	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,33 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(WIN32_OBJPRE)%.obj: win32/%.c
+	$(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/objs.mk	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,35 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+WIN32_SRC_DIR = ui/win32/
+WIN32_OBJPRE = $(OBJ_DIR)$(WIN32_SRC_DIR)
+
+WIN32OBJ = toolkit.obj
+
+TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%)
+TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/toolkit.c	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,85 @@
+/*
+ * 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 "toolkit.h"
+#include "Windows.h"
+
+#include "../common/properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static const char *application_name;
+
+static ui_callback   startup_func;
+static void          *startup_data;
+static ui_callback   open_func;
+void                 *open_data;
+static ui_callback   exit_func;
+void                 *exit_data;
+
+void ui_init(const char *appname, int argc, char **argv) {
+      application_name = appname;
+}
+
+const char* ui_appname() {
+    return application_name;
+}
+
+void ui_onstartup(ui_callback f, void *userdata) {
+    startup_func = f;
+    startup_data = userdata;
+}
+
+void ui_onopen(ui_callback f, void *userdata) {
+    open_func = f;
+    open_data = userdata;
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+    exit_func = f;
+    exit_data = userdata;
+}
+
+void ui_main() {
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
+
+    // event loop
+    MSG msg;
+    while (GetMessage(&msg, NULL, 0, 0)) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+
+    if(exit_func) {
+        exit_func(NULL, exit_data);
+    }
+    uic_store_app_properties();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/toolkit.h	Sat Apr 05 16:46:11 2025 +0200
@@ -0,0 +1,48 @@
+/*
+ * 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 TOOLKIT_H
+#define	TOOLKIT_H
+
+#include <inttypes.h>
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLKIT_H */
+
--- a/ui/winui/container.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/winui/container.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -51,6 +51,19 @@
 }
 
 
+UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	UIWIDGET widget = create_widget(obj, args, userdata);
+	FrameworkElement w = widget->uielement.as<FrameworkElement>();
+
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(w, false);
+
+	return widget;
+}
+
 // --------------------- UiBoxContainer ---------------------
 
 static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {
@@ -873,6 +886,7 @@
 }
 
 
+
 /*
 * -------------------- Layout Functions --------------------
 *
@@ -905,6 +919,11 @@
 	ct->layout.vfill = fill;
 }
 
+void ui_layout_override_defaults(UiObject* obj, UiBool def) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.override_defaults = def;
+}
+
 void ui_layout_width(UiObject* obj, int width) {
 	UiContainer* ct = uic_get_current_container(obj);
 	ct->layout.width = width;
--- a/ui/winui/container.h	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/winui/container.h	Sat Apr 05 16:46:11 2025 +0200
@@ -69,6 +69,7 @@
     UiBool       vexpand;
     UiBool       hfill;
     UiBool       vfill;
+    UiBool       override_defaults;
     int          width;
     int          height;
     int          colspan;
--- a/ui/winui/toolkit.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/winui/toolkit.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -232,9 +232,13 @@
 
 void ui_show(UiObject* obj) {
 	if (obj->wobj) {
-		obj->wobj->window.Activate();
+		if (!obj->wobj->window.Visible()) {
+			obj->wobj->window.Activate();
+			obj->ref++;
+		}
 	} else if(obj->widget && obj->widget->Show) {
 		obj->widget->Show();
+		obj->ref++; // TODO: should we check if the widget is already visible?
 	}
 }
 
--- a/ui/winui/window.cpp	Tue Feb 25 21:11:00 2025 +0100
+++ b/ui/winui/window.cpp	Sat Apr 05 16:46:11 2025 +0200
@@ -63,6 +63,11 @@
 
 UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}
 
+extern "C" static void ui_window_widget_destroy(UiObject *obj) {
+	obj->ref = 1;
+	obj->wobj->window.Close();
+}
+
 UiObject* ui_window(const char* title, void* window_data) {
 	UiObject* obj = ui_simple_window(title, window_data);
 
@@ -178,17 +183,14 @@
 	obj->wobj = new UiWindow(window);
 	ui_context_add_window_destructor(obj->ctx, obj->wobj);
 
-	window.Closed([obj](IInspectable const& sender, WindowEventArgs) {
-		if (obj->ctx->close_callback) {
-			UiEvent evt;
-			evt.obj = obj;
-			evt.document = obj->ctx->document;
-			evt.window = obj->window;
-			evt.eventdata = NULL;
-			evt.intval = 0;
-			obj->ctx->close_callback(&evt, obj->ctx->close_data);
+	window.Closed([obj](IInspectable const& sender, WindowEventArgs e) {
+		uic_context_prepare_close(obj->ctx);
+		obj->ref--;
+		if (obj->ref > 0) {
+			obj->wobj->window.AppWindow().Hide();
+			e.Handled(true);
 		} else {
-			ui_context_destroy(obj->ctx);
+			uic_object_destroy(obj);
 		}
 		});
 

mercurial