add existing toolkit code

Sun, 21 Jan 2024 16:30:18 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 21 Jan 2024 16:30:18 +0100
changeset 0
2483f517c562
child 1
b5bb7b3cd597

add existing toolkit code

.hgignore file | annotate | diff | comparison | revisions
Makefile file | annotate | diff | comparison | revisions
application/Makefile file | annotate | diff | comparison | revisions
application/main.c file | annotate | diff | comparison | revisions
configure file | annotate | diff | comparison | revisions
make/Makefile.mk file | annotate | diff | comparison | revisions
make/clang.mk file | annotate | diff | comparison | revisions
make/configure.vm file | annotate | diff | comparison | revisions
make/gcc.mk file | annotate | diff | comparison | revisions
make/mingw.mk file | annotate | diff | comparison | revisions
make/osx.mk file | annotate | diff | comparison | revisions
make/package_osx.sh file | annotate | diff | comparison | revisions
make/package_unix.sh file | annotate | diff | comparison | revisions
make/package_windows.sh file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
make/suncc.mk file | annotate | diff | comparison | revisions
make/toolchain.sh file | annotate | diff | comparison | revisions
make/vs/idav.sln file | annotate | diff | comparison | revisions
make/vs/idav/app.manifest file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj file | annotate | diff | comparison | revisions
make/vs/idav/idav.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/idav/main.c file | annotate | diff | comparison | revisions
make/vs/idav/main.h file | annotate | diff | comparison | revisions
make/vs/idav/packages.config file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.exe.recipe file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.ilk file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.log file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/CL.command.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/CL.read.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/CL.write.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/Cl.items.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/CopyLocal.read.1u.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/CopyLocal.write.1u.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/idav.lastbuildstate file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/link.command.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/link.read.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.tlog/link.write.1.tlog file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.vcxproj.AssemblyReference.cache file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.vcxproj.CopyComplete file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/idav.vcxproj.FileListAbsolute.txt file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/main.obj file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/vc143.idb file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/vc143.pdb file | annotate | diff | comparison | revisions
make/vs/idav/x64/Debug/vcpkg.applocal.log file | annotate | diff | comparison | revisions
make/vs/testapp/app.manifest file | annotate | diff | comparison | revisions
make/vs/testapp/main.c file | annotate | diff | comparison | revisions
make/vs/testapp/packages.config file | annotate | diff | comparison | revisions
make/vs/testapp/testapp.vcxproj file | annotate | diff | comparison | revisions
make/vs/testapp/testapp.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/ucx/ucx.vcxproj file | annotate | diff | comparison | revisions
make/vs/ucx/ucx.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/uicommon/uicommon.vcxproj file | annotate | diff | comparison | revisions
make/vs/uicommon/uicommon.vcxproj.filters file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.AppLifecycle.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.AppLifecycle.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.DynamicDependency.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.DynamicDependency.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.PushNotifications.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.PushNotifications.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.Security.AccessControl.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.System.Power.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.System.Power.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.System.winmd file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.Windows.System.xml file | annotate | diff | comparison | revisions
make/vs/x64/Debug/Microsoft.WindowsAppRuntime.Bootstrap.dll file | annotate | diff | comparison | revisions
make/vs/x64/Debug/idav.exe file | annotate | diff | comparison | revisions
make/vs/x64/Debug/idav.pdb file | annotate | diff | comparison | revisions
make/windows.mk file | annotate | diff | comparison | revisions
resource/.DS_Store file | annotate | diff | comparison | revisions
resource/locales/de_DE.properties file | annotate | diff | comparison | revisions
resource/locales/en_EN.properties file | annotate | diff | comparison | revisions
resource/template.app/Contents/Info.plist file | annotate | diff | comparison | revisions
resource/template.app/Contents/PkgInfo file | annotate | diff | comparison | revisions
resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings file | annotate | diff | comparison | revisions
resource/template.app/Contents/Resources/English.lproj/MainMenu.nib file | annotate | diff | comparison | revisions
ucx/Makefile file | annotate | diff | comparison | revisions
ucx/allocator.c file | annotate | diff | comparison | revisions
ucx/array_list.c file | annotate | diff | comparison | revisions
ucx/buffer.c file | annotate | diff | comparison | revisions
ucx/compare.c file | annotate | diff | comparison | revisions
ucx/cx/allocator.h file | annotate | diff | comparison | revisions
ucx/cx/array_list.h file | annotate | diff | comparison | revisions
ucx/cx/buffer.h file | annotate | diff | comparison | revisions
ucx/cx/collection.h file | annotate | diff | comparison | revisions
ucx/cx/common.h file | annotate | diff | comparison | revisions
ucx/cx/common.h.orig file | annotate | diff | comparison | revisions
ucx/cx/compare.h file | annotate | diff | comparison | revisions
ucx/cx/hash_key.h file | annotate | diff | comparison | revisions
ucx/cx/hash_map.h file | annotate | diff | comparison | revisions
ucx/cx/iterator.h file | annotate | diff | comparison | revisions
ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
ucx/cx/list.h file | annotate | diff | comparison | revisions
ucx/cx/map.h file | annotate | diff | comparison | revisions
ucx/cx/mempool.h file | annotate | diff | comparison | revisions
ucx/cx/printf.h file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/cx/utils.h file | annotate | diff | comparison | revisions
ucx/hash_key.c file | annotate | diff | comparison | revisions
ucx/hash_map.c file | annotate | diff | comparison | revisions
ucx/linked_list.c file | annotate | diff | comparison | revisions
ucx/list.c file | annotate | diff | comparison | revisions
ucx/map.c file | annotate | diff | comparison | revisions
ucx/mempool.c file | annotate | diff | comparison | revisions
ucx/printf.c file | annotate | diff | comparison | revisions
ucx/string.c file | annotate | diff | comparison | revisions
ucx/szmul.c file | annotate | diff | comparison | revisions
ucx/utils.c file | annotate | diff | comparison | revisions
ui/Makefile file | annotate | diff | comparison | revisions
ui/cocoa/Makefile file | annotate | diff | comparison | revisions
ui/cocoa/container.h file | annotate | diff | comparison | revisions
ui/cocoa/container.m file | annotate | diff | comparison | revisions
ui/cocoa/graphics.h file | annotate | diff | comparison | revisions
ui/cocoa/graphics.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/resource.h file | annotate | diff | comparison | revisions
ui/cocoa/resource.m file | annotate | diff | comparison | revisions
ui/cocoa/stock.h file | annotate | diff | comparison | revisions
ui/cocoa/stock.m 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/toolbar.h file | annotate | diff | comparison | revisions
ui/cocoa/toolbar.m file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.h file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.m file | annotate | diff | comparison | revisions
ui/cocoa/tree.h file | annotate | diff | comparison | revisions
ui/cocoa/tree.m file | annotate | diff | comparison | revisions
ui/cocoa/window.h file | annotate | diff | comparison | revisions
ui/cocoa/window.m file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/document.c file | annotate | diff | comparison | revisions
ui/common/document.h file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/menu.h file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/object.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/properties.h file | annotate | diff | comparison | revisions
ui/common/toolbar.c file | annotate | diff | comparison | revisions
ui/common/toolbar.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/types.h file | annotate | diff | comparison | revisions
ui/common/ucx_properties.c file | annotate | diff | comparison | revisions
ui/common/ucx_properties.h file | annotate | diff | comparison | revisions
ui/gtk/Makefile file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/button.h file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/container.h file | annotate | diff | comparison | revisions
ui/gtk/display.c file | annotate | diff | comparison | revisions
ui/gtk/display.h file | annotate | diff | comparison | revisions
ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.h file | annotate | diff | comparison | revisions
ui/gtk/draw_cairo.c file | annotate | diff | comparison | revisions
ui/gtk/draw_cairo.h file | annotate | diff | comparison | revisions
ui/gtk/draw_gdk.c file | annotate | diff | comparison | revisions
ui/gtk/draw_gdk.h file | annotate | diff | comparison | revisions
ui/gtk/entry.c file | annotate | diff | comparison | revisions
ui/gtk/entry.h file | annotate | diff | comparison | revisions
ui/gtk/graphics.c file | annotate | diff | comparison | revisions
ui/gtk/graphics.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/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
ui/gtk/model.c file | annotate | diff | comparison | revisions
ui/gtk/model.h file | annotate | diff | comparison | revisions
ui/gtk/objs.mk file | annotate | diff | comparison | revisions
ui/gtk/range.c file | annotate | diff | comparison | revisions
ui/gtk/range.h file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolbar.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/tree.c file | annotate | diff | comparison | revisions
ui/gtk/tree.h file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/motif/Makefile file | annotate | diff | comparison | revisions
ui/motif/button.c file | annotate | diff | comparison | revisions
ui/motif/button.h file | annotate | diff | comparison | revisions
ui/motif/container.c file | annotate | diff | comparison | revisions
ui/motif/container.h file | annotate | diff | comparison | revisions
ui/motif/dnd.c file | annotate | diff | comparison | revisions
ui/motif/dnd.h file | annotate | diff | comparison | revisions
ui/motif/graphics.c file | annotate | diff | comparison | revisions
ui/motif/graphics.h file | annotate | diff | comparison | revisions
ui/motif/image.c file | annotate | diff | comparison | revisions
ui/motif/image.h file | annotate | diff | comparison | revisions
ui/motif/label.c file | annotate | diff | comparison | revisions
ui/motif/label.h file | annotate | diff | comparison | revisions
ui/motif/list.c file | annotate | diff | comparison | revisions
ui/motif/list.h file | annotate | diff | comparison | revisions
ui/motif/menu.c file | annotate | diff | comparison | revisions
ui/motif/menu.h file | annotate | diff | comparison | revisions
ui/motif/objs.mk file | annotate | diff | comparison | revisions
ui/motif/range.c file | annotate | diff | comparison | revisions
ui/motif/range.h file | annotate | diff | comparison | revisions
ui/motif/stock.c file | annotate | diff | comparison | revisions
ui/motif/stock.h file | annotate | diff | comparison | revisions
ui/motif/text.c file | annotate | diff | comparison | revisions
ui/motif/text.h file | annotate | diff | comparison | revisions
ui/motif/toolbar.c file | annotate | diff | comparison | revisions
ui/motif/toolbar.h file | annotate | diff | comparison | revisions
ui/motif/toolkit.c file | annotate | diff | comparison | revisions
ui/motif/toolkit.h file | annotate | diff | comparison | revisions
ui/motif/tree.c file | annotate | diff | comparison | revisions
ui/motif/tree.h 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/label.h 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/objs.mk file | annotate | diff | comparison | revisions
ui/qt/qt4.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/window.cpp file | annotate | diff | comparison | revisions
ui/qt/window.h file | annotate | diff | comparison | revisions
ui/ui/button.h 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/dnd.h file | annotate | diff | comparison | revisions
ui/ui/entry.h file | annotate | diff | comparison | revisions
ui/ui/graphics.h file | annotate | diff | comparison | revisions
ui/ui/image.h file | annotate | diff | comparison | revisions
ui/ui/menu.h file | annotate | diff | comparison | revisions
ui/ui/properties.h file | annotate | diff | comparison | revisions
ui/ui/range.h file | annotate | diff | comparison | revisions
ui/ui/stock.h file | annotate | diff | comparison | revisions
ui/ui/text.h file | annotate | diff | comparison | revisions
ui/ui/toolbar.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
ui/ui/ui.h file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
ui/winui/App.idl file | annotate | diff | comparison | revisions
ui/winui/App.xaml file | annotate | diff | comparison | revisions
ui/winui/App.xaml.cpp file | annotate | diff | comparison | revisions
ui/winui/App.xaml.h file | annotate | diff | comparison | revisions
ui/winui/Assets/LockScreenLogo.scale-200.png file | annotate | diff | comparison | revisions
ui/winui/Assets/SplashScreen.scale-200.png file | annotate | diff | comparison | revisions
ui/winui/Assets/Square150x150Logo.scale-200.png file | annotate | diff | comparison | revisions
ui/winui/Assets/Square44x44Logo.scale-200.png file | annotate | diff | comparison | revisions
ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png file | annotate | diff | comparison | revisions
ui/winui/Assets/StoreLogo.png file | annotate | diff | comparison | revisions
ui/winui/Assets/Wide310x150Logo.scale-200.png file | annotate | diff | comparison | revisions
ui/winui/MainWindow.idl file | annotate | diff | comparison | revisions
ui/winui/MainWindow.xaml file | annotate | diff | comparison | revisions
ui/winui/MainWindow.xaml.cpp file | annotate | diff | comparison | revisions
ui/winui/MainWindow.xaml.h file | annotate | diff | comparison | revisions
ui/winui/Package.appxmanifest file | annotate | diff | comparison | revisions
ui/winui/app.manifest file | annotate | diff | comparison | revisions
ui/winui/appmenu.cpp file | annotate | diff | comparison | revisions
ui/winui/appmenu.h file | annotate | diff | comparison | revisions
ui/winui/button.cpp file | annotate | diff | comparison | revisions
ui/winui/button.h file | annotate | diff | comparison | revisions
ui/winui/commandbar.cpp file | annotate | diff | comparison | revisions
ui/winui/commandbar.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/dnd.cpp file | annotate | diff | comparison | revisions
ui/winui/dnd.h file | annotate | diff | comparison | revisions
ui/winui/icons.cpp file | annotate | diff | comparison | revisions
ui/winui/icons.h file | annotate | diff | comparison | revisions
ui/winui/label.cpp file | annotate | diff | comparison | revisions
ui/winui/label.h file | annotate | diff | comparison | revisions
ui/winui/list.cpp file | annotate | diff | comparison | revisions
ui/winui/list.h file | annotate | diff | comparison | revisions
ui/winui/packages.config file | annotate | diff | comparison | revisions
ui/winui/pch.cpp file | annotate | diff | comparison | revisions
ui/winui/pch.h file | annotate | diff | comparison | revisions
ui/winui/readme.txt file | annotate | diff | comparison | revisions
ui/winui/stock.cpp file | annotate | diff | comparison | revisions
ui/winui/stock.h file | annotate | diff | comparison | revisions
ui/winui/table.cpp file | annotate | diff | comparison | revisions
ui/winui/table.h file | annotate | diff | comparison | revisions
ui/winui/text.cpp file | annotate | diff | comparison | revisions
ui/winui/text.h file | annotate | diff | comparison | revisions
ui/winui/toolkit.cpp file | annotate | diff | comparison | revisions
ui/winui/toolkit.h file | annotate | diff | comparison | revisions
ui/winui/util.cpp file | annotate | diff | comparison | revisions
ui/winui/util.h file | annotate | diff | comparison | revisions
ui/winui/window.cpp file | annotate | diff | comparison | revisions
ui/winui/window.h file | annotate | diff | comparison | revisions
ui/winui/winui.vcxproj file | annotate | diff | comparison | revisions
ui/winui/winui.vcxproj.filters file | annotate | diff | comparison | revisions
ui/wpf/Makefile file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Application.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Container.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Controls.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/DrawingArea.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/MainToolBar.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Menu.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Properties/AssemblyInfo.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/TextArea.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Toolkit.cs file | annotate | diff | comparison | revisions
ui/wpf/UIcore/UIcore.csproj file | annotate | diff | comparison | revisions
ui/wpf/UIcore/Window.cs file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper.sln file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper.v12.suo file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/AssemblyInfo.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/ReadMe.txt file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/Stdafx.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/Stdafx.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj.filters file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj.user file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/app.ico file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/app.rc file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/container.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/container.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/controls.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/controls.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/graphics.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/graphics.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/menu.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/menu.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/resource.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/toolbar.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/toolbar.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/toolkit.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/toolkit.h file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/window.cpp file | annotate | diff | comparison | revisions
ui/wpf/UIwrapper/UIwrapper/window.h file | annotate | diff | comparison | revisions
ui/wpf/button.c file | annotate | diff | comparison | revisions
ui/wpf/button.h file | annotate | diff | comparison | revisions
ui/wpf/container.c file | annotate | diff | comparison | revisions
ui/wpf/container.h file | annotate | diff | comparison | revisions
ui/wpf/graphics.c file | annotate | diff | comparison | revisions
ui/wpf/graphics.h file | annotate | diff | comparison | revisions
ui/wpf/label.c file | annotate | diff | comparison | revisions
ui/wpf/label.h file | annotate | diff | comparison | revisions
ui/wpf/menu.c file | annotate | diff | comparison | revisions
ui/wpf/menu.h file | annotate | diff | comparison | revisions
ui/wpf/objs.mk file | annotate | diff | comparison | revisions
ui/wpf/text.c file | annotate | diff | comparison | revisions
ui/wpf/text.h file | annotate | diff | comparison | revisions
ui/wpf/toolbar.c file | annotate | diff | comparison | revisions
ui/wpf/toolbar.h file | annotate | diff | comparison | revisions
ui/wpf/toolkit.c file | annotate | diff | comparison | revisions
ui/wpf/toolkit.h file | annotate | diff | comparison | revisions
ui/wpf/window.c file | annotate | diff | comparison | revisions
ui/wpf/window.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,15 @@
+relre:^build/.*$
+relre:^config.mk$
+relre:^core$
+relre:^make/vs/.vs/.*
+relre:^make/vs/packages/.*
+relre:^make/vs/.*vcxproj\.user
+relre:^ui/winui/winui\.vcxproj\.user
+relre:^ui/winui/Generated Files
+relre:^ui/wpf/UIcore/obj
+relre:^ui/wpf/UIwrapper/.vs
+relre:^ui/wpf/UIwrapper/UIwrapper.VC
+relre:^ui/wpf/UIwrapper/UIwrapper/Debug
+relre:^ui/wpf/UIwrapper/UIwrapper/Release
+relre:^ui/wpf/UIwrapper/UIwrapper/x64
+relre:^ui/wpf/UIwrapper/ipch
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,38 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+all: config.mk
+	$(MAKE) -f make/Makefile.mk
+
+config.mk:
+	./configure
+
+clean: FORCE
+	rm -fR build/*
+
+FORCE:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,45 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+BUILD_ROOT = ..
+include ../config.mk
+
+CFLAGS += -I../ui/ -I../ucx
+
+SRC = main.c
+
+OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
+
+all: ../build/bin/mk12
+
+../build/bin/mk12: $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
+	$(LD) -o ../build/bin/mk12$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS)
+
+../build/application/%.$(OBJ_EXT): %.c
+	$(CC) ../ucx $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/main.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,110 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ui/ui.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+
+typedef struct {
+    UiText *text;
+} MyDocument;
+
+MyDocument *doc1;
+MyDocument *doc2;
+
+
+void action_menu(UiEvent *event, void *userdata) {
+    printf("action_menu: %s\n", (char*)userdata);
+}
+
+void action_button(UiEvent *event, void *userdata) {
+    printf("button test\n");
+    MyDocument *doc = event->document;
+    if(!doc) {
+        printf("no document\n");
+        return;
+    }
+    
+    char *text = doc->text->get(doc->text);
+    printf("text: {\n%s\n}\n", text);
+}
+
+void action_switch(UiEvent *event, void *userdata) {
+    if(event->document == doc1) {
+        ui_set_document(event->obj, doc2);
+    } else {
+        ui_set_document(event->obj, doc1);
+    }
+}
+
+
+MyDocument* create_doc(void) {
+    MyDocument *doc = ui_document_new(sizeof(MyDocument));
+    UiContext *docctx = ui_document_context(doc);
+    doc->text = ui_text_new(docctx, "text");
+    return doc;
+}
+
+void application_startup(UiEvent *event, void *data) {
+    
+    UiObject *obj = ui_window("Test", NULL);
+    ui_textarea_nv(obj, "text");
+    ui_button(obj, "Test", action_button, NULL);
+    ui_button(obj, "Switch Document", action_switch, NULL);
+    
+    doc1 = create_doc();
+    doc2 = create_doc();
+    
+    ui_attach_document(obj->ctx, doc1);
+    
+    ui_show(obj);
+}
+
+int main(int argc, char** argv) { 
+    ui_init("app1", argc, argv);
+    ui_onstartup(application_startup, NULL);
+    
+    // menu
+    ui_menu("_File");
+    ui_menuitem("_Hello", action_menu, NULL);
+    ui_submenu("Submenu1");
+    ui_submenu("Submenu2");
+    ui_menuitem("item2", action_menu, NULL);
+    ui_submenu_end();
+    ui_menuitem("item3", action_menu, NULL);
+    ui_submenu_end();
+    ui_menuitem("item4", action_menu, NULL);
+
+    
+    ui_main();
+    
+    return (EXIT_SUCCESS);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,818 @@
+#!/bin/sh
+
+
+PREFIX=/usr
+EPREFIX=$PREFIX
+
+BINDIR=
+SBINDIR=
+LIBDIR=
+LIBEXECDIR=
+DATADIR=
+SYSCONFDIR=
+SHAREDSTATEDIR=
+LOCALSTATEDIR=
+INCLUDEDIR=
+INFODIR=
+MANDIR=
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+	echo "Cannot create tmp dir"
+	echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+
+# 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]
+  --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]
+
+Options:
+  --toolkit=(gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+
+__EOF__
+}
+
+#
+# parse arguments 
+#
+for ARG in $@
+do
+    case "$ARG" in
+		"--prefix="*)         PREFIX=${ARG#--prefix=} ;;
+		"--exec-prefix="*)    EPREFIX=${ARG#--exec-prefix=} ;;
+		"--bindir="*)         BINDIR=${ARG#----bindir=} ;;
+		"--sbindir="*)        SBINDIR=${ARG#--sbindir=} ;;
+		"--libdir="*)         LIBDIR=${ARG#--libdir=} ;;
+		"--libexecdir="*)     LIBEXECDIR=${ARG#--libexecdir=} ;;
+		"--datadir="*)        DATADIR=${ARG#--datadir=} ;;
+		"--sysconfdir="*)     SYSCONFDIR=${ARG#--sysconfdir=} ;;
+		"--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;;
+		"--localstatedir="*)  LOCALSTATEDIR=${ARG#--localstatedir=} ;;
+		"--includedir="*)     INCLUDEDIR=${ARG#--includedir=} ;;
+		"--infodir="*)        INFODIR=${ARG#--infodir=} ;;
+		"--mandir"*)          MANDIR=${ARG#--mandir} ;;
+		"--help"*) printhelp; exit 1 ;;
+    	"--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+		"-"*) echo "unknown option: $ARG"; exit 1 ;;
+	esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+	BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+	SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+	LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+	LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+	DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+	SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+	SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+	LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+	INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+	INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+	MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+	PLATFORM_NAME=$p
+	break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+
+PREFIX=$PREFIX
+EPREFIX=$EPREFIX
+
+BINDIR=$BINDIR
+SBINDIR=$SBINDIR
+LIBDIR=$LIBDIR
+LIBEXECDIR=$LIBEXECDIR
+DATADIR=$DATADIR
+SYSCONFDIR=$SYSCONFDIR
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+LOCALSTATEDIR=$LOCALSTATEDIR
+INCLUDEDIR=$INCLUDEDIR
+INFODIR=$INFODIR
+MANDIR=$MANDIR
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+dependency_qt4()
+{
+    printf "checking for qt4... "
+    # dependency qt4 
+    while true
+    do
+        qmake-qt4 -o - /dev/null | grep DEFINES\  > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `qmake-qt4 -o - /dev/null | grep DEFINES\ `"
+        else
+            break
+        fi
+        qmake-qt4 -o - /dev/null | grep INCPATH\  > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `qmake-qt4 -o - /dev/null | grep INCPATH\ `"
+        else
+            break
+        fi
+         > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS ``"
+        else
+            break
+        fi
+        which qmake-qt4 > /dev/null
+        if [ $? -ne 0 ]; then
+        	break
+        fi
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_gtk2legacy()
+{
+    printf "checking for gtk2legacy... "
+    # dependency gtk2legacy 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+        	break
+        fi
+		$PKG_CONFIG gtk+-2.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-2.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-2.0`"
+        CFLAGS="$CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_qt5()
+{
+    printf "checking for qt5... "
+    # dependency qt5 
+    while true
+    do
+        qmake-qt5 -o - /dev/null | grep DEFINES\  > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `qmake-qt5 -o - /dev/null | grep DEFINES\ `"
+        else
+            break
+        fi
+        qmake-qt5 -o - /dev/null | grep INCPATH\  > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `qmake-qt5 -o - /dev/null | grep INCPATH\ `"
+        else
+            break
+        fi
+         > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS ``"
+        else
+            break
+        fi
+        which qmake-qt5 > /dev/null
+        if [ $? -ne 0 ]; then
+        	break
+        fi
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_gtk2()
+{
+    printf "checking for gtk2... "
+    # dependency gtk2 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+        	break
+        fi
+		$PKG_CONFIG gtk+-2.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-2.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-2.0`"
+        CFLAGS="$CFLAGS -DUI_GTK2"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+        pkg-config --atleast-version=2.20 gtk+-2.0 > /dev/null
+        if [ $? -ne 0 ]; then
+        	break
+        fi
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_gtk3()
+{
+    printf "checking for gtk3... "
+    # dependency gtk3 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+        	break
+        fi
+		$PKG_CONFIG gtk+-3.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-3.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-3.0`"
+        CFLAGS="$CFLAGS -DUI_GTK3"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_gtk4()
+{
+    printf "checking for gtk4... "
+    # dependency gtk4 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+        	break
+        fi
+		$PKG_CONFIG gtk+-4.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-4.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-4.0`"
+        CFLAGS="$CFLAGS -DUI_GTK3"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_motif()
+{
+    printf "checking for motif... "
+    # dependency motif platform="bsd"
+    while true
+    do
+    	if isnotplatform "bsd"; then
+            break
+        fi
+        CFLAGS="$CFLAGS -DUI_MOTIF -I/usr/local/include/X11"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+		echo yes
+        return 0
+    done
+	
+    # dependency motif 
+    while true
+    do
+        CFLAGS="$CFLAGS -DUI_MOTIF"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_winui()
+{
+    printf "checking for winui... "
+    # dependency winui platform="windows"
+    while true
+    do
+    	if isnotplatform "windows"; then
+            break
+        fi
+        CFLAGS="$CFLAGS -DUI_WINUI"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+dependency_cocoa()
+{
+    printf "checking for cocoa... "
+    # dependency cocoa platform="macos"
+    while true
+    do
+    	if isnotplatform "macos"; then
+            break
+        fi
+        CFLAGS="$CFLAGS -DUI_COCOA"    
+        LDFLAGS="$LDFLAGS -lobjc -framework Cocoa"    
+		echo yes
+        return 0
+    done
+	
+	echo no
+	return 1
+}
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# general dependencies
+CFLAGS=
+LDFLAGS=
+while true
+do
+    if isnotplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+		cat >> $TEMP_DIR/make.mk << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
+PACKAGE_SCRIPT = package_osx.sh
+
+__EOF__
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    if isnotplatform "unix"; then
+        break
+    fi
+    if isplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+		cat >> $TEMP_DIR/make.mk << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
+PACKAGE_SCRIPT = package_unix.sh
+
+__EOF__
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    if isnotplatform "bsd"; then
+        break
+    fi
+    while true
+    do
+        
+        CFLAGS="$CFLAGS -I/usr/local/include"    
+        LDFLAGS="$LDFLAGS -L/usr/local/lib"    
+        
+        break
+    done
+    
+    break
+done
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# OPTION VALUES
+#
+checkopt_toolkit_gtk4()
+{
+	VERR=0
+	dependency_gtk4
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_gtk3()
+{
+	VERR=0
+	dependency_gtk3
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_gtk2()
+{
+	VERR=0
+	dependency_gtk2
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_gtk2legacy()
+{
+	VERR=0
+	dependency_gtk2legacy
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_gdk.o
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_qt5()
+{
+	VERR=0
+	dependency_qt5
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = qt
+LD = $(CXX)
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_qt4()
+{
+	VERR=0
+	dependency_qt4
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = qt
+LD = $(CXX)
+
+__EOF__
+	return 0
+}
+checkopt_toolkit_motif()
+{
+	VERR=0
+	dependency_motif
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = motif
+
+__EOF__
+	return 0
+}
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+# Target: tk
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+
+# Features
+
+# Option: --toolkit
+if [ -z $OPT_TOOLKIT ]; then
+	SAVED_ERROR=$ERROR
+	SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+	ERROR=0
+	while true
+	do
+		if isplatform "windows"; then
+		checkopt_toolkit_winui
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: winui" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		fi
+		if isplatform "macos"; then
+		checkopt_toolkit_cocoa
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: cocoa" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		fi
+		checkopt_toolkit_gtk3
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: gtk3" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		checkopt_toolkit_qt5
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: qt5" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		checkopt_toolkit_gtk2
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: gtk2" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		checkopt_toolkit_qt4
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: qt4" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		checkopt_toolkit_motif
+		if [ $? -eq 0 ]; then
+			echo "  toolkit: motif" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		break
+	done
+	if [ $ERROR -ne 0 ]; then
+		SAVED_ERROR=1
+	fi
+	ERROR=$SAVED_ERROR
+	DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+	if false; then
+		false
+	elif [ $OPT_TOOLKIT = "gtk4" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_gtk4
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "gtk3" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_gtk3
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "gtk2" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_gtk2
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "gtk2legacy" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_gtk2legacy
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "qt5" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_qt5
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "qt4" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_qt4
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	elif [ $OPT_TOOLKIT = "motif" ]; then
+		echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+		checkopt_toolkit_motif
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	fi
+fi
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "TK_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "TK_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "TK_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+if [ $ERROR -ne 0 ]; then
+	echo
+	echo "Error: Unresolved dependencies"
+	echo $DEPENDENCIES_FAILED
+	rm -Rf $TEMP_DIR
+	exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "Options:"
+cat $TEMP_DIR/options
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/Makefile.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,54 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2023 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.
+#
+
+# this makefile is invoked from the build root directory
+
+BUILD_ROOT = ./
+include config.mk
+
+BUILD_DIRS = build/bin build/lib
+BUILD_DIRS += build/application build/ucx
+BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
+
+all: $(BUILD_DIRS) ucx ui application
+	make/$(PACKAGE_SCRIPT)
+
+$(BUILD_DIRS):
+	mkdir -p $@
+
+ui: ucx FORCE
+	cd ui; $(MAKE) all
+
+ucx: FORCE
+	cd ucx; $(MAKE) all
+
+application: ui FORCE
+	cd application; $(MAKE)
+
+FORCE:
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/clang.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,9 @@
+#
+# clang toolchain config
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/configure.vm	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,615 @@
+#!/bin/sh
+
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.name}=`${var.value}`
+#else
+${var.name}=${var.value}
+#end
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=/usr
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$PREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=
+#end
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+	echo "Cannot create tmp dir"
+	echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.isDefault()} )
+${feature.getVarName()}=on
+#end
+#end
+
+# 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]
+  --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]
+
+#if( $options.size() > 0 )
+Options:
+#foreach( $opt in $options )
+  --${opt.getArgument()}=${opt.getValuesString()}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+#if( $feature.default )
+  --disable-${feature.arg}
+#else
+  --enable-${feature.arg}
+#end
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments 
+#
+#set( $D = '$' )
+for ARG in $@
+do
+    case "$ARG" in
+		"--prefix="*)         PREFIX=${D}{ARG#--prefix=} ;;
+		"--exec-prefix="*)    EPREFIX=${D}{ARG#--exec-prefix=} ;;
+		"--bindir="*)         BINDIR=${D}{ARG#----bindir=} ;;
+		"--sbindir="*)        SBINDIR=${D}{ARG#--sbindir=} ;;
+		"--libdir="*)         LIBDIR=${D}{ARG#--libdir=} ;;
+		"--libexecdir="*)     LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
+		"--datadir="*)        DATADIR=${D}{ARG#--datadir=} ;;
+		"--sysconfdir="*)     SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
+		"--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
+		"--localstatedir="*)  LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
+		"--includedir="*)     INCLUDEDIR=${D}{ARG#--includedir=} ;;
+		"--infodir="*)        INFODIR=${D}{ARG#--infodir=} ;;
+		"--mandir"*)          MANDIR=${D}{ARG#--mandir} ;;
+		"--help"*) printhelp; exit 1 ;;
+	#foreach( $opt in $options )
+    	"--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
+    #end
+	#foreach( $feature in $features )
+		"--enable-${feature.arg}") ${feature.getVarName()}=on ;;
+		"--disable-${feature.arg}") unset ${feature.getVarName()} ;;
+	#end
+		"-"*) echo "unknown option: $ARG"; exit 1 ;;
+	esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+	BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+	SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+	LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+	LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+	DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+	SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+	SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+	LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+	INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+	INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+	MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+	PLATFORM_NAME=$p
+	break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+#foreach( $var in $vars )
+${var.name}=$${var.name}
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=$PREFIX
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$EPREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=$BINDIR
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=$SBINDIR
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=$LIBDIR
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=$LIBEXECDIR
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=$DATADIR
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=$SYSCONFDIR
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=$LOCALSTATEDIR
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=$INCLUDEDIR
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=$INFODIR
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=$MANDIR
+#end
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+#foreach( $dependency in $namedDependencies )
+dependency_${dependency.name}()
+{
+    printf "checking for ${dependency.name}... "
+    #foreach( $sub in $dependency.getSubdependencies() )
+    # dependency $sub.name $sub.getPlatformString()
+    while true
+    do
+    	#if( $sub.platform )
+    	if isnotplatform "${sub.platform}"; then
+            break
+        fi
+    	#end
+		#foreach( $not in $sub.getNotList() )
+		if isplatform "${not}"; then
+            break
+        fi
+		#end
+        #if( $sub.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+        	break
+        fi
+        #end
+        #foreach( $pkg in $sub.pkgconfig )
+		$PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        #foreach( $flags in $sub.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -eq 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+        #foreach( $test in $sub.tests )
+        $test > /dev/null
+        if [ $? -ne 0 ]; then
+        	break
+        fi
+        #end
+		#if ( $sub.make.length() > 0 )
+		cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name		
+$sub.make
+__EOF__
+        #end
+		echo yes
+        return 0
+    done
+	
+	#end
+	echo no
+	return 1
+}
+#end
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# general dependencies
+CFLAGS=
+LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+	#if( $dependency.platform )
+    if isnotplatform "${dependency.platform}"; then
+        break
+    fi
+    #end
+	#foreach( $not in $dependency.getNotList() )
+    if isplatform "${not}"; then
+        break
+    fi
+	#end
+    while true
+    do
+        #if( $dependency.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+            ERROR=1
+            break
+        fi
+        #end
+        #foreach( $pkg in $dependency.pkgconfig )
+        printf "checking for pkg-config package $pkg.getPkgConfigParam()... "
+		$PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ]; then
+            echo no
+            ERROR=1
+            break
+        fi
+        echo yes
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        
+        #foreach( $flags in $dependency.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -ne 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            ERROR=1
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+		#if ( $dependency.make.length() > 0 )
+		cat >> $TEMP_DIR/make.mk << __EOF__
+$dependency.make
+__EOF__
+        #end
+        
+        break
+    done
+    
+    break
+done
+#end
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+	VERR=0
+	#foreach( $dep in $val.dependencies )
+	dependency_$dep
+	if [ $? -ne 0 ]; then
+		VERR=1
+	fi
+	#end
+	if [ $VERR -ne 0 ]; then
+		return 1
+	fi
+	#foreach( $def in $val.defines )
+		CFLAGS="$CFLAGS ${def.toFlags()}"
+	#end
+	#if( $val.hasMake() )
+	cat >> $TEMP_DIR/make.mk << __EOF__
+$val.make
+__EOF__
+	#end
+	return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+#foreach( $target in $targets )
+#if ( $target.name )
+# Target: $target.name
+#else
+# Target
+#end
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+dependency_$dependency
+if [ $? -ne 0 ]; then
+	DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+	ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+	# check dependency
+	dependency_$dependency
+	if [ $? -ne 0 ]; then
+		# "auto" features can fail and are just disabled in this case
+		if [ $${feature.getVarName()} != "auto" ]; then
+			DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+			ERROR=1
+		fi
+	fi
+#end
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z ${D}${opt.getVarName()} ]; then
+	SAVED_ERROR=$ERROR
+	SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+	ERROR=0
+	while true
+	do
+		#foreach( $optdef in $opt.defaults )
+		#if( $optdef.platform )
+		if isplatform "$optdef.platform"; then
+		#end
+		$optdef.func
+		if [ $? -eq 0 ]; then
+			echo "  ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options
+			ERROR=0
+			break
+		fi
+		#if( $optdef.platform )
+		fi
+		#end
+		#end
+		break
+	done
+	if [ $ERROR -ne 0 ]; then
+		SAVED_ERROR=1
+	fi
+	ERROR=$SAVED_ERROR
+	DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+	if false; then
+		false
+	#foreach( $optval in $opt.values )
+	elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then
+		echo "  ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options
+		$optval.func
+		if [ $? -ne 0 ]; then
+			ERROR=1
+		fi
+	#end
+	fi
+fi
+#end
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "${target.getCFlags()}  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#end
+if [ $ERROR -ne 0 ]; then
+	echo
+	echo "Error: Unresolved dependencies"
+	echo $DEPENDENCIES_FAILED
+	rm -Rf $TEMP_DIR
+	exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+#if ( $options.size() > 0 )
+echo "Options:"
+cat $TEMP_DIR/options
+#end
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/gcc.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,10 @@
+#
+# gcc toolchain config
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/mingw.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,46 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+MSBUILD = MSBuild.exe
+
+CFLAGS  = -std=gnu99 -c -O2 -m64
+COFLAGS = -o
+LDFLAGS = 
+LOFLAGS = -o
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT = .exe
+
+PACKAGE_SCRIPT = package_windows.sh
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/osx.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,43 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS  += -std=gnu99 -g -I/usr/include/libxml2
+LDFLAGS += -lxml2 -lz -lpthread -licucore -lm
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT =
+
+PACKAGE_SCRIPT = package_osx.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/package_osx.sh	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# create .app
+rm -Rf build/mk12.app
+cp -R resource/template.app build/mk12.app
+
+mkdir -p build/mk12.app/Contents/MacOS/
+
+cp build/bin/mk12 build/mk12.app/Contents/MacOS/
+
+cp -R resource/locales build/mk12.app/Contents/Resources/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/package_unix.sh	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,2 @@
+#!/bin/sh
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/package_windows.sh	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,2 @@
+#!/bin/sh
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/project.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+	<dependency name="gtk4">
+		<pkgconfig>gtk+-4.0</pkgconfig>
+		<cflags>-DUI_GTK3</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
+	<dependency name="gtk3">
+		<pkgconfig>gtk+-3.0</pkgconfig>
+		<cflags>-DUI_GTK3</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
+	<dependency name="gtk2">
+		<test>pkg-config --atleast-version=2.20 gtk+-2.0</test>
+		<pkgconfig>gtk+-2.0</pkgconfig>
+		<cflags>-DUI_GTK2</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
+	<dependency name="gtk2legacy">
+		<pkgconfig>gtk+-2.0</pkgconfig>
+		<cflags>-DUI_GTK2 -DUI_GTK2LEGACY</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
+	<dependency name="winui" platform="windows">
+		<cflags>-DUI_WINUI</cflags>
+	</dependency>
+	<dependency name="qt4">
+		<test>which qmake-qt4</test>
+		<cflags type="exec">qmake-qt4 -o - /dev/null | grep DEFINES\ </cflags>
+		<cflags type="exec">qmake-qt4 -o - /dev/null | grep INCPATH\ </cflags>
+		<ldflags type="exec"><cflags type="exec">qmake-qt4 -o - /dev/null | grep LIBS\ </cflags></ldflags>
+	</dependency>
+	<dependency name="qt5">
+		<test>which qmake-qt5</test>
+		<cflags type="exec">qmake-qt5 -o - /dev/null | grep DEFINES\ </cflags>
+		<cflags type="exec">qmake-qt5 -o - /dev/null | grep INCPATH\ </cflags>
+		<ldflags type="exec"><cflags type="exec">qmake-qt5 -o - /dev/null | grep LIBS\ </cflags></ldflags>
+	</dependency>
+	<dependency name="cocoa" platform="macos">
+		<cflags>-DUI_COCOA</cflags>
+		<ldflags>-lobjc -framework Cocoa</ldflags>
+	</dependency>
+	
+	<dependency name="motif" platform="bsd">
+		<cflags>-DUI_MOTIF -I/usr/local/include/X11</cflags>
+		<ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+	</dependency>
+	
+	<dependency name="motif">
+		<cflags>-DUI_MOTIF</cflags>
+		<ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+	</dependency>
+	
+	<dependency platform="macos">
+		<make>OBJ_EXT = .o</make>
+		<make>LIB_EXT = .a</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>PACKAGE_SCRIPT = package_unix.sh</make>
+	</dependency>
+	
+	<dependency platform="bsd">
+		<cflags>-I/usr/local/include</cflags>
+		<ldflags>-L/usr/local/lib</ldflags>
+	</dependency>
+	
+	<target name="tk">
+		<option arg="toolkit">
+			<value str="gtk4">
+				<dependencies>gtk4</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_cairo.o</make>
+			</value>
+			<value str="gtk3">
+				<dependencies>gtk3</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_cairo.o</make>
+			</value>
+			<value str="gtk2">
+				<dependencies>gtk2</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_cairo.o</make>
+			</value>
+			<value str="gtk2legacy">
+				<dependencies>gtk2legacy</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_gdk.o</make>
+			</value>
+			<value str="qt5">
+				<dependencies>qt5</dependencies>
+				<make>TOOLKIT = qt</make>
+				<make>LD = $(CXX)</make>
+			</value>
+			<value str="qt4">
+				<dependencies>qt4</dependencies>
+				<make>TOOLKIT = qt</make>
+				<make>LD = $(CXX)</make>
+			</value>
+			<value str="motif">
+				<dependencies>motif</dependencies>
+				<make>TOOLKIT = motif</make>
+			</value>
+			<default value="winui" platform="windows" />
+			<default value="cocoa" platform="macos" />
+			<default value="gtk3" />
+			<default value="qt5" />
+			<default value="gtk2" />
+			<default value="qt4" />
+			<default value="motif" />
+		</option>
+	</target>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/suncc.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,10 @@
+#
+# suncc toolchain
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/toolchain.sh	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,181 @@
+#!/bin/sh
+#
+# toolchain detection
+#
+
+C_COMPILERS="cc gcc clang suncc"
+CPP_COMPILERS="CC g++ clang++ sunCC"
+unset CC_ARG_CHECKED
+unset TOOLCHAIN_DETECTION_ERROR
+unset TOOLCHAIN_NAME
+
+check_c_compiler()
+{
+	cat > $TEMP_DIR/test.c << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+	printf("clang\n");
+#elif defined(__GNUC__)
+	printf("gcc\n");
+#elif defined(__sun)
+	printf("suncc\n");
+#else
+	printf("unknown\n");
+#endif
+	return 0;
+}
+__EOF__
+	rm -f $TEMP_DIR/checkcc
+	$1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
+	
+	if [ $? -ne 0 ]; then
+		return 1
+	fi
+	return 0
+}
+
+check_cpp_compiler()
+{
+	cat > $TEMP_DIR/test.cpp << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+	std::cout << "clang" << std::endl;
+#elif defined(__GNUC__)
+	std::cout << "gcc" << std::endl;
+#elif defined(__sun)
+	std::cout << "suncc" << std::endl;
+#else
+	std::cout << "unknown" << std::endl;
+#endif
+	return 0;
+}
+__EOF__
+	rm -f $TEMP_DIR/checkcc
+	$1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
+	
+	if [ $? -ne 0 ]; then
+		return 1
+	fi
+	return 0
+}
+
+printf "detect C compiler... "
+
+for COMP in $C_COMPILERS
+do
+	check_c_compiler $COMP
+	if [ $? -ne 0 ]; then
+		if [ ! -z "$CC" ]; then
+			if [ $COMP = $CC ]; then
+				echo "$CC is not a working C Compiler"
+				TOOLCHAIN_DETECTION_ERROR="error"
+				break
+			fi
+		fi
+	else
+		TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+		USE_TOOLCHAIN=$TOOLCHAIN_NAME
+		if [ $COMP = "cc" ]; then
+			# we have found a working compiler, but in case
+			# the compiler is gcc or clang, we try to use
+			# these commands and not 'cc'
+			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+			if [ $TOOLCHAIN_NAME = "gcc" ]; then
+				check_c_compiler "gcc"
+				if [ $? -eq 0 ]; then
+					COMP=gcc
+					USE_TOOLCHAIN="gcc"
+				fi
+			fi
+			if [ $TOOLCHAIN_NAME = "clang" ]; then
+				check_c_compiler "clang"
+				if [ $? -eq 0 ]; then
+					COMP=clang
+					USE_TOOLCHAIN="clang"
+				fi
+			fi
+		fi
+		
+		TOOLCHAIN_NAME=$USE_TOOLCHAIN
+		TOOLCHAIN_CC=$COMP
+		echo $COMP
+		break
+	fi
+done
+if [ -z $TOOLCHAIN_CC ]; then
+	echo "not found"
+fi
+
+printf "detect C++ compiler... "
+
+for COMP in $CPP_COMPILERS
+do
+	check_cpp_compiler $COMP
+	if [ $? -ne 0 ]; then
+		if [ ! -z "$CXX" ]; then
+			if [ $COMP = $CXX ]; then
+				echo "$CC is not a working C++ Compiler"
+				TOOLCHAIN_DETECTION_ERROR="error"
+				break
+			fi
+		fi
+	else
+		if [ $COMP = "CC" ]; then
+			# we have found a working compiler, but in case
+			# the compiler is gcc or clang, we try to use
+			# these commands and not 'cc'
+			TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+			USE_TOOLCHAIN=$TOOLCHAIN_NAME
+			if [ $TOOLCHAIN_NAME = "gcc" ]; then
+				check_cpp_compiler "g++"
+				if [ $? -eq 0 ]; then
+				   COMP=g++
+				   USE_TOOLCHAIN="gcc"
+				fi
+			fi
+			if [ $TOOLCHAIN_NAME = "clang" ]; then
+				check_cpp_compiler "clang++"
+				if [ $? -eq 0 ]; then
+				   COMP=clang++
+				   USE_TOOLCHAIN="clang"
+				fi
+			fi
+		fi
+		
+		TOOLCHAIN_NAME=$USE_TOOLCHAIN
+		TOOLCHAIN_CXX=$COMP
+		echo $COMP
+		break
+	fi
+done
+if [ -z $TOOLCHAIN_CXX ]; then
+	echo "not found"
+fi
+
+TOOLCHAIN_LD=$TOOLCHAIN_CC
+
+if [ -z "$TOOLCHAIN_NAME" ]; then
+	TOOLCHAIN_DETECTION_ERROR="error"
+else
+	cat >> $TEMP_DIR/config.mk << __EOF__
+# toolchain
+__EOF__
+	echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
+	if [ ! -z "$TOOLCHAIN_CXX" ]; then
+		echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
+	fi
+	echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
+	echo >> $TEMP_DIR/config.mk
+	
+	cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
+	if [ $? -eq 0 ]; then 
+		echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
+	else
+		echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
+		echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
+	fi
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav.sln	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,85 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "uicommon", "uicommon\uicommon.vcxproj", "{8B88698E-C185-4383-99FE-0C34D6DEED2E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idav", "idav\idav.vcxproj", "{F975D652-779E-4945-BFC2-8C649C6F9938}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|ARM64 = Debug|ARM64
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|ARM64 = Release|ARM64
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.ActiveCfg = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.Build.0 = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.ActiveCfg = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.Build.0 = Debug|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.ActiveCfg = Debug|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.Build.0 = Debug|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.ActiveCfg = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.Build.0 = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.ActiveCfg = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32
+		{27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.ActiveCfg = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Build.0 = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Deploy.0 = Debug|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.ActiveCfg = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Build.0 = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Deploy.0 = Debug|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.ActiveCfg = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Build.0 = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Deploy.0 = Release|ARM64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.ActiveCfg = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Build.0 = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Deploy.0 = Release|x64
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.ActiveCfg = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Build.0 = Release|Win32
+		{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Deploy.0 = Release|Win32
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|ARM64.ActiveCfg = Debug|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|ARM64.Build.0 = Debug|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|x64.ActiveCfg = Debug|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|x64.Build.0 = Debug|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|x86.ActiveCfg = Debug|Win32
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Debug|x86.Build.0 = Debug|Win32
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|ARM64.ActiveCfg = Release|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|ARM64.Build.0 = Release|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|x64.ActiveCfg = Release|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|x64.Build.0 = Release|x64
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|x86.ActiveCfg = Release|Win32
+		{8B88698E-C185-4383-99FE-0C34D6DEED2E}.Release|x86.Build.0 = Release|Win32
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.ActiveCfg = Debug|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.Build.0 = Debug|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.ActiveCfg = Debug|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.Build.0 = Debug|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.ActiveCfg = Debug|Win32
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.Build.0 = Debug|Win32
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.ActiveCfg = Release|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.Build.0 = Release|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.ActiveCfg = Release|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.Build.0 = Release|x64
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.ActiveCfg = Release|Win32
+		{F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.Build.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {141CA624-F556-4BE7-9218-8D6EEAB95C95}
+	EndGlobalSection
+EndGlobal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/app.manifest	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="test.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. 
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 
+      
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+    </application>
+  </compatibility>
+  
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+</assembly>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/idav.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" />
+  <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" />
+  <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{F975D652-779E-4945-BFC2-8C649C6F9938}</ProjectGuid>
+    <RootNamespace>testapp</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
+    <WindowsPackageType>None</WindowsPackageType>
+    <AppxPackage>false</AppxPackage>
+    <ProjectName>idav</ProjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\idav\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">
+      <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="main.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" />
+    <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/idav.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/main.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,20 @@
+#ifdef _WIN32
+#include <Windows.h>
+#endif
+
+
+
+int idav_main(void) {
+	return 0;
+}
+
+#ifndef _WIN32
+int main(int argc, char** argv) {
+	return idav_main();
+}
+#else
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
+	return idav_main();
+}
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/main.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,2 @@
+#pragma once
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/packages.config	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />
+  <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />
+</packages>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/idav.exe.recipe	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project>
+  <ProjectOutputs>
+    <ProjectOutput>
+      <FullPath>C:\Users\Olaf\Projekte\idav\make\vs\x64\Debug\idav.exe</FullPath>
+    </ProjectOutput>
+  </ProjectOutputs>
+  <ContentFiles />
+  <SatelliteDlls />
+  <NonRecipeFileRefs />
+</Project>
\ No newline at end of file
Binary file make/vs/idav/x64/Debug/idav.ilk has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/idav.log	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,4 @@
+  main.c
+  idav.vcxproj -> C:\Users\Olaf\Projekte\idav\make\vs\x64\Debug\idav.exe
+  C:\Users\Olaf\Projekte\idav\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\..\..\runtimes\win10-x64\native\Microsoft.WindowsAppRuntime.Bootstrap.dll
+  1 Datei(en) kopiert
Binary file make/vs/idav/x64/Debug/idav.tlog/CL.command.1.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.tlog/CL.read.1.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.tlog/CL.write.1.tlog has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/idav.tlog/Cl.items.tlog	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1 @@
+C:\Users\Olaf\Projekte\idav\make\vs\idav\main.c;C:\Users\Olaf\Projekte\idav\make\vs\idav\x64\Debug\main.obj
Binary file make/vs/idav/x64/Debug/idav.tlog/CopyLocal.read.1u.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.tlog/CopyLocal.write.1u.tlog has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/idav.tlog/idav.lastbuildstate	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,2 @@
+PlatformToolSet=v143:VCToolArchitecture=Native64Bit:VCToolsVersion=14.37.32822:TargetPlatformVersion=10.0.22621.0:VcpkgTriplet=x64-windows:
+Debug|x64|C:\Users\Olaf\Projekte\idav\make\vs\|
Binary file make/vs/idav/x64/Debug/idav.tlog/link.command.1.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.tlog/link.read.1.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.tlog/link.write.1.tlog has changed
Binary file make/vs/idav/x64/Debug/idav.vcxproj.AssemblyReference.cache has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/idav.vcxproj.FileListAbsolute.txt	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,2 @@
+C:\Users\Olaf\Projekte\idav\make\vs\idav\x64\Debug\idav.vcxproj.AssemblyReference.cache
+C:\Users\Olaf\Projekte\idav\make\vs\idav\x64\Debug\idav.vcxproj.CopyComplete
Binary file make/vs/idav/x64/Debug/main.obj has changed
Binary file make/vs/idav/x64/Debug/vc143.idb has changed
Binary file make/vs/idav/x64/Debug/vc143.pdb has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/idav/x64/Debug/vcpkg.applocal.log	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/app.manifest	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="test.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. 
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 
+      
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+    </application>
+  </compatibility>
+  
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+</assembly>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/main.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,378 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 <Windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <ui/ui.h>
+
+typedef struct WindowData {
+    UiInteger* check;
+    UiInteger* toggle;
+    UiInteger* radio;
+    UiString* text;
+    UiString* password;
+    UiList* list;
+    UiString* t1;
+    UiString* t2;
+    UiString* t3;
+    UiString* path;
+    UiList* list2;
+    UiList* list3;
+    UiDouble* progress;
+    UiInteger* spinner;
+} WindowData;
+
+static UiIcon* folder_icon;
+
+void action1(UiEvent* event, void* data) {
+    char* action = data;
+    
+    WindowData* wdata = event->window;
+    int64_t is_checked = wdata->check->get(wdata->check);
+    int64_t radio = wdata->radio->get(wdata->radio);
+
+    printf("data: %s %d\n", data, is_checked);
+
+    double d = wdata->progress->get(wdata->progress);
+    wdata->progress->set(wdata->progress, d + 1);
+
+    int spinner_active = wdata->spinner->get(wdata->spinner);
+    wdata->spinner->set(wdata->spinner, !spinner_active);
+}
+
+void action_set_checkbox(UiEvent* event, void* data) {
+    char* action = data;
+
+    WindowData* wdata = event->window;
+    wdata->check->set(wdata->check, 1);
+}
+
+void action_onchange(UiEvent* event, void* data) {
+    printf("onchange: %d\n", event->intval);
+}
+
+void action_switch(UiEvent* event, void* data) {
+    printf("onchange: %d\n", event->intval);
+}
+
+void action_toolbar_button(UiEvent* event, void *data) {
+    printf("toolbar action\n");
+}
+
+
+void action_listselection_changed(UiEvent* event, void* data) {
+    printf("selection changed\n");
+    UiListSelection* sel = event->eventdata;
+    for (int i = 0; i < sel->count; i++) {
+        int row = sel->rows[i];
+        printf("row: %d\n", row);
+    }
+}
+
+void action_onactivate(UiEvent* event, void* Data) {
+    printf("activate\n");
+    UiListSelection* sel = event->eventdata;
+    for (int i = 0; i < sel->count; i++) {
+        int row = sel->rows[i];
+        printf("row: %d\n", row);
+    }
+}
+
+typedef struct TableData {
+    char* col1;
+    char* col2;
+    char* col3;
+} TableData;
+
+void* table_getvalue(void* data, int i) {
+    TableData* t = data;
+    switch (i) {
+    case 0: return folder_icon;
+    case 1: return t->col1;
+    case 2: return t->col2;
+    case 3: return t->col3;
+    }
+    return NULL;
+}
+
+void action_add(UiEvent* event, void* data) {
+    WindowData* wdata = event->window;
+    char* t1 = wdata->t1->get(wdata->t1);
+    char* t2 = wdata->t2->get(wdata->t2);
+    char* t3 = wdata->t3->get(wdata->t3);
+
+    TableData* tdat = malloc(sizeof(TableData));
+    tdat->col1 = _strdup(t1);
+    tdat->col2 = _strdup(t2);
+    tdat->col3 = _strdup(t3);
+    ui_list_append(wdata->list2, tdat);
+    wdata->list2->update(wdata->list2, 0);
+
+}
+
+void action_breadcrumb(UiEvent* event, void* data) {
+    int i = event->intval;
+    char* c = event->eventdata;
+    printf("index: %d\n", i);
+}
+
+void dragstart(UiEvent* event, void* data) {
+    UiListDnd* ldnd = event->eventdata;
+    ui_selection_settext(ldnd->dnd, "Hello World!", -1);
+}
+
+void dragcomplete(UiEvent* event, void* data) {
+
+}
+
+void dragover(UiEvent* event, void* data) {
+
+}
+
+void drop(UiEvent* event, void* data) {
+
+}
+
+void application_startup(UiEvent* event, void* data) {
+    UiObject* obj = ui_window("Test", NULL);
+    WindowData* wdata = ui_malloc(obj->ctx, sizeof(WindowData));
+    obj->window = wdata;
+    wdata->check = ui_int_new(obj->ctx, "check");
+    wdata->toggle = ui_int_new(obj->ctx, "toggle");
+    wdata->radio = ui_int_new(obj->ctx, "radio");
+    wdata->text = ui_string_new(obj->ctx, "text");
+    wdata->password = ui_string_new(obj->ctx, "password");
+    wdata->list = ui_list_new(obj->ctx, "list");
+    wdata->list2 = ui_list_new(obj->ctx, "list2");
+    wdata->list3 = ui_list_new(obj->ctx, "list3");
+    wdata->t1 = ui_string_new(obj->ctx, "t1");
+    wdata->t2 = ui_string_new(obj->ctx, "t2");
+    wdata->t3 = ui_string_new(obj->ctx, "t3");
+    wdata->path = ui_string_new(obj->ctx, "path");
+    wdata->progress = ui_double_new(obj->ctx, "progress");
+    wdata->spinner = ui_int_new(obj->ctx, "spinner");
+
+    ui_list_append(wdata->list, "Hello");
+    ui_list_append(wdata->list, "World");
+    ui_list_append(wdata->list, "Item3");
+    ui_list_append(wdata->list, "Item4");
+    ui_list_append(wdata->list, "Item5");
+    ui_list_append(wdata->list, "Item6");
+
+    ui_list_append(wdata->list3, "usr");
+    ui_list_append(wdata->list3, "share");
+    ui_list_append(wdata->list3, "test");
+    ui_list_append(wdata->list3, "dir");
+
+    //folder_icon = ui_icon("Folder", 32);
+    folder_icon = ui_foldericon(16);
+
+    TableData* td1 = malloc(sizeof(TableData));
+    TableData* td2 = malloc(sizeof(TableData));
+    TableData* td3 = malloc(sizeof(TableData));
+    TableData* td4 = malloc(sizeof(TableData));
+    TableData* td5 = malloc(sizeof(TableData));
+    TableData* td6 = malloc(sizeof(TableData));
+    td1->col1 = "a1";
+    td1->col2 = "b1";
+    td1->col3 = "c1";
+    td2->col1 = "a2";
+    td2->col2 = "b2";
+    td2->col3 = "b3";
+    td3->col1 = "a3";
+    td3->col2 = "b3";
+    td3->col3 = "c3";
+    td4->col1 = "a3";
+    td4->col2 = "b3";
+    td4->col3 = "c3";
+    td5->col1 = "a3";
+    td5->col2 = "b3";
+    td5->col3 = "c3";
+    td6->col1 = "a3";
+    td6->col2 = "b3";
+    td6->col3 = "c3";
+
+    ui_list_append(wdata->list2, td1);
+    ui_list_append(wdata->list2, td2);
+    ui_list_append(wdata->list2, td3);
+    ui_list_append(wdata->list2, td4);
+    ui_list_append(wdata->list2, td5);
+    ui_list_append(wdata->list2, td6);
+
+    ui_scrolledwindow0(obj) {
+        ui_grid(obj, .margin = 10, .columnspacing = 5, .rowspacing = 20) {
+            ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+            ui_button(obj, .label = "Button2", .onclick = action1, .onclickdata = "action2");
+            ui_button(obj, .label = "Button3", .onclick = action1, .onclickdata = "action3", .hexpand = true);
+            ui_newline(obj);
+
+            ui_button(obj, .label = "Button4", .onclick = action1, .onclickdata = "action4");
+            ui_button(obj, .label = "Button5", .onclick = action1, .onclickdata = "action5", .colspan = 2);
+            ui_newline(obj);
+
+            ui_button(obj, .label = "Very Long Button Label Text ____________ Test", .onclick = action_set_checkbox);
+            ui_newline(obj);
+
+            ui_checkbox(obj, .label = "Option 1", .value = wdata->check, .onchange = action_onchange);
+            ui_togglebutton(obj, .label = "Option 2", .value = wdata->toggle);
+            ui_newline(obj);
+
+            ui_label(obj, .label = "Progress");
+            ui_progressspinner(obj, .value = wdata->spinner);
+            ui_newline(obj);
+            
+            ui_hbox(obj, .colspan = 3) {
+                ui_radiobutton(obj, .label = "Radio 1", .value = wdata->radio);
+                ui_radiobutton(obj, .label = "Radio 2", .value = wdata->radio);
+                ui_radiobutton(obj, .label = "Radio 3", .value = wdata->radio);
+            }
+            ui_newline(obj);
+            ui_radiobutton(obj, .label = "Radio 4", .value = wdata->radio);
+            ui_switch(obj, .label = "test", .onchange = action_switch);
+            ui_newline(obj);
+
+            //ui_breadcrumbbar(obj, .list = wdata->list3, .onactivate=action_breadcrumb);
+            ui_textfield(obj, .varname = "newtext");
+            ui_path_textfield(obj, .colspan = 2, .value=wdata->path, .onactivate = action_breadcrumb);
+            ui_newline(obj);
+            wdata->path->set(wdata->path, "/usr/path/test");
+            
+            ui_textfield(obj, .value = wdata->text);
+            ui_passwordfield(obj, .value = wdata->password);
+            ui_newline(obj);
+
+            ui_frame(obj, .label = "Test", .colspan = 3) {
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+            }
+            ui_newline(obj);
+            
+            ui_expander(obj, .label = "Expand", .colspan = 3, .margin = 10, .spacing = 5, .isexpanded = false) {
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");
+            }
+            ui_newline(obj);
+
+            ui_combobox(obj, .list = wdata->list, .onselection= action_listselection_changed, .onactivate= action_onactivate);
+            ui_newline(obj);
+
+            ui_tabview(obj, .colspan = 3, .vexpand = true, .hexpand = true, .tabview = UI_TABVIEW_NAVIGATION_SIDE) {
+                ui_tab(obj, "Tab 1") {
+                    ui_button(obj, .label = "Tab 1 Button");
+                }
+                ui_tab(obj, "Tab 2") {
+                    ui_button(obj, .label = "Tab 2 Button");
+                }
+                ui_tab(obj, "Tab 3") {
+
+                }
+            }
+            ui_newline(obj);
+
+            ui_label(obj, .label = "Test Label");
+            ui_progressbar(obj, .value = wdata->progress, .colspan = 2);
+            ui_newline(obj);
+
+            ui_newline(obj);
+            ui_textfield(obj, .value = wdata->t1);
+            ui_textfield(obj, .value = wdata->t2);
+            ui_textfield(obj, .value = wdata->t3);
+            ui_newline(obj);
+            ui_button(obj, .label = "Add", .onclick = action_add);
+            ui_newline(obj);
+
+
+            ui_newline(obj);
+
+            UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", UI_STRING, "Col 3", -1);
+            model->getvalue = table_getvalue;
+            ui_table(obj,   .colspan = 3, .model = model, .list = wdata->list2, .onactivate = action_onactivate,
+                            .onselection = action_listselection_changed,
+                            .ondragstart = dragstart, .ondragcomplete = dragcomplete, .ondrop = drop);
+            ui_model_free(obj->ctx, model);
+        }
+    }   
+
+    ui_show(obj);
+}
+
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
+{ 
+    ui_init("app1", 0, NULL);
+    ui_onstartup(application_startup, NULL);
+    
+    ui_menu("File") {
+        ui_menuitem(.label = "Item 1");
+        ui_menuitem(.label = "Item 2");
+        ui_menuseparator();
+        ui_menu("File Sub") {
+            ui_menuitem(.label = "Sub Item");
+        }
+
+        ui_menuitem(.label = "Exit");
+    }
+
+    ui_toolbar_item("Test", .label = "Home", .icon = "Home", .onclick = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle2", .label = "Toggle2", .onchange = action_toolbar_button);
+    ui_toolbar_toggleitem("Toggle3", .label = "Toggle3", .onchange = action_toolbar_button);
+
+    ui_toolbar_menu("Menu", .label = "Menu") {
+        
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menuitem(.label = "x", NULL, NULL);
+        ui_menu("TB Sub") {
+            ui_menuitem("TB subitem", NULL, NULL);
+        }
+    }
+
+    ui_toolbar_menu(NULL, .label = "Menu") {
+        ui_menuitem("Secondary Test", NULL, NULL);
+        ui_menu("Secondary Sub") {
+            ui_menuitem("Secondary subitem", NULL, NULL);
+        }
+    }
+
+    ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);
+    ui_toolbar_add_default("Toggle2", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Toggle3", UI_TOOLBAR_CENTER);
+    ui_toolbar_add_default("Menu", UI_TOOLBAR_RIGHT);
+
+    ui_main();
+
+    return (EXIT_SUCCESS);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/packages.config	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />
+  <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />
+</packages>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/testapp.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" />
+  <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" />
+  <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+	
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{3541f08b-e6cc-4c23-a0d3-51983aab33c6}</ProjectGuid>
+    <RootNamespace>testapp</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
+    <WindowsPackageType>None</WindowsPackageType>
+    <AppxPackage>false</AppxPackage>
+    <ProjectName>idav</ProjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">
+      <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" />
+    <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" />
+    <Import Project="..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/testapp/testapp.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="main.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/ucx/ucx.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{27da0164-3475-43e2-a1a4-a5d07d305749}</ProjectGuid>
+    <RootNamespace>ucx</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\ucx\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>false</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ucx\allocator.c" />
+    <ClCompile Include="..\..\..\ucx\array_list.c" />
+    <ClCompile Include="..\..\..\ucx\buffer.c" />
+    <ClCompile Include="..\..\..\ucx\compare.c" />
+    <ClCompile Include="..\..\..\ucx\hash_key.c" />
+    <ClCompile Include="..\..\..\ucx\hash_map.c" />
+    <ClCompile Include="..\..\..\ucx\linked_list.c" />
+    <ClCompile Include="..\..\..\ucx\list.c" />
+    <ClCompile Include="..\..\..\ucx\map.c" />
+    <ClCompile Include="..\..\..\ucx\mempool.c" />
+    <ClCompile Include="..\..\..\ucx\printf.c" />
+    <ClCompile Include="..\..\..\ucx\string.c" />
+    <ClCompile Include="..\..\..\ucx\utils.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h" />
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h" />
+    <ClInclude Include="..\..\..\ucx\cx\collection.h" />
+    <ClInclude Include="..\..\..\ucx\cx\common.h" />
+    <ClInclude Include="..\..\..\ucx\cx\compare.h" />
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h" />
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h" />
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h" />
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\list.h" />
+    <ClInclude Include="..\..\..\ucx\cx\map.h" />
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h" />
+    <ClInclude Include="..\..\..\ucx\cx\printf.h" />
+    <ClInclude Include="..\..\..\ucx\cx\string.h" />
+    <ClInclude Include="..\..\..\ucx\cx\utils.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/ucx/ucx.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ucx\allocator.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\array_list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\buffer.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\compare.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\hash_key.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\hash_map.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\linked_list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\list.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\map.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\printf.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\string.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\utils.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ucx\mempool.c">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\collection.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\common.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\compare.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\list.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\map.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\printf.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\string.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ucx\cx\utils.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/uicommon/uicommon.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>17.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{8b88698e-c185-4383-99fe-0c34d6deed2e}</ProjectGuid>
+    <RootNamespace>uicommon</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\..\build\vs\uicommon\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>false</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ui\common\context.c" />
+    <ClCompile Include="..\..\..\ui\common\document.c" />
+    <ClCompile Include="..\..\..\ui\common\menu.c" />
+    <ClCompile Include="..\..\..\ui\common\object.c" />
+    <ClCompile Include="..\..\..\ui\common\properties.c" />
+    <ClCompile Include="..\..\..\ui\common\toolbar.c" />
+    <ClCompile Include="..\..\..\ui\common\types.c" />
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ui\common\context.h" />
+    <ClInclude Include="..\..\..\ui\common\document.h" />
+    <ClInclude Include="..\..\..\ui\common\menu.h" />
+    <ClInclude Include="..\..\..\ui\common\object.h" />
+    <ClInclude Include="..\..\..\ui\common\properties.h" />
+    <ClInclude Include="..\..\..\ui\common\toolbar.h" />
+    <ClInclude Include="..\..\..\ui\common\types.h" />
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/uicommon/uicommon.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien\Source">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\ui\common\context.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\document.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\menu.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\object.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\properties.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\toolbar.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\types.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\ui\common\context.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\document.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\menu.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\object.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\properties.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\toolbar.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\types.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h">
+      <Filter>Ressourcendateien\Source</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
Binary file make/vs/x64/Debug/Microsoft.Windows.AppLifecycle.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.AppLifecycle.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.AppLifecycle</name>
+  </assembly>
+  <members>
+    <member name="T:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager">
+      <summary>Provides static methods you can use to register and unregister for certain types of activations for your app.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForFileTypeActivation(System.String[],System.String,System.String,System.String[],System.String)">
+      <summary>Registers to activate the app when the specified file type is opened via ShellExecute, or the command-line.</summary>
+      <param name="supportedFileTypes">One or more supported file types, specified by the file extension including the leading ., such as .docx.</param>
+      <param name="logo">The path to the image or resource used by Windows for the file type. For packaged apps, this parameter is a package-relative path to an image file. For unpackaged, this parameter is a literal filepath to a binary file (DLL, EXE) plus a resource index.</param>
+      <param name="displayName">This display name used by Windows for the file type.</param>
+      <param name="supportedVerbs">Zero or more app-defined verbs. Each verb is added to the File Explorer context menu when a registered file is right-clicked, and the selected verb is passed to the app as the IFileActivatedEventArgs.Verb property.</param>
+      <param name="exePath">The path to the executable to be activated. If you pass an empty string, the current exectuable will be activated by default. Typically this parameter is specified if the caller of this method is the app's installer rather than the app itself.</param>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForProtocolActivation(System.String,System.String,System.String,System.String)">
+      <summary>Registers to activate the app when the specified URI scheme is executed via ShellExecute, or the command-line.</summary>
+      <param name="scheme">The URI scheme to register for activations, such as https.</param>
+      <param name="logo">The path to the image or resource used by Windows for the URI scheme. For packaged apps, this parameter is a package-relative path to an image file. For unpackaged, this parameter is a literal filepath to a binary file (DLL, EXE) plus a resource index.</param>
+      <param name="displayName">This display name used by Windows for the URI scheme.</param>
+      <param name="exePath">The path to the executable to be activated. If you pass an empty string, the current exectuable will be activated by default. Typically this parameter is specified if the caller of this method is the app's installer rather than the app itself.</param>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForStartupActivation(System.String,System.String)">
+      <summary>Registers to activate the app when when the app is started by the user logging into the Windows OS, either because of a registry key, or because of a shortcut in a well-known startup folder.</summary>
+      <param name="taskId">An app-defined ID that can be used to unregister for startup activations later by using the UnregisterForStartupActivation method.</param>
+      <param name="exePath">The path to the executable to be activated. If you pass an empty string, the current exectuable will be activated by default. Typically this parameter is specified if the caller of this method is the app's installer rather than the app itself.</param>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForFileTypeActivation(System.String[],System.String)">
+      <summary>Unregisters a file type activation that was registered earlier by using the RegisterForFileTypeActivation method.</summary>
+      <param name="fileTypes">The file type that was previously registered for protocol activation.</param>
+      <param name="exePath">The path to the executable that was previously registered for protocol activation.</param>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForProtocolActivation(System.String,System.String)">
+      <summary>Unregisters a protocol activation that was registered earlier by using the RegisterForProtocolActivation method.</summary>
+      <param name="scheme">The URI scheme that was previously registered for protocol activation.</param>
+      <param name="exePath">The path to the executable that was previously registered for protocol activation.</param>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForStartupActivation(System.String)">
+      <summary>Unregisters a startup activation that was registered earlier by using the RegisterForStartupActivation method.</summary>
+      <param name="taskId">The task ID that was previously registered for protocol activation.</param>
+    </member>
+    <member name="T:Microsoft.Windows.AppLifecycle.AppActivationArguments">
+      <summary>Contains information about the type and data payload for an app activation that was registered by using one of the static methods of the ActivationRegistrationManager class.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.AppLifecycle.AppActivationArguments.Data">
+      <summary>Gets the data payload for a registered activation.</summary>
+      <returns>The data payload for a registered activation. For more information about the type of this object, see the remarks.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.AppLifecycle.AppActivationArguments.Kind">
+      <summary>Gets the type of a registered activation.</summary>
+      <returns>The type of a registered activation.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.AppLifecycle.AppInstance">
+      <summary>Represents an instance of an app.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.AppLifecycle.AppInstance.Activated">
+      <summary>Raised when an app activation is triggered that was registered by using one of the static methods of the ActivationRegistrationManager class.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.FindOrRegisterForKey(System.String)">
+      <summary>Registers an app instance with the platform, or finds an existing instance if another instance has already registered this key.</summary>
+      <param name="key">A non-empty string as a key for the instance.</param>
+      <returns>An app instance that represents the first app that registered the key. The caller can determine whether that instance is the current instance.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.GetActivatedEventArgs">
+      <summary>Retrieves the event args for an app activation that was registered by using one of the static methods of the ActivationRegistrationManager class.</summary>
+      <returns>An object that contains the activation type and the data payload.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent">
+      <summary>Retrieves the current running instance of the app.</summary>
+      <returns>The current running instance of the app.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.GetInstances">
+      <summary>Retrieves a collection of all running instances of the app.</summary>
+      <returns>The collection of all running instances of the app.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.RedirectActivationToAsync(Microsoft.Windows.AppLifecycle.AppActivationArguments)">
+      <summary>Redirects the current activation request to another app instance.</summary>
+      <param name="args">The activation arguments to pass to the other app instance.</param>
+      <returns>An object that represents the results of the asynchronous operation.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.AppLifecycle.AppInstance.UnregisterKey">
+      <summary>Unregisters a given key for this app instance.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.AppLifecycle.AppInstance.IsCurrent">
+      <summary>Gets a value that indicates whether this AppInstance object represents the current instance of the app or a different instance.</summary>
+      <returns>true indicates that this AppInstance object represents the current instance of the app; false indicates it represents a different instance.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.AppLifecycle.AppInstance.Key">
+      <summary>Gets an app-defined string value that identifies the current app instance for redirection purposes.</summary>
+      <returns>An app-defined string value that identifies the current app instance for redirection purposes.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.AppLifecycle.AppInstance.ProcessId">
+      <summary>Gets the process ID of the app instance.</summary>
+      <returns>The process ID of the app instance.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.AppLifecycle.ExtendedActivationKind">
+      <summary>Defines values that represent activation types.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.AppointmentsProvider">
+      <summary>The user wants to manage appointments that are provided by the app.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.BarcodeScannerProvider">
+      <summary>The app was activated as a barcode scanner provider.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.CachedFileUpdater">
+      <summary>The user wants to save a file that the app provides content management for.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.CameraSettings">
+      <summary>The app captures photos or video from an attached camera.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.CommandLineLaunch">
+      <summary>The app was launched from the command line.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ComponentUI">
+      <summary>Reserved for system use.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Contact">
+      <summary>The user wants to handle calls or messages for the phone number of a contact that is provided by the app.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ContactPanel">
+      <summary>The app was launched from the My People UI.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ContactPicker">
+      <summary>The user wants to pick contacts.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Device">
+      <summary>The app handles AutoPlay.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.DevicePairing">
+      <summary>This app was activated as a result of pairing a device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.DialReceiver">
+      <summary>This app was launched by another app on a different device by using the DIAL protocol.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.File">
+      <summary>An app launched a file whose file type this app is registered to handle.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.FileOpenPicker">
+      <summary>The user wants to pick files that are provided by the app.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.FilePickerExperience">
+      <summary>Reserved for system use.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.FileSavePicker">
+      <summary>The user wants to save a file and selected the app as the location.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.GameUIProvider">
+      <summary>The app was activated because it was launched by the OS due to a game's request for Xbox-specific UI.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Launch">
+      <summary>The user launched the app or tapped a content tile.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.LockScreen">
+      <summary>The app was activated as the lock screen.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.LockScreenCall">
+      <summary>The app launches a call from the lock screen. If the user wants to accept the call, the app displays its call UI directly on the lock screen without requiring the user to unlock. A lock-screen call is a special type of launch activation.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.LockScreenComponent">
+      <summary>Reserved for system use.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PhoneCallActivation">
+      <summary>The app was activated by a phone call.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PickerReturned">
+      <summary>Windows Phone only. The app was activated after the completion of a picker.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PickFileContinuation">
+      <summary>Windows Phone only. The app was activated after the app was suspended for a file picker operation.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PickFolderContinuation">
+      <summary>Windows Phone only. The app was activated after the app was suspended for a folder picker operation.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PickSaveFileContinuation">
+      <summary>Windows Phone only. The app was activated after the app was suspended for a file save picker operation.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Print3DWorkflow">
+      <summary>This app was launched by another app to provide a customized printing experience for a 3D printer.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PrintSupportJobUI">
+      <summary>The app was activated as print support job UI.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PrintSupportSettingsUI">
+      <summary>The app was activated as print support settings UI.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PrintTaskSettings">
+      <summary>The app handles print tasks.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.PrintWorkflowForegroundTask">
+      <summary>The app was activated because the user is printing to a printer that has a Print Workflow App associated with it which has requested user input.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Protocol">
+      <summary>An app launched a URI whose scheme name this app is registered to handle.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ProtocolForResults">
+      <summary>The app was launched by another app with the expectation that it will return a result back to the caller.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Push" />
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.RestrictedLaunch">
+      <summary>The user launched the restricted app.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Search">
+      <summary>The user wants to search with the app.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ShareTarget">
+      <summary>The app is activated as a target for share operations.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.StartupTask">
+      <summary>The app was activated because the app is specified to launch at system startup or user log-in.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.ToastNotification">
+      <summary>The app was activated when a user tapped on the body of a toast notification or performed an action inside a toast notification.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.UserDataAccountsProvider">
+      <summary>The app was launched to handle the user interface for account management. In circumstances where the system would have shown the default system user interface, it instead has invoked your app with the UserDataAccountProvider contract. The activation payload contains information about the type of operation being requested and all the information necessary to replicate the system-provided user interface. This activation kind is limited to 1st party apps. To use this field, you must add the userDataAccountsProvider capability in your app's package manifest. For more info see App capability declarations.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.VoiceCommand">
+      <summary>The app was activated as the result of a voice command.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.VpnForeground">
+      <summary>The app was activated as a VPN app in the foreground.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.WalletAction">
+      <summary>Windows Phone only. The app was activated to perform a Wallet operation.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.WebAccountProvider">
+      <summary>The app was activated by a web account provider.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.AppLifecycle.ExtendedActivationKind.WebAuthenticationBrokerContinuation">
+      <summary>Windows Phone only. The app was activated after the app was suspended for a web authentication broker operation.</summary>
+    </member>
+  </members>
+</doc>
\ No newline at end of file
Binary file make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.DynamicDependency.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.DynamicDependency.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.ApplicationModel.DynamicDependency</name>
+  </assembly>
+  <members>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions">
+      <summary>Defines options that can be applied when adding a run-time reference to a framework package by using the PackageDependency.Add" method.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions.#ctor">
+      <summary>Creates a new instance of the AddPackageDependencyOptions class.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions.PrependIfRankCollision">
+      <summary>When you call the PackageDependency.Add" method and multiple packages are present in the package graph with the same rank as specified by the Rank property, this property indicates whether the resolved package is added before others of the same rank.</summary>
+      <returns>If true, the resolved package is added before others of the same rank. If false, the resolved package is not added before others of the same rank.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions.Rank">
+      <summary>The rank to use to add the resolved package to the caller's package graph.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions">
+      <summary>Defines criteria that can be applied when creating an install-time reference to a framework package by using the PackageDependency.Create" method. This informs the OS that your unpackaged app has a dependency upon a framework package that meets the specified criteria.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions.#ctor">
+      <summary>Creates a new instance of the CreatePackageDependencyOptions class.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions.Architectures">
+      <summary>Gets or sets the processor architectures of the framework package on which your unpackaged app has a dependency.</summary>
+      <returns>A bitwise combination of values that indicates the processor architectures of the framework package on which your unpackaged app has a dependency.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions.LifetimeArtifact">
+      <summary>Gets or sets the name of the artifact used to define the lifetime of the package dependency, if the LifetimeArtifactKind property is set to PackageDependencyLifetimeArtifactKind.FilePath or PackageDependencyLifetimeArtifactKind.RegistryKey.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions.LifetimeArtifactKind">
+      <summary>Gets or sets the type of artifact to use to define the lifetime of the package dependency.</summary>
+      <returns>The type of artifact to use to define the lifetime of the package dependency.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions.VerifyDependencyResolution">
+      <summary>Gets or sets a value that indicates whether to disable dependency resolution when pinning a package dependency. This is useful for installers running as user contexts other than the target user (for example, installers running as LocalSystem).</summary>
+      <returns>Specify true to verify dependency resolution when pinning a package dependency; specify false to disable dependency resolution.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency">
+      <summary>Represents a framework package on which the current app has a dependency, and includes members you can use to manage the lifetime of the dependency.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Add">
+      <summary>Adds a run-time reference for the framework package dependency you created earlier by using the Create method. After this method successfully returns, your app may activate types and use content from the framework package.</summary>
+      <returns>An object that provides context info about the framework package dependency and enables you to remove the run-time reference.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Add(Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions)">
+      <summary>Adds a run-time reference for the framework package dependency you created earlier by using the Create method, with the specified options. After this method successfully returns, your app can activate types and use content from the framework package.</summary>
+      <param name="options">Defines additional options to specify the framework package reference.</param>
+      <returns>An object that provides context info about the framework package dependency and enables you to remove the run-time reference.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Create(System.String,Windows.ApplicationModel.PackageVersion)">
+      <summary>Creates an install-time reference for a framework package dependency for the current app, using the specified package family name and minimum version. When you use this method, the framework package dependency is accessible to the current user only. To create a framework package dependency that is accessible to all users, use the CreateForSystem method instead.</summary>
+      <param name="packageFamilyName">The package family name of the framework package on which to take dependency.</param>
+      <param name="minVersion">The minimum version of the framework package on which to take dependency.</param>
+      <returns>The object that represents the package dependency, and provides members you can use to manage the lifetime of the dependency.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Create(System.String,Windows.ApplicationModel.PackageVersion,Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions)">
+      <summary>Creates an install-time reference for a framework package dependency for the current app, using the specified package family name and minimum version and the specified options. When you use this method, the framework package dependency is accessible to the current user only. To create a framework package dependency that is accessible to all users, use the CreateForSystem method instead.</summary>
+      <param name="packageFamilyName">The package family name of the framework package on which to take dependency.</param>
+      <param name="minVersion">The minimum version of the framework package on which to take dependency.</param>
+      <param name="options">Defines additional criteria to specify the framework package you want to use in your app.</param>
+      <returns>The object that represents the package dependency, and provides members you can use to manage the lifetime of the dependency.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.CreateForSystem(System.String,Windows.ApplicationModel.PackageVersion,Microsoft.Windows.ApplicationModel.DynamicDependency.CreatePackageDependencyOptions)">
+      <summary>Creates an install-time reference for a framework package dependency for the current app, using the specified package family name and minimum version and the specified options. This method creates a framework package dependency that is accessible to all users, and this method requires that the caller has administrative privileges. To create a framework package dependency that is accessible only to the current user, use the Create method instead.</summary>
+      <param name="packageFamilyName">The package family name of the framework package on which to take dependency.</param>
+      <param name="minVersion">The minimum version of the framework package on which to take dependency.</param>
+      <param name="options">Defines additional criteria to specify the framework package you want to use in your app.</param>
+      <returns>The object that represents the package dependency, and provides members you can use to manage the lifetime of the dependency.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Delete">
+      <summary>Deletes the install-time reference for the framework package dependency you created earlier by using the Create method. This method informs the OS that it is safe to remove the framework package if no other apps have a dependency on it.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.GetFromId(System.String)">
+      <summary>Creates a new package dependency instance from the specified package dependency ID.</summary>
+      <param name="id">The existing package dependency ID from which to create the new package dependency object.</param>
+      <returns>The object that represents the new package dependency, and provides members you can use to manage the lifetime of the dependency.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.GetFromIdForSystem(System.String)">
+      <summary>Creates a new package dependency instance from the specified package dependency ID. The package dependency is accessible to all users, , and this method requires that the caller has administrative privileges.</summary>
+      <param name="id">The existing package dependency ID from which to create the new package dependency object.</param>
+      <returns>The object that represents the new package dependency, and provides members you can use to manage the lifetime of the dependency.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.GenerationId">
+      <summary>Gets the package graph's current generation ID.</summary>
+      <returns>The package graph's current generation ID.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.Id">
+      <summary>Gets the ID of the package dependency. This value is available after successful calls to the Create and CreateForSystem methods.</summary>
+      <returns>The ID of the package dependency.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext">
+      <summary>Provides context info about a resolved framework package dependency that was created by using the PackageDependency.Add method.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext.#ctor(Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContextId)">
+      <summary>Creates a new instance of the PackageDependencyContext class based on the specified context ID.</summary>
+      <param name="contextId">The context ID on which to base the new PackageDependencyContext.</param>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext.Remove">
+      <summary>Removes a resolved package dependency from the current process' package graph (that is, a run-time reference for a framework package dependency that was added by using the PackageDependency.Add.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext.ContextId">
+      <summary>Gets the context ID of the resolved framework package dependency for the current context PackageDependencyContext object.</summary>
+      <returns>The context ID of the resolved framework package dependency for the current context PackageDependencyContext object.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext.PackageDependencyId">
+      <summary>Gets the ID of the resolved framework package dependency for the current context PackageDependencyContext object.</summary>
+      <returns>The ID of the resolved framework package dependency for the current context PackageDependencyContext object.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext.PackageFullName">
+      <summary>Gets the package full name for the resolved framework package dependency for the current context PackageDependencyContext object.</summary>
+      <returns>The package full name for the resolved framework package dependency for the current context PackageDependencyContext object.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContextId">
+      <summary>Encapsulates a unique ID for a resolved framework package dependency that is described by a PackageDependencyContext object.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContextId.Id">
+      <summary>The unique ID for a resolved framework package dependency.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyLifetimeArtifactKind">
+      <summary>Defines the type of artifacts you can assign to the LifetimeArtifactKind property to define the lifetime of a package dependency.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyLifetimeArtifactKind.FilePath">
+      <summary>The lifetime artifact is an absolute filename or path. The package dependency is implicitly deleted when this is deleted.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyLifetimeArtifactKind.Process">
+      <summary>The current process is the lifetime artifact. The package dependency is implicitly deleted when the process terminates.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyLifetimeArtifactKind.RegistryKey">
+      <summary>The lifetime artifact is a registry key in the format root\subkey, where root is one of the following: HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, or HKEY_USERS. The package dependency is implicitly deleted when this is deleted.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures">
+      <summary>Defines the processor architectures for a framework package dependency that you create by using the PackageDependency.Create" method.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.Arm">
+      <summary>Specifies the ARM architecture.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.Arm64">
+      <summary>Specifies the ARM64 architecture.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.Neutral">
+      <summary>Specifies the neutral architecture.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.None">
+      <summary>No processor architecture is specified.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.X64">
+      <summary>Specifies the x64 architecture.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.X86">
+      <summary>Specifies the x86 architecture.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyProcessorArchitectures.X86OnArm64">
+      <summary>Specifies the x86/A64 architecture.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyRank">
+      <summary>Represents the default rank value to use to resolve a framework package dependency when using the PackageDependency.Add" method.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyRank.Default">
+      <summary>Gets the default rank value to use to resolve a framework package dependency when using the PackageDependency.Add" method.</summary>
+      <returns>The default rank value (currently this value is 0).</returns>
+    </member>
+  </members>
+</doc>
\ No newline at end of file
Binary file make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.ApplicationModel.WindowsAppRuntime</name>
+  </assembly>
+  <members>
+    <member name="T:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManager">
+      <summary>Provides access to deployment information for the Windows App SDK runtime.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManager.GetStatus">
+      <summary>Returns the current deployment status of the Windows App SDK runtime that is currently loaded. Use this method to identify if there is work required to install Windows App SDK runtime packages before the current app can use Windows App SDK features.</summary>
+      <returns>An object that provides deployment status and error information for the Windows App SDK runtime referenced by the current package.</returns>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManager.Initialize">
+      <summary>Checks the status of the Windows App SDK runtime referenced by the current package and attempts to register any missing packages that can be registered.</summary>
+      <returns>An object that provides deployment status and error information for the Windows App SDK runtime referenced by the current package.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentResult">
+      <summary>Provides deployment status and error information for the Windows App SDK runtime referenced by the current package.</summary>
+    </member>
+    <member name="M:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentResult.#ctor(Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus,Windows.Foundation.HResult)">
+      <summary>Initializes a new instance of the DeploymentResult class.</summary>
+      <param name="status">The deployment status of the Windows App SDK runtime that is currently loaded.</param>
+      <param name="extendedError">The first encountered error if there was an error initializing the Windows App SDK runtime or getting the status of the runtime.</param>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentResult.ExtendedError">
+      <summary>Gets the first encountered error if there was an error initializing the Windows App SDK runtime or getting the status of the runtime.</summary>
+      <returns>The first encountered error if there was an error initializing the Windows App SDK runtime or getting the status of the runtime. If there is no error, this property returns S_OK.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentResult.Status">
+      <summary>Gets the deployment status of the Windows App SDK runtime that is currently loaded.</summary>
+      <returns>The deployment status of the Windows App SDK runtime that is currently loaded.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus">
+      <summary>Represents the deployment status of the Windows App SDK runtime that is currently loaded.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus.Ok">
+      <summary>The Windows App SDK runtime is in a good deployment state.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus.PackageInstallFailed">
+      <summary>The installation of a package for the Windows App SDK runtime failed.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus.PackageInstallRequired">
+      <summary>A package install is required in order for the Windows App SDK runtime to be in a good deployment state.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentStatus.Unknown">
+      <summary>The Windows App SDK runtime is in an unknown deployment state.</summary>
+    </member>
+  </members>
+</doc>
\ No newline at end of file
Binary file make/vs/x64/Debug/Microsoft.Windows.PushNotifications.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.PushNotifications.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.PushNotifications</name>
+  </assembly>
+  <members>
+  </members>
+</doc>
Binary file make/vs/x64/Debug/Microsoft.Windows.Security.AccessControl.winmd has changed
Binary file make/vs/x64/Debug/Microsoft.Windows.System.Power.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.System.Power.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.System.Power</name>
+  </assembly>
+  <members>
+    <member name="T:Microsoft.Windows.System.Power.BatteryStatus">
+      <summary>Defines values that represent the status of the battery on the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.BatteryStatus.Charging">
+      <summary>The battery is charging.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.BatteryStatus.Discharging">
+      <summary>The battery is discharging.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.BatteryStatus.Idle">
+      <summary>The battery is idle.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.BatteryStatus.NotPresent">
+      <summary>The battery is not present.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.DisplayStatus">
+      <summary>Defines values that represent the status of the display that is associated with the app's session.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.DisplayStatus.Dimmed">
+      <summary>The display is dimmed.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.DisplayStatus.Off">
+      <summary>The display is off.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.DisplayStatus.On">
+      <summary>The display is on.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.EffectivePowerMode">
+      <summary>Defines values that represent the effective power mode of the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.Balanced">
+      <summary>The device is in the balanced effective power mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.BatterySaver">
+      <summary>The device is in battery saver mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.BetterBattery">
+      <summary>The device is in the better battery effective power mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.GameMode">
+      <summary>The device is in game mode power mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.HighPerformance">
+      <summary>The device is in the high performance effective power mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.MaxPerformance">
+      <summary>The device is in the maximum performance effective power mode.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EffectivePowerMode.MixedReality">
+      <summary>The device is in the windows mixed reality power mode.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.EnergySaverStatus">
+      <summary>Defines values that represent the battery saver states of the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EnergySaverStatus.Disabled">
+      <summary>Battery saver is disabled.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EnergySaverStatus.Off">
+      <summary>Battery saver is off.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EnergySaverStatus.On">
+      <summary>Battery saver is on.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.EnergySaverStatus.Uninitialized">
+      <summary>Battery saver is uninitialized.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.PowerManager">
+      <summary>Provides static events that notify your app of changes to the devices power state and static properties that provide access to current power state information.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.BatteryStatusChanged">
+      <summary>Raised when the status of the battery on the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.DisplayStatusChanged">
+      <summary>Raised when the status of the display that is associated with the app's session.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.EffectivePowerModeChanged">
+      <summary>Raised when the effective power mode of the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.EnergySaverStatusChanged">
+      <summary>Raised when battery saver has been turned off or on in response to changing power conditions.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.PowerSourceKindChanged">
+      <summary>Raised when the power source of the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.PowerSupplyStatusChanged">
+      <summary>Raised when the power supply status of the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.RemainingChargePercentChanged">
+      <summary>Raised when the remaining charge percentage of the battery on the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.RemainingDischargeTimeChanged">
+      <summary>Raised when the remaining discharge time of the battery on the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.SystemIdleStatusChanged">
+      <summary>Raised when the system is busy. This indicates that the system will not be moving into an idle state in the near future and that the current time is a good time for components to perform background or idle tasks that would otherwise prevent the computer from entering an idle state.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.SystemSuspendStatusChanged">
+      <summary>Raised when the suspend status of the device has changed.</summary>
+    </member>
+    <member name="E:Microsoft.Windows.System.Power.PowerManager.UserPresenceStatusChanged">
+      <summary>Raised when the user status associated with the app's session has changed.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.BatteryStatus">
+      <summary>Gets the current status of the battery on the device.</summary>
+      <returns>The current status of the battery.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.DisplayStatus">
+      <summary>Gets the current status of the display that is associated with the app's session.</summary>
+      <returns>The current status of the display that is associated with the app's session.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.EffectivePowerMode">
+      <summary>Gets the current effective power mode of the device.</summary>
+      <returns>The current effective power mode of the device.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.EnergySaverStatus">
+      <summary>Gets the current state of battery saver on the device.</summary>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.PowerSourceKind">
+      <summary>Gets the current power source of the device.</summary>
+      <returns>The current power source of the device.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.PowerSupplyStatus">
+      <summary>Gets the current power supply status of the device.</summary>
+      <returns>The current power supply status of the device.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.RemainingChargePercent">
+      <summary>Gets the remaining charge percentage of the battery on the device.</summary>
+      <returns>The remaining charge percentage of the battery.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.RemainingDischargeTime">
+      <summary>Gets the remaining discharge time of the battery on the device.</summary>
+      <returns>The remaining discharge time of the battery.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.SystemSuspendStatus">
+      <summary>Gets the current suspend status of the device.</summary>
+      <returns>The current suspend status of the device.</returns>
+    </member>
+    <member name="P:Microsoft.Windows.System.Power.PowerManager.UserPresenceStatus">
+      <summary>Gets the current user status associated with the app's session.</summary>
+      <returns>The current user present status of the device.</returns>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.PowerSourceKind">
+      <summary>Defines values that represent the power source of the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.PowerSourceKind.AC">
+      <summary>The computer is powered by an AC power source (or similar, such as a laptop powered by a 12V automotive adapter).</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.PowerSourceKind.DC">
+      <summary>The computer is powered by an onboard battery power source.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.PowerSupplyStatus">
+      <summary>Defines values that represent the power supply status of the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.PowerSupplyStatus.Adequate">
+      <summary>Power supply is adequate.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.PowerSupplyStatus.Inadequate">
+      <summary>Power supply is not adequate.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.PowerSupplyStatus.NotPresent">
+      <summary>Power supply is not present.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.SystemSuspendStatus">
+      <summary>Defines values that represent the suspend status of the device.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.SystemSuspendStatus.AutoResume">
+      <summary>The device is automatically resuming from suspend state.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.SystemSuspendStatus.Entering">
+      <summary>The device is entering suspend state.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.SystemSuspendStatus.ManualResume">
+      <summary>The user has manually resumed the device from suspend state.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.SystemSuspendStatus.Uninitialized">
+      <summary>The suspend status is not initialized.</summary>
+    </member>
+    <member name="T:Microsoft.Windows.System.Power.UserPresenceStatus">
+      <summary>Defines values that represent the user status associated with the app's session.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.UserPresenceStatus.Absent">
+      <summary>The user is absent.</summary>
+    </member>
+    <member name="F:Microsoft.Windows.System.Power.UserPresenceStatus.Present">
+      <summary>The user is present.</summary>
+    </member>
+  </members>
+</doc>
\ No newline at end of file
Binary file make/vs/x64/Debug/Microsoft.Windows.System.winmd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/vs/x64/Debug/Microsoft.Windows.System.xml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <assembly>
+    <name>Microsoft.Windows.System</name>
+  </assembly>
+  <members>
+  </members>
+</doc>
Binary file make/vs/x64/Debug/Microsoft.WindowsAppRuntime.Bootstrap.dll has changed
Binary file make/vs/x64/Debug/idav.exe has changed
Binary file make/vs/x64/Debug/idav.pdb has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/windows.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,42 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 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.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS  = -std=gnu99
+LDFLAGS = 
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = obj
+LIB_EXT = lib
+APP_EXT = .exe
+
Binary file resource/.DS_Store has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resource/locales/de_DE.properties	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1 @@
+hello = HALLO WELT!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resource/locales/en_EN.properties	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1 @@
+hello = HELLO WORLD!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resource/template.app/Contents/Info.plist	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildMachineOSBuild</key>
+	<string>10K549</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>de_DE</string>
+	<key>CFBundleExecutable</key>
+	<string>mk12</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.yourcompany.toolkit</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>toolkit</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>DTCompiler</key>
+	<string></string>
+	<key>DTPlatformBuild</key>
+	<string>10M2518</string>
+	<key>DTPlatformVersion</key>
+	<string>PG</string>
+	<key>DTSDKBuild</key>
+	<string>10M2518</string>
+	<key>DTSDKName</key>
+	<string>macosx10.6</string>
+	<key>DTXcode</key>
+	<string>0400</string>
+	<key>DTXcodeBuild</key>
+	<string>10M2518</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>10.7</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>CFBundleDisplayName</key>
+	<string></string>
+	<key>CFBundleGetInfoString</key>
+	<string></string>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
+	<key>CFBundleDocumentTypes</key>
+	<array>
+		<dict>
+			<key>LSItemContentTypes</key>
+			<array>
+				<string>public.data</string>
+			</array>
+			<key>CFBundleTypeIconFile</key>
+			<string></string>
+			<key>CFBundleTypeName</key>
+			<string>DocumentType</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+<!--
+			<key>NSDocumentClass</key>
+			<string>Document</string>
+-->
+		</dict>
+	</array>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resource/template.app/Contents/PkgInfo	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1 @@
+APPL????
\ No newline at end of file
Binary file resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings has changed
Binary file resource/template.app/Contents/Resources/English.lproj/MainMenu.nib has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,61 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2013 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.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+# list of source files
+SRC  = allocator.c
+SRC += array_list.c
+SRC += basic_mempool.c
+SRC += buffer.c
+SRC += compare.c
+SRC += hash_key.c
+SRC += hash_map.c
+SRC += linked_list.c
+SRC += list.c
+SRC += map.c
+SRC += printf.c
+SRC += string.c
+SRC += utils.c
+
+OBJ   = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
+
+UCX_LIB = ../build/lib/libucx$(LIB_EXT)
+
+all: ../build/ucx $(UCX_LIB)
+
+$(UCX_LIB): $(OBJ)
+	$(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ)
+
+../build/ucx:
+	mkdir -p ../build/ucx
+
+../build/ucx/%$(OBJ_EXT): %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/allocator.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,136 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/allocator.h"
+
+__attribute__((__malloc__, __alloc_size__(2)))
+static void *cx_malloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        size_t n
+) {
+    return malloc(n);
+}
+
+__attribute__((__warn_unused_result__, __alloc_size__(3)))
+static void *cx_realloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        void *mem,
+        size_t n
+) {
+    return realloc(mem, n);
+}
+
+__attribute__((__malloc__, __alloc_size__(2, 3)))
+static void *cx_calloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        size_t nelem,
+        size_t n
+) {
+    return calloc(nelem, n);
+}
+
+__attribute__((__nonnull__))
+static void cx_free_stdlib(
+        __attribute__((__unused__)) void *d,
+        void *mem
+) {
+    free(mem);
+}
+
+static cx_allocator_class cx_default_allocator_class = {
+        cx_malloc_stdlib,
+        cx_realloc_stdlib,
+        cx_calloc_stdlib,
+        cx_free_stdlib
+};
+
+struct cx_allocator_s cx_default_allocator = {
+        &cx_default_allocator_class,
+        NULL
+};
+CxAllocator *cxDefaultAllocator = &cx_default_allocator;
+
+
+int cx_reallocate(
+        void **mem,
+        size_t n
+) {
+    void *nmem = realloc(*mem, n);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
+// IMPLEMENTATION OF HIGH LEVEL API
+
+void *cxMalloc(
+        CxAllocator const *allocator,
+        size_t n
+) {
+    return allocator->cl->malloc(allocator->data, n);
+}
+
+void *cxRealloc(
+        CxAllocator const *allocator,
+        void *mem,
+        size_t n
+) {
+    return allocator->cl->realloc(allocator->data, mem, n);
+}
+
+int cxReallocate(
+        CxAllocator const *allocator,
+        void **mem,
+        size_t n
+) {
+    void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
+void *cxCalloc(
+        CxAllocator const *allocator,
+        size_t nelem,
+        size_t n
+) {
+    return allocator->cl->calloc(allocator->data, nelem, n);
+}
+
+void cxFree(
+        CxAllocator const *allocator,
+        void *mem
+) {
+    allocator->cl->free(allocator->data, mem);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/array_list.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,547 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/array_list.h"
+#include <assert.h>
+#include <string.h>
+
+// LOW LEVEL ARRAY LIST FUNCTIONS
+
+enum cx_array_copy_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        void const *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) {
+    // assert pointers
+    assert(target != NULL);
+    assert(size != NULL);
+    assert(src != NULL);
+
+    // determine capacity
+    size_t cap = capacity == NULL ? *size : *capacity;
+
+    // check if resize is required
+    size_t minsize = index + elem_count;
+    size_t newsize = *size < minsize ? minsize : *size;
+    bool needrealloc = newsize > cap;
+
+    // reallocate if possible
+    if (needrealloc) {
+        // a reallocator and a capacity variable must be available
+        if (reallocator == NULL || capacity == NULL) {
+            return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
+        }
+
+        // check, if we need to repair the src pointer
+        uintptr_t targetaddr = (uintptr_t) *target;
+        uintptr_t srcaddr = (uintptr_t) src;
+        bool repairsrc = targetaddr <= srcaddr
+                         && srcaddr < targetaddr + cap * elem_size;
+
+        // calculate new capacity (next number divisible by 16)
+        cap = newsize - (newsize % 16) + 16;
+        assert(cap > newsize);
+
+        // perform reallocation
+        void *newmem = reallocator->realloc(
+                *target, cap, elem_size, reallocator
+        );
+        if (newmem == NULL) {
+            return CX_ARRAY_COPY_REALLOC_FAILED;
+        }
+
+        // repair src pointer, if necessary
+        if (repairsrc) {
+            src = ((char *) newmem) + (srcaddr - targetaddr);
+        }
+
+        // store new pointer and capacity
+        *target = newmem;
+        *capacity = cap;
+    }
+
+    // determine target pointer
+    char *start = *target;
+    start += index * elem_size;
+
+    // copy elements and set new size
+    memmove(start, src, elem_count * elem_size);
+    *size = newsize;
+
+    // return successfully
+    return CX_ARRAY_COPY_SUCCESS;
+}
+
+#ifndef CX_ARRAY_SWAP_SBO_SIZE
+#define CX_ARRAY_SWAP_SBO_SIZE 128
+#endif
+
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+) {
+    assert(arr != NULL);
+
+    // short circuit
+    if (idx1 == idx2) return;
+
+    char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
+    void *tmp;
+
+    // decide if we can use the local buffer
+    if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
+        tmp = malloc(elem_size);
+        // we don't want to enforce error handling
+        if (tmp == NULL) abort();
+    } else {
+        tmp = sbo_mem;
+    }
+
+    // calculate memory locations
+    char *left = arr, *right = arr;
+    left += idx1 * elem_size;
+    right += idx2 * elem_size;
+
+    // three-way swap
+    memcpy(tmp, left, elem_size);
+    memcpy(left, right, elem_size);
+    memcpy(right, tmp, elem_size);
+
+    // free dynamic memory, if it was needed
+    if (tmp != sbo_mem) {
+        free(tmp);
+    }
+}
+
+// HIGH LEVEL ARRAY LIST FUNCTIONS
+
+typedef struct {
+    struct cx_list_s base;
+    void *data;
+    size_t capacity;
+    struct cx_array_reallocator_s reallocator;
+} cx_array_list;
+
+static void *cx_arl_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        struct cx_array_reallocator_s *alloc
+) {
+    // retrieve the pointer to the list allocator
+    CxAllocator const *al = alloc->ptr1;
+
+    // use the list allocator to reallocate the memory
+    return cxRealloc(al, array, capacity * elem_size);
+}
+
+static void cx_arl_destructor(struct cx_list_s *list) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    char *ptr = arl->data;
+
+    if (list->simple_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+    if (list->advanced_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+
+    cxFree(list->allocator, arl->data);
+    cxFree(list->allocator, list);
+}
+
+static size_t cx_arl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    // out of bounds and special case check
+    if (index > list->size || n == 0) return 0;
+
+    // get a correctly typed pointer to the list
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // do we need to move some elements?
+    if (index < list->size) {
+        char const *first_to_move = (char const *) arl->data;
+        first_to_move += index * list->item_size;
+        size_t elems_to_move = list->size - index;
+        size_t start_of_moved = index + n;
+
+        if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
+                &arl->data,
+                &list->size,
+                &arl->capacity,
+                start_of_moved,
+                first_to_move,
+                list->item_size,
+                elems_to_move,
+                &arl->reallocator
+        )) {
+            // if moving existing elems is unsuccessful, abort
+            return 0;
+        }
+    }
+
+    // note that if we had to move the elements, the following operation
+    // is guaranteed to succeed, because we have the memory already allocated
+    // therefore, it is impossible to leave this function with an invalid array
+
+    // place the new elements
+    if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
+            &arl->data,
+            &list->size,
+            &arl->capacity,
+            index,
+            array,
+            list->item_size,
+            n,
+            &arl->reallocator
+    )) {
+        return n;
+    } else {
+        // array list implementation is "all or nothing"
+        return 0;
+    }
+}
+
+static int cx_arl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return 1 != cx_arl_insert_array(list, index, element, 1);
+}
+
+static int cx_arl_insert_iter(
+        struct cx_mut_iterator_s *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    if (iter->index < list->size) {
+        int result = cx_arl_insert_element(
+                list,
+                iter->index + 1 - prepend,
+                elem
+        );
+        if (result == 0 && prepend != 0) {
+            iter->index++;
+            iter->elem_handle = ((char *) iter->elem_handle) + list->item_size;
+        }
+        return result;
+    } else {
+        int result = cx_arl_insert_element(list, list->size, elem);
+        iter->index = list->size;
+        return result;
+    }
+}
+
+static int cx_arl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // out-of-bounds check
+    if (index >= list->size) {
+        return 1;
+    }
+
+    // content destruction
+    cx_invoke_destructor(list, ((char *) arl->data) + index * list->item_size);
+
+    // short-circuit removal of last element
+    if (index == list->size - 1) {
+        list->size--;
+        return 0;
+    }
+
+    // just move the elements starting at index to the left
+    int result = cx_array_copy(
+            &arl->data,
+            &list->size,
+            &arl->capacity,
+            index,
+            ((char *) arl->data) + (index + 1) * list->item_size,
+            list->item_size,
+            list->size - index - 1,
+            &arl->reallocator
+    );
+    if (result == 0) {
+        // decrease the size
+        list->size--;
+    }
+    return result;
+}
+
+static void cx_arl_clear(struct cx_list_s *list) {
+    if (list->size == 0) return;
+
+    cx_array_list *arl = (cx_array_list *) list;
+    char *ptr = arl->data;
+
+    if (list->simple_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+    if (list->advanced_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+
+    memset(arl->data, 0, list->size * list->item_size);
+    list->size = 0;
+}
+
+static int cx_arl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->size || j >= list->size) return 1;
+    cx_array_list *arl = (cx_array_list *) list;
+    cx_array_swap(arl->data, list->item_size, i, j);
+    return 0;
+}
+
+static void *cx_arl_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    if (index < list->size) {
+        cx_array_list const *arl = (cx_array_list const *) list;
+        char *space = arl->data;
+        return space + index * list->item_size;
+    } else {
+        return NULL;
+    }
+}
+
+static ssize_t cx_arl_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    assert(list->cmpfunc != NULL);
+    assert(list->size < SIZE_MAX / 2);
+    char *cur = ((cx_array_list const *) list)->data;
+
+    for (ssize_t i = 0; i < (ssize_t) list->size; i++) {
+        if (0 == list->cmpfunc(elem, cur)) {
+            return i;
+        }
+        cur += list->item_size;
+    }
+
+    return -1;
+}
+
+static void cx_arl_sort(struct cx_list_s *list) {
+    assert(list->cmpfunc != NULL);
+    qsort(((cx_array_list *) list)->data,
+          list->size,
+          list->item_size,
+          list->cmpfunc
+    );
+}
+
+static int cx_arl_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    assert(list->cmpfunc != NULL);
+    if (list->size == other->size) {
+        char const *left = ((cx_array_list const *) list)->data;
+        char const *right = ((cx_array_list const *) other)->data;
+        for (size_t i = 0; i < list->size; i++) {
+            int d = list->cmpfunc(left, right);
+            if (d != 0) {
+                return d;
+            }
+            left += list->item_size;
+            right += other->item_size;
+        }
+        return 0;
+    } else {
+        return list->size < other->size ? -1 : 1;
+    }
+}
+
+static void cx_arl_reverse(struct cx_list_s *list) {
+    if (list->size < 2) return;
+    void *data = ((cx_array_list const *) list)->data;
+    size_t half = list->size / 2;
+    for (size_t i = 0; i < half; i++) {
+        cx_array_swap(data, list->item_size, i, list->size - 1 - i);
+    }
+}
+
+static bool cx_arl_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_list_s const *list = iter->src_handle;
+    return iter->index < list->size;
+}
+
+static void *cx_arl_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_arl_iter_next(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        struct cx_mut_iterator_s *iter = it;
+        itbase->remove = false;
+        cx_arl_remove(iter->src_handle, iter->index);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index++;
+        iter->elem_handle =
+                ((char *) iter->elem_handle)
+                + ((struct cx_list_s const *) iter->src_handle)->item_size;
+    }
+}
+
+static void cx_arl_iter_prev(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    struct cx_mut_iterator_s *iter = it;
+    cx_array_list *const list = iter->src_handle;
+    if (itbase->remove) {
+        itbase->remove = false;
+        cx_arl_remove(iter->src_handle, iter->index);
+    }
+    iter->index--;
+    if (iter->index < list->base.size) {
+        iter->elem_handle = ((char *) list->data)
+                            + iter->index * list->base.item_size;
+    }
+}
+
+static bool cx_arl_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static struct cx_iterator_s cx_arl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter;
+
+    iter.index = index;
+    iter.src_handle = list;
+    iter.elem_handle = cx_arl_at(list, index);
+    iter.base.valid = cx_arl_iter_valid;
+    iter.base.current = cx_arl_iter_current;
+    iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
+    iter.base.flag_removal = cx_arl_iter_flag_rm;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    return iter;
+}
+
+static cx_list_class cx_array_list_class = {
+        cx_arl_destructor,
+        cx_arl_insert_element,
+        cx_arl_insert_array,
+        cx_arl_insert_iter,
+        cx_arl_remove,
+        cx_arl_clear,
+        cx_arl_swap,
+        cx_arl_at,
+        cx_arl_find,
+        cx_arl_sort,
+        cx_arl_compare,
+        cx_arl_reverse,
+        cx_arl_iterator,
+};
+
+CxList *cxArrayListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size,
+        size_t initial_capacity
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_array_list_class;
+    list->base.allocator = allocator;
+    list->base.cmpfunc = comparator;
+    list->capacity = initial_capacity;
+
+    if (item_size > 0) {
+        list->base.item_size = item_size;
+    } else {
+        item_size = sizeof(void *);
+        cxListStorePointers((CxList *) list);
+    }
+
+    // allocate the array after the real item_size is known
+    list->data = cxCalloc(allocator, initial_capacity, item_size);
+    if (list->data == NULL) {
+        cxFree(allocator, list);
+        return NULL;
+    }
+
+    // configure the reallocator
+    list->reallocator.realloc = cx_arl_realloc;
+    list->reallocator.ptr1 = (void *) allocator;
+
+    return (CxList *) list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/buffer.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,415 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/buffer.h"
+#include "cx/utils.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+) {
+    if (allocator == NULL) allocator = cxDefaultAllocator;
+    buffer->allocator = allocator;
+    buffer->flags = flags;
+    if (!space) {
+        buffer->bytes = cxMalloc(allocator, capacity);
+        if (buffer->bytes == NULL) {
+            return 1;
+        }
+        buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+    } else {
+        buffer->bytes = space;
+    }
+    buffer->capacity = capacity;
+    buffer->size = 0;
+    buffer->pos = 0;
+
+    buffer->flush_func = NULL;
+    buffer->flush_target = NULL;
+    buffer->flush_blkmax = 0;
+    buffer->flush_blksize = 4096;
+    buffer->flush_threshold = SIZE_MAX;
+
+    return 0;
+}
+
+void cxBufferDestroy(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
+    }
+}
+
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+) {
+    CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
+    if (buf == NULL) return NULL;
+    if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
+        return buf;
+    } else {
+        cxFree(allocator, buf);
+        return NULL;
+    }
+}
+
+void cxBufferFree(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
+    }
+    cxFree(buffer->allocator, buffer);
+}
+
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+) {
+    size_t npos;
+    switch (whence) {
+        case SEEK_CUR:
+            npos = buffer->pos;
+            break;
+        case SEEK_END:
+            npos = buffer->size;
+            break;
+        case SEEK_SET:
+            npos = 0;
+            break;
+        default:
+            return -1;
+    }
+
+    size_t opos = npos;
+    npos += offset;
+
+    if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+        return -1;
+    }
+
+    if (npos >= buffer->size) {
+        return -1;
+    } else {
+        buffer->pos = npos;
+        return 0;
+    }
+
+}
+
+void cxBufferClear(CxBuffer *buffer) {
+    memset(buffer->bytes, 0, buffer->size);
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
+int cxBufferEof(CxBuffer const *buffer) {
+    return buffer->pos >= buffer->size;
+}
+
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t newcap
+) {
+    if (newcap <= buffer->capacity) {
+        return 0;
+    }
+
+    if (cxReallocate(buffer->allocator,
+                     (void **) &buffer->bytes, newcap) == 0) {
+        buffer->capacity = newcap;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+/**
+ * Helps flushing data to the flush target of a buffer.
+ *
+ * @param buffer the buffer containing the config
+ * @param space the data to flush
+ * @param size the element size
+ * @param nitems the number of items
+ * @return the number of items flushed
+ */
+static size_t cx_buffer_write_flush_helper(
+        CxBuffer *buffer,
+        unsigned char const *space,
+        size_t size,
+        size_t nitems
+) {
+    size_t pos = 0;
+    size_t remaining = nitems;
+    size_t max_items = buffer->flush_blksize / size;
+    while (remaining > 0) {
+        size_t items = remaining > max_items ? max_items : remaining;
+        size_t flushed = buffer->flush_func(
+                space + pos,
+                size, items,
+                buffer->flush_target);
+        if (flushed > 0) {
+            pos += (flushed * size);
+            remaining -= flushed;
+        } else {
+            // if no bytes can be flushed out anymore, we give up
+            break;
+        }
+    }
+    return nitems - remaining;
+}
+
+size_t cxBufferWrite(
+        void const *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    // optimize for easy case
+    if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+        memcpy(buffer->bytes + buffer->pos, ptr, nitems);
+        buffer->pos += nitems;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems;
+    }
+
+    size_t len;
+    size_t nitems_out = nitems;
+    if (cx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    size_t required = buffer->pos + len;
+    if (buffer->pos > required) {
+        return 0;
+    }
+
+    bool perform_flush = false;
+    if (required > buffer->capacity) {
+        if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) {
+            if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+                perform_flush = true;
+            } else {
+                if (cxBufferMinimumCapacity(buffer, required)) {
+                    return 0;
+                }
+            }
+        } else {
+            if (buffer->flush_blkmax > 0) {
+                perform_flush = true;
+            } else {
+                // truncate data to be written, if we can neither extend nor flush
+                len = buffer->capacity - buffer->pos;
+                if (size > 1) {
+                    len -= len % size;
+                }
+                nitems_out = len / size;
+            }
+        }
+    }
+
+    if (len == 0) {
+        return len;
+    }
+
+    if (perform_flush) {
+        size_t flush_max;
+        if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
+            return 0;
+        }
+        size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
+                           ? buffer->pos
+                           : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
+        if (flush_pos == buffer->pos) {
+            // entire buffer has been flushed, we can reset
+            buffer->size = buffer->pos = 0;
+
+            size_t items_flush; // how many items can also be directly flushed
+            size_t items_keep; // how many items have to be written to the buffer
+
+            items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
+            if (items_flush > 0) {
+                items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
+                // in case we could not flush everything, keep the rest
+            }
+            items_keep = nitems - items_flush;
+            if (items_keep > 0) {
+                // try again with the remaining stuff
+                unsigned char const *new_ptr = ptr;
+                new_ptr += items_flush * size;
+                // report the directly flushed items as written plus the remaining stuff
+                return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
+            } else {
+                // all items have been flushed - report them as written
+                return nitems;
+            }
+        } else if (flush_pos == 0) {
+            // nothing could be flushed at all, we immediately give up without writing any data
+            return 0;
+        } else {
+            // we were partially successful, we shift left and try again
+            cxBufferShiftLeft(buffer, flush_pos);
+            return cxBufferWrite(ptr, size, nitems, buffer);
+        }
+    } else {
+        memcpy(buffer->bytes + buffer->pos, ptr, len);
+        buffer->pos += len;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems_out;
+    }
+
+}
+
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+) {
+    c &= 0xFF;
+    unsigned char const ch = c;
+    if (cxBufferWrite(&ch, 1, 1, buffer) == 1) {
+        return c;
+    } else {
+        return EOF;
+    }
+}
+
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+) {
+    return cxBufferWrite(str, 1, strlen(str), buffer);
+}
+
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    size_t len;
+    if (cx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    if (buffer->pos + len > buffer->size) {
+        len = buffer->size - buffer->pos;
+        if (size > 1) len -= len % size;
+    }
+
+    if (len <= 0) {
+        return len;
+    }
+
+    memcpy(ptr, buffer->bytes + buffer->pos, len);
+    buffer->pos += len;
+
+    return len / size;
+}
+
+int cxBufferGet(CxBuffer *buffer) {
+    if (cxBufferEof(buffer)) {
+        return EOF;
+    } else {
+        int c = buffer->bytes[buffer->pos];
+        buffer->pos++;
+        return c;
+    }
+}
+
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
+        buffer->size -= shift;
+
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+) {
+    size_t req_capacity = buffer->size + shift;
+    size_t movebytes;
+
+    // auto extend buffer, if required and enabled
+    if (buffer->capacity < req_capacity) {
+        if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+            if (cxBufferMinimumCapacity(buffer, req_capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+
+    memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+    buffer->size = shift + movebytes;
+
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+
+    return 0;
+}
+
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+) {
+    if (shift < 0) {
+        return cxBufferShiftLeft(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return cxBufferShiftRight(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/compare.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,201 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/compare.h"
+
+#include <math.h>
+
+int cx_cmp_int(void const *i1, void const *i2) {
+    int a = *((const int *) i1);
+    int b = *((const int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longint(void const *i1, void const *i2) {
+    long int a = *((const long int *) i1);
+    long int b = *((const long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longlong(void const *i1, void const *i2) {
+    long long a = *((const long long *) i1);
+    long long b = *((const long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int16(void const *i1, void const *i2) {
+    int16_t a = *((const int16_t *) i1);
+    int16_t b = *((const int16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int32(void const *i1, void const *i2) {
+    int32_t a = *((const int32_t *) i1);
+    int32_t b = *((const int32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int64(void const *i1, void const *i2) {
+    int64_t a = *((const int64_t *) i1);
+    int64_t b = *((const int64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint(void const *i1, void const *i2) {
+    unsigned int a = *((const unsigned int *) i1);
+    unsigned int b = *((const unsigned int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulongint(void const *i1, void const *i2) {
+    unsigned long int a = *((const unsigned long int *) i1);
+    unsigned long int b = *((const unsigned long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulonglong(void const *i1, void const *i2) {
+    unsigned long long a = *((const unsigned long long *) i1);
+    unsigned long long b = *((const unsigned long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint16(void const *i1, void const *i2) {
+    uint16_t a = *((const uint16_t *) i1);
+    uint16_t b = *((const uint16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint32(void const *i1, void const *i2) {
+    uint32_t a = *((const uint32_t *) i1);
+    uint32_t b = *((const uint32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint64(void const *i1, void const *i2) {
+    uint64_t a = *((const uint64_t *) i1);
+    uint64_t b = *((const uint64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_float(void const *f1, void const *f2) {
+    float a = *((const float *) f1);
+    float b = *((const float *) f2);
+    if (fabsf(a - b) < 1e-6f) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_double(
+        void const *d1,
+        void const *d2
+) {
+    double a = *((const double *) d1);
+    double b = *((const double *) d2);
+    if (fabs(a - b) < 1e-14) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_intptr(
+        void const *ptr1,
+        void const *ptr2
+) {
+    intptr_t p1 = *(const intptr_t *) ptr1;
+    intptr_t p2 = *(const intptr_t *) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
+
+int cx_cmp_uintptr(
+        void const *ptr1,
+        void const *ptr2
+) {
+    uintptr_t p1 = *(const uintptr_t *) ptr1;
+    uintptr_t p2 = *(const uintptr_t *) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/allocator.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,240 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file allocator.h
+ * Interface for custom allocators.
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define UCX_ALLOCATOR_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The class definition for an allocator.
+ */
+typedef struct {
+    /**
+     * The allocator's malloc() implementation.
+     */
+    void *(*malloc)(
+            void *data,
+            size_t n
+    );
+
+    /**
+     * The allocator's realloc() implementation.
+     */
+    void *(*realloc)(
+            void *data,
+            void *mem,
+            size_t n
+    )
+    __attribute__((__warn_unused_result__));
+
+    /**
+     * The allocator's calloc() implementation.
+     */
+    void *(*calloc)(
+            void *data,
+            size_t nelem,
+            size_t n
+    );
+
+    /**
+     * The allocator's free() implementation.
+     */
+    void (*free)(
+            void *data,
+            void *mem
+    )
+    __attribute__((__nonnull__));
+} cx_allocator_class;
+
+/**
+ * Structure holding the data for an allocator.
+ */
+struct cx_allocator_s {
+    /**
+     * A pointer to the instance of the allocator class.
+     */
+    cx_allocator_class *cl;
+    /**
+     * A pointer to the data this allocator uses.
+     */
+    void *data;
+};
+
+/**
+ * High-Level type alias for the allocator type.
+ */
+typedef struct cx_allocator_s CxAllocator;
+
+/**
+ * A default allocator using standard library malloc() etc.
+ */
+extern CxAllocator *cxDefaultAllocator;
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param memory a pointer to the object to destruct
+  */
+typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__));
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param data an optional pointer to custom data
+ * @param memory a pointer to the object to destruct
+  */
+typedef void (*cx_destructor_func2)(
+        void *data,
+        void *memory
+) __attribute__((__nonnull__(2)));
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ *
+ * \par Error handling
+ * \c errno will be set by realloc() on failure.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+int cx_reallocate(
+        void **mem,
+        size_t n
+)
+__attribute__((__nonnull__));
+
+/**
+ * Allocate \p n bytes of memory.
+ *
+ * @param allocator the allocator
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
+ */
+void *cxMalloc(
+        CxAllocator const *allocator,
+        size_t n
+)
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2)));
+
+/**
+ * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving the memory
+ * was not necessary.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param n the new size in bytes
+ * @return a pointer to the re-allocated memory
+ */
+void *cxRealloc(
+        CxAllocator const *allocator,
+        void *mem,
+        size_t n
+)
+__attribute__((__warn_unused_result__))
+__attribute__((__alloc_size__(3)));
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+int cxReallocate(
+        CxAllocator const *allocator,
+        void **mem,
+        size_t n
+)
+__attribute__((__nonnull__));
+
+/**
+ * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
+ *
+ * @param allocator the allocator
+ * @param nelem the number of elements
+ * @param n the size of each element in bytes
+ * @return a pointer to the allocated memory
+ */
+void *cxCalloc(
+        CxAllocator const *allocator,
+        size_t nelem,
+        size_t n
+)
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2, 3)));
+
+/**
+ * Free a block allocated by this allocator.
+ *
+ * \note Freeing a block of a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem a pointer to the block to free
+ */
+void cxFree(
+        CxAllocator const *allocator,
+        void *mem
+)
+__attribute__((__nonnull__));
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ALLOCATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/array_list.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,194 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file array_list.h
+ * \brief Array list implementation.
+ * \details Also provides several low-level functions for custom array list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_ARRAY_LIST_H
+#define UCX_ARRAY_LIST_H
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Defines a reallocation mechanism for arrays.
+ */
+struct cx_array_reallocator_s {
+    /**
+     * Re-allocates space for the given array.
+     *
+     * Implementations are not required to free the original array.
+     * This allows re-allocation of static memory by allocating heap memory
+     * and copying the array contents. The information in \p data can keep
+     * track of the state of the memory or other additional allocator info.
+     *
+     * @param array the array to reallocate
+     * @param capacity the new capacity (number of elements)
+     * @param elem_size the size of each element
+     * @param alloc a reference to this allocator
+     * @return a pointer to the reallocated memory or \c NULL on failure
+     */
+    void *(*realloc)(
+            void *array,
+            size_t capacity,
+            size_t elem_size,
+            struct cx_array_reallocator_s *alloc
+    );
+
+    /**
+     * Custom data pointer.
+     */
+    void *ptr1;
+    /**
+     * Custom data pointer.
+     */
+    void *ptr2;
+    /**
+     * Custom data integer.
+     */
+    size_t int1;
+    /**
+     * Custom data integer.
+     */
+    size_t int2;
+};
+
+/**
+ * Return codes for cx_array_copy().
+ */
+enum cx_array_copy_result {
+    CX_ARRAY_COPY_SUCCESS,
+    CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED,
+    CX_ARRAY_COPY_REALLOC_FAILED,
+};
+
+/**
+ * Copies elements from one array to another.
+ *
+ * The elements are copied to the \p target array at the specified \p index,
+ * overwriting possible elements. The \p index does not need to be in range of
+ * the current array \p size. If the new index plus the number of elements added
+ * would extend the array's size, and \p capacity is not \c NULL, the remaining
+ * capacity is used.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made, unless the allocator is set to \c NULL, in which case
+ * this function ultimately returns a failure.
+ *
+ * @param target the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity -
+ * \c NULL if only the size shall be used to bound the array
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to copy
+ * @param reallocator the array re-allocator to use, or \c NULL
+ * if re-allocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+enum cx_array_copy_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        void const *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) __attribute__((__nonnull__(1, 2, 5)));
+
+
+/**
+ * Swaps two array elements.
+ *
+ * @param arr the array
+ * @param elem_size the element size
+ * @param idx1 index of first element
+ * @param idx2 index of second element
+ */
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+) __attribute__((__nonnull__));
+
+/**
+ * Allocates an array list for storing elements with \p item_size bytes each.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param allocator the allocator for allocating the list memory
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL sort and find functions will not work)
+ * @param item_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+CxList *cxArrayListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size,
+        size_t initial_capacity
+);
+
+/**
+ * Allocates an array list for storing elements with \p item_size bytes each.
+ *
+ * The list will use the cxDefaultAllocator and \em NO compare function.
+ * If you want to call functions that need a compare function, you have to
+ * set it immediately after creation or use cxArrayListCreate().
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param item_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+#define cxArrayListCreateSimple(item_size, initial_capacity) \
+    cxArrayListCreate(NULL, NULL, item_size, initial_capacity)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ARRAY_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/buffer.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,451 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file buffer.h
+ *
+ * \brief Advanced buffer implementation.
+ *
+ * Instances of CxBuffer can be used to read from or to write to like one
+ * would do with a stream.
+ *
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_BUFFER_H
+#define UCX_BUFFER_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define CX_BUFFER_DEFAULT 0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ */
+#define CX_BUFFER_FREE_CONTENTS 0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define CX_BUFFER_AUTO_EXTEND 0x02
+
+/** Structure for the UCX buffer data. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    union {
+        /**
+         * Data is interpreted as text.
+         */
+        char *space;
+        /**
+         * Data is interpreted as binary.
+         */
+        unsigned char *bytes;
+    };
+    /** The allocator to use for automatic memory management. */
+    CxAllocator const *allocator;
+    /** Current position of the buffer. */
+    size_t pos;
+    /** Current capacity (i.e. maximum size) of the buffer. */
+    size_t capacity;
+    /** Current size of the buffer content. */
+    size_t size;
+    /**
+     * The buffer may not extend beyond this threshold before starting to flush.
+     * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
+     */
+    size_t flush_threshold;
+    /**
+     * The block size for the elements to flush.
+     * Default is 4096 bytes.
+     */
+    size_t flush_blksize;
+    /**
+     * The maximum number of blocks to flush in one cycle.
+     * Zero disables flushing entirely (this is the default).
+     * Set this to \c SIZE_MAX to flush the entire buffer.
+     *
+     * @attention if the maximum number of blocks multiplied with the block size
+     * is smaller than the expected contents written to this buffer within one write
+     * operation, multiple flush cycles are performed after that write.
+     * That means the total number of blocks flushed after one write to this buffer may
+     * be larger than \c flush_blkmax.
+     */
+    size_t flush_blkmax;
+
+    /**
+     * The write function used for flushing.
+     * If NULL, the flushed content gets discarded.
+     */
+    cx_write_func flush_func;
+
+    /**
+     * The target for \c flush_func.
+     */
+    void *flush_target;
+
+    /**
+     * Flag register for buffer features.
+     * @see #CX_BUFFER_DEFAULT
+     * @see #CX_BUFFER_FREE_CONTENTS
+     * @see #CX_BUFFER_AUTO_EXTEND
+     */
+    int flags;
+} cx_buffer_s;
+
+/**
+ * UCX buffer.
+ */
+typedef cx_buffer_s CxBuffer;
+
+/**
+ * Initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param buffer the buffer to initialize
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator this buffer shall use for automatic
+ * memory management. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return zero on success, non-zero if a required allocation failed
+ */
+__attribute__((__nonnull__(1)))
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+);
+
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return a pointer to the buffer on success, \c NULL if a required allocation failed
+ */
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+);
+
+/**
+ * Destroys the buffer contents.
+ *
+ * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled.
+ * If you want to free the memory of the entire buffer, use cxBufferFree().
+ *
+ * @param buffer the buffer which contents shall be destroyed
+ * @see cxBufferInit()
+ */
+__attribute__((__nonnull__))
+void cxBufferDestroy(CxBuffer *buffer);
+
+/**
+ * Deallocates the buffer.
+ *
+ * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
+ * the contents. If you \em only want to destroy the contents, use cxBufferDestroy().
+ *
+ * @param buffer the buffer to deallocate
+ * @see cxBufferCreate()
+ */
+__attribute__((__nonnull__))
+void cxBufferFree(CxBuffer *buffer);
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ *
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ *
+ * If the offset is negative, the contents are shifted to the left where the
+ * first \p shift bytes are discarded.
+ * The new size of the buffer is the old size minus the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ *
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ *
+ * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ *
+ * \attention
+ * Security Note: The shifting operation does \em not erase the previously occupied memory cells.
+ * But you can easily do that manually, e.g. by calling
+ * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
+ * for a left shift.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+__attribute__((__nonnull__))
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+);
+
+/**
+ * Shifts the buffer to the right.
+ * See cxBufferShift() for details.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+/**
+ * Shifts the buffer to the left.
+ * See cxBufferShift() for details.
+ *
+ * \note Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ *
+ * @param buffer the buffer
+ * @param shift the positive shift offset
+ * @return always zero
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+
+/**
+ * Moves the position of the buffer.
+ *
+ * The new position is relative to the \p whence argument.
+ *
+ * \li \c SEEK_SET marks the start of the buffer.
+ * \li \c SEEK_CUR marks the current position.
+ * \li \c SEEK_END marks the end of the buffer.
+ *
+ * With an offset of zero, this function sets the buffer position to zero
+ * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position
+ * unchanged (\c SEEK_CUR).
+ *
+ * @param buffer the buffer
+ * @param offset position offset relative to \p whence
+ * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+__attribute__((__nonnull__))
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ *
+ * The data is deleted by zeroing it with a call to memset().
+ *
+ * @param buffer the buffer to be cleared
+ */
+__attribute__((__nonnull__))
+void cxBufferClear(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer capacity.
+ *
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * available byte of the buffer.
+ */
+__attribute__((__nonnull__))
+int cxBufferEof(CxBuffer const *buffer);
+
+
+/**
+ * Ensures that the buffer has a minimum capacity.
+ *
+ * If the current capacity is not sufficient, the buffer will be extended.
+ *
+ * @param buffer the buffer
+ * @param capacity the minimum required capacity for this buffer
+ * @return 0 on success or a non-zero value on failure
+ */
+__attribute__((__nonnull__))
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t capacity
+);
+
+/**
+ * Writes data to a CxBuffer.
+ *
+ * If flushing is enabled and the buffer needs to flush, the data is flushed to
+ * the target until the target signals that it cannot take more data by
+ * returning zero via the respective write function. In that case, the remaining
+ * data in this buffer is shifted to the beginning of this buffer so that the
+ * newly available space can be used to append as much data as possible. This
+ * function only stops writing more elements, when the flush target and this
+ * buffer are both incapable of taking more data or all data has been written.
+ * The number returned by this function is the total number of elements that
+ * could be written during the process. It does not necessarily mean that those
+ * elements are still in this buffer, because some of them could have also be
+ * flushed already.
+ *
+ * If automatic flushing is not enabled, the position of the buffer is increased
+ * by the number of bytes written.
+ *
+ * \note The signature is compatible with the fwrite() family of functions.
+ *
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to write to
+ * @return the total count of elements written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferWrite(
+        void const *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+);
+
+/**
+ * Reads data from a CxBuffer.
+ *
+ * The position of the buffer is increased by the number of bytes read.
+ *
+ * \note The signature is compatible with the fread() family of functions.
+ *
+ * @param ptr a pointer to the memory area where to store the read data
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to read from
+ * @return the total number of elements read
+ */
+__attribute__((__nonnull__))
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+);
+
+/**
+ * Writes a character to a buffer.
+ *
+ * The least significant byte of the argument is written to the buffer. If the
+ * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled,
+ * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is
+ * disabled or buffer extension fails, \c EOF is returned.
+ *
+ * On successful write, the position of the buffer is increased.
+ *
+ * @param buffer the buffer to write to
+ * @param c the character to write
+ * @return the byte that has bean written or \c EOF when the end of the stream is
+ * reached and automatic extension is not enabled or not possible
+ */
+__attribute__((__nonnull__))
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+);
+
+/**
+ * Writes a string to a buffer.
+ *
+ * @param buffer the buffer
+ * @param str the zero-terminated string
+ * @return the number of bytes written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+);
+
+/**
+ * Gets a character from a buffer.
+ *
+ * The current position of the buffer is increased after a successful read.
+ *
+ * @param buffer the buffer to read from
+ * @return the character or \c EOF, if the end of the buffer is reached
+ */
+__attribute__((__nonnull__))
+int cxBufferGet(CxBuffer *buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_BUFFER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/collection.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,147 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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.
+ */
+/**
+ * \file collection.h
+ * \brief Common definitions for various collection implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COLLECTION_H
+#define UCX_COLLECTION_H
+
+#include "allocator.h"
+#include "iterator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Special constant used for creating collections that are storing pointers.
+ */
+#define CX_STORE_POINTERS 0
+
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+        void const *left,
+        void const *right
+);
+
+/**
+ * Use this macro to declare common members for a collection structure.
+ */
+#define CX_COLLECTION_MEMBERS \
+    /** \
+     * The allocator to use. \
+     */ \
+    CxAllocator const *allocator; \
+    /** \
+     * The comparator function for the elements. \
+     */ \
+    cx_compare_func cmpfunc; \
+    /** \
+     * The size of each element. \
+     */ \
+    size_t item_size; \
+    /** \
+     * The number of currently stored elements. \
+     */ \
+    size_t size; \
+    /** \
+     * An optional simple destructor for the collection's elements. \
+     * \
+     * @attention Read the documentation of the particular collection implementation \
+     * whether this destructor shall only destroy the contents or also free the memory. \
+     */ \
+    cx_destructor_func simple_destructor; \
+    /** \
+     * An optional advanced destructor for the collection's elements. \
+     * \
+     * @attention Read the documentation of the particular collection implementation \
+     * whether this destructor shall only destroy the contents or also free the memory. \
+     */ \
+    cx_destructor_func2 advanced_destructor; \
+    /** \
+     * The pointer to additional data that is passed to the advanced destructor. \
+     */ \
+    void *destructor_data; \
+    /** \
+     * Indicates if this instance of a collection is supposed to store pointers \
+     * instead of copies of the actual objects. \
+     */ \
+    bool store_pointer;
+
+/**
+ * Invokes the simple destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_simple_destructor(c, e) \
+    (c)->simple_destructor((c)->store_pointer ? (*((void **) (e))) : (e))
+
+/**
+ * Invokes the advanced destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_advanced_destructor(c, e) \
+    (c)->advanced_destructor((c)->destructor_data, \
+    (c)->store_pointer ? (*((void **) (e))) : (e))
+
+
+/**
+ * Invokes all available destructor functions for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_destructor(c, e) \
+    if ((c)->simple_destructor) cx_invoke_simple_destructor(c,e); \
+    if ((c)->advanced_destructor) cx_invoke_advanced_destructor(c,e)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_COLLECTION_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/common.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,141 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, 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 UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   0
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+// Common Includes
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        void const *,
+        size_t,
+        size_t,
+        void *
+);
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+        void *,
+        size_t,
+        size_t,
+        void *
+);
+
+
+// Compiler specific stuff
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#ifdef _MSC_VER
+
+// fix missing ssize_t definition
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+
+// fix missing _Thread_local support
+#define _Thread_local __declspec(thread)
+
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/common.h.orig	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, 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 UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   0
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+#define __attribute__(...) 
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        void const *,
+        size_t,
+        size_t,
+        void *
+);
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+        void *,
+        size_t,
+        size_t,
+        void *
+);
+
+#ifdef _WIN32
+
+#ifdef __MINGW32__
+#include <sys/types.h>
+#endif // __MINGW32__
+
+#else // !_WIN32
+
+#include <sys/types.h>
+
+#endif // _WIN32
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/compare.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,220 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file compare.h
+ * \brief A collection of simple compare functions.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COMPARE_H
+#define UCX_COMPARE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Compares two integers of type int.
+ *
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type long int.
+ *
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type long long.
+ *
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longlong(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int16(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int32(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int64(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned int.
+ *
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned long int.
+ *
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulongint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned long long.
+ *
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulonglong(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint16(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint32(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint64(void const *i1, void const *i2);
+
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int cx_cmp_float(void const *f1, void const *f2);
+
+/**
+ * Compares two real numbers of type double with precision 1e-14.
+ *
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int cx_cmp_double(
+        void const *d1,
+        void const *d2
+);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (intptr_t const*)
+ * @param ptr2 pointer to pointer two (intptr_t const*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_intptr(
+        void const *ptr1,
+        void const *ptr2
+);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (uintptr_t const*)
+ * @param ptr2 pointer to pointer two (uintptr_t const*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_uintptr(
+        void const *ptr1,
+        void const *ptr2
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_COMPARE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_key.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,129 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file hash_key.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_HASH_KEY_H
+#define UCX_HASH_KEY_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for a key within a hash map. */
+struct cx_hash_key_s {
+    /** The key data. */
+    void const *data;
+    /**
+     * The key data length.
+     */
+    size_t len;
+    /** The hash value of the key data. */
+    unsigned hash;
+};
+
+/**
+ * Type for a hash key.
+ */
+typedef struct cx_hash_key_s CxHashKey;
+
+/**
+ * Computes a murmur2 32 bit hash.
+ *
+ * You need to initialize \c data and \c len in the key struct.
+ * The hash is then directly written to that struct.
+ *
+ * \note If \c data is \c NULL, the hash is defined as 1574210520.
+ *
+ * @param key the key, the hash shall be computed for
+ */
+void cx_hash_murmur(CxHashKey *key);
+
+/**
+ * Computes a hash key from a string.
+ *
+ * The string needs to be zero-terminated.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_str(char const *str);
+
+/**
+ * Computes a hash key from a byte array.
+ *
+ * @param bytes the array
+ * @param len the length
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_bytes(
+        unsigned char const *bytes,
+        size_t len
+);
+
+/**
+ * Computes a hash key for an arbitrary object.
+ *
+ * The computation uses the in-memory representation that might not be
+ * the same on different platforms. Therefore, this hash should not be
+ * used for data exchange with different machines.
+ *
+ * @param obj a pointer to an arbitrary object
+ * @param len the length of object in memory
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key(
+        void const *obj,
+        size_t len
+);
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_KEY_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_map.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,134 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file hash_map.h
+ * \brief Hash map implementation.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_HASH_MAP_H
+#define UCX_HASH_MAP_H
+
+#include "map.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for an element of a hash map. */
+struct cx_hash_map_element_s;
+
+/**
+ * Internal structure for a hash map.
+ */
+struct cx_hash_map_s {
+    /**
+     * Base structure for maps.
+     */
+    struct cx_map_s base;
+    /**
+     * The buckets of this map, each containing a linked list of elements.
+     */
+    struct cx_hash_map_element_s **buckets;
+    /**
+     * The number of buckets.
+     */
+    size_t bucket_count;
+};
+
+
+/**
+ * Creates a new hash map with the specified number of buckets.
+ *
+ * If \p buckets is zero, an implementation defined default will be used.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param allocator the allocator to use
+ * @param itemsize the size of one element
+ * @param buckets the initial number of buckets in this hash map
+ * @return a pointer to the new hash map
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMap *cxHashMapCreate(
+        CxAllocator const *allocator,
+        size_t itemsize,
+        size_t buckets
+);
+
+/**
+ * Creates a new hash map with a default number of buckets.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param itemsize the size of one element
+ * @return a pointer to the new hash map
+ */
+#define cxHashMapCreateSimple(itemsize) \
+    cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+
+/**
+ * Increases the number of buckets, if necessary.
+ *
+ * The load threshold is \c 0.75*buckets. If the element count exceeds the load
+ * threshold, the map will be rehashed. Otherwise, no action is performed and
+ * this function simply returns 0.
+ *
+ * The rehashing process ensures, that the number of buckets is at least
+ * 2.5 times the element count. So there is enough room for additional
+ * elements without the need of another soon rehashing.
+ *
+ * You can use this function after filling a map to increase access performance.
+ *
+ * @note If the specified map is not a hash map, the behavior is undefined.
+ *
+ * @param map the map to rehash
+ * @return zero on success, non-zero if a memory allocation error occurred
+ */
+__attribute__((__nonnull__))
+int cxMapRehash(CxMap *map);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/iterator.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,263 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file iterator.h
+ * \brief Interface for iterator implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_ITERATOR_H
+#define UCX_ITERATOR_H
+
+#include "common.h"
+
+/**
+ * The base of mutating and non-mutating iterators.
+ */
+struct cx_iterator_base_s {
+    /**
+     * True iff the iterator points to valid data.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*valid)(void const *);
+
+    /**
+     * Returns a pointer to the current element.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current)(void const *);
+
+    /**
+     * Original implementation in case the function needs to be wrapped.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current_impl)(void const *);
+
+    /**
+     * Advances the iterator.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void (*next)(void *);
+
+    /**
+     * Flag current element for removal, if possible.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*flag_removal)(void *);
+
+    /**
+     * Indicates whether this iterator may remove elements.
+     */
+    bool mutating;
+
+    /**
+     * Internal flag for removing the current element when advancing.
+     */
+    bool remove;
+};
+
+/**
+ * Internal iterator struct - use CxMutIterator.
+ */
+struct cx_mut_iterator_s {
+
+    /**
+     * The base properties of this iterator.
+     */
+    struct cx_iterator_base_s base;
+
+    /**
+     * Handle for the current element, if required.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    void *src_handle;
+
+    /**
+     * Field for storing a key-value pair.
+     * May be used by iterators that iterate over k/v-collections.
+     */
+    struct {
+        /**
+         * A pointer to the key.
+         */
+        void const *key;
+        /**
+         * A pointer to the value.
+         */
+        void *value;
+    } kv_data;
+
+    /**
+     * Field for storing a slot number.
+     * May be used by iterators that iterate over multi-bucket collections.
+     */
+    size_t slot;
+
+    /**
+     * If the iterator is position-aware, contains the index of the element in the underlying collection.
+     * Otherwise, this field is usually uninitialized.
+     */
+    size_t index;
+};
+
+/**
+ * Mutating iterator value type.
+ *
+ * An iterator points to a certain element in an (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that iterator. However, if the
+ * iterator is based on a collection and the underlying collection is mutated by other means than this iterator
+ * (e.g. elements added or removed), the iterator becomes invalid (regardless of what cxIteratorValid() returns)
+ * and MUST be re-obtained from the collection.
+ *
+ * @see CxIterator
+ */
+typedef struct cx_mut_iterator_s CxMutIterator;
+
+/**
+ * Internal iterator struct - use CxIterator.
+ */
+struct cx_iterator_s {
+
+    /**
+     * The base properties of this iterator.
+     */
+    struct cx_iterator_base_s base;
+
+    /**
+     * Handle for the current element, if required.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    void const *src_handle;
+
+    /**
+     * Field for storing a key-value pair.
+     * May be used by iterators that iterate over k/v-collections.
+     */
+    struct {
+        /**
+         * A pointer to the key.
+         */
+        void const *key;
+        /**
+         * A pointer to the value.
+         */
+        void *value;
+    } kv_data;
+
+    /**
+     * Field for storing a slot number.
+     * May be used by iterators that iterate over multi-bucket collections.
+     */
+    size_t slot;
+
+    /**
+     * If the iterator is position-aware, contains the index of the element in the underlying collection.
+     * Otherwise, this field is usually uninitialized.
+     */
+    size_t index;
+};
+
+/**
+ * Iterator value type.
+ * An iterator points to a certain element in a (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
+ * this iterator cannot mutate the collection itself (add or remove elements) and any mutation of the
+ * collection by other means makes this iterator invalid (regardless of what cxIteratorValid() returns).
+ *
+ * @see CxMutIterator
+ */
+typedef struct cx_iterator_s CxIterator;
+
+/**
+ * Checks if the iterator points to valid data.
+ *
+ * This is especially false for past-the-end iterators.
+ *
+ * @param iter the iterator
+ * @return true iff the iterator points to valid data
+ */
+#define cxIteratorValid(iter) (iter).base.valid(&(iter))
+
+/**
+ * Returns a pointer to the current element.
+ *
+ * The behavior is undefined if this iterator is invalid.
+ *
+ * @param iter the iterator
+ * @return a pointer to the current element
+ */
+#define cxIteratorCurrent(iter) (iter).base.current(&iter)
+
+/**
+ * Advances the iterator to the next element.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorNext(iter) (iter).base.next(&iter)
+
+/**
+ * Flags the current element for removal.
+ *
+ * @param iter the iterator
+ * @return false if this iterator cannot remove the element
+ */
+#define cxIteratorFlagRemoval(iter) (iter).base.flag_removal(&iter)
+
+/**
+ * Loops over an iterator.
+ * @param type the type of the elements
+ * @param elem the name of the iteration variable
+ * @param iter the iterator
+ */
+#define cx_foreach(type, elem, iter) \
+for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter))
+
+#endif // UCX_ITERATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/linked_list.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,415 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file linked_list.h
+ * \brief Linked list implementation.
+ * \details Also provides several low-level functions for custom linked list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LINKED_LIST_H
+#define UCX_LINKED_LIST_H
+
+#include "common.h"
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Set this flag to true, if you want to disable the use of SBO for
+ * linked list swap operations.
+ */
+extern bool CX_DISABLE_LINKED_LIST_SWAP_SBO;
+
+/**
+ * Allocates a linked list for storing elements with \p item_size bytes each.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param allocator the allocator for allocating the list nodes
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL sort and find functions will not work)
+ * @param item_size the size of each element in bytes
+ * @return the created list
+ */
+CxList *cxLinkedListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size
+);
+
+/**
+ * Allocates a linked list for storing elements with \p item_size bytes each.
+ *
+ * The list will use cxDefaultAllocator and no comparator function. If you want
+ * to call functions that need a comparator, you must either set one immediately
+ * after list creation or use cxLinkedListCreate().
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param item_size the size of each element in bytes
+ * @return the created list
+ */
+#define cxLinkedListCreateSimple(item_size) \
+    cxLinkedListCreate(NULL, NULL, item_size)
+
+/**
+ * Finds the node at a certain index.
+ *
+ * This function can be used to start at an arbitrary position within the list.
+ * If the search index is large than the start index, \p loc_advance must denote
+ * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * But it is also possible that the search index is smaller than the start index
+ * (e.g. in cases where traversing a list backwards is faster) in which case
+ * \p loc_advance must denote the location of some sort of \c prev pointer
+ * (i.e. a pointer to the previous node).
+ *
+ * @param start a pointer to the start node
+ * @param start_index the start index
+ * @param loc_advance the location of the pointer to advance
+ * @param index the search index
+ * @return the node found at the specified index
+ */
+void *cx_linked_list_at(
+        void const *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the index of an element within a linked list.
+ *
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+ssize_t cx_linked_list_find(
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the first node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a prev pointer whose location within the node struct is
+ * denoted by \p loc_prev.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_prev the location of the \c prev pointer
+ * @return a pointer to the first node
+ */
+void *cx_linked_list_first(
+        void const *node,
+        ptrdiff_t loc_prev
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the last node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a next pointer whose location within the node struct is
+ * denoted by \p loc_next.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_next the location of the \c next pointer
+ * @return a pointer to the last node
+ */
+void *cx_linked_list_last(
+        void const *node,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the predecessor of a node in case it is not linked.
+ *
+ * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ *
+ * @param begin the node where to start the search
+ * @param loc_next the location of the \c next pointer
+ * @param node the successor of the node to find
+ * @return the node or \c NULL if \p node has no predecessor
+ */
+void *cx_linked_list_prev(
+        void const *begin,
+        ptrdiff_t loc_next,
+        void const *node
+) __attribute__((__nonnull__));
+
+/**
+ * Adds a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be appended
+ */
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Prepends a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be prepended
+ */
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Links two nodes.
+ *
+ * @param left the new predecessor of \p right
+ * @param right the new successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Unlinks two nodes.
+ *
+ * If right is not the successor of left, the behavior is undefined.
+ *
+ * @param left the predecessor of \p right
+ * @param right the successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Inserts a new node after a given node of a linked list.
+ * The new node must not be part of any list already.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
+ * @param new_node a pointer to the node that shall be prepended
+ */
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+) __attribute__((__nonnull__(6)));
+
+/**
+ * Inserts a chain of nodes after a given node of a linked list.
+ * The chain must not be part of any list already.
+ *
+ * If you do not explicitly specify the end of the chain, it will be determined by traversing
+ * the \c next pointer.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need
+ * to provide a valid \p loc_prev location.
+ * Then the chain will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL to prepend the chain to the list)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
+ */
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+) __attribute__((__nonnull__(6)));
+
+/**
+ * Removes a node from the linked list.
+ *
+ * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * addresses are provided, the pointers are adjusted accordingly.
+ *
+ * The following combinations of arguments are valid (more arguments are optional):
+ * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ *
+ * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used
+ * to traverse to a former adjacent node in the list.
+ *
+ * @param begin a pointer to the begin node pointer (optional)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node to remove
+ */
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) __attribute__((__nonnull__(5)));
+
+
+/**
+ * Determines the size of a linked list starting with \p node.
+ * @param node the first node
+ * @param loc_next the location of the \c next pointer within the node struct
+ * @return the size of the list or zero if \p node is \c NULL
+ */
+size_t cx_linked_list_size(
+        void const *node,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Sorts a linked list based on a comparison function.
+ *
+ * This function can work with linked lists of the following structure:
+ * \code
+ * typedef struct node node;
+ * struct node {
+ *   node* prev;
+ *   node* next;
+ *   my_payload data;
+ * }
+ * \endcode
+ *
+ * @note This is a recursive function with at most logarithmic recursion depth.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the compare function defining the sort order
+ */
+void cx_linked_list_sort(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) __attribute__((__nonnull__(1, 6)));
+
+
+/**
+ * Compares two lists element wise.
+ *
+ * \note Both list must have the same structure.
+ *
+ * @param begin_left the begin of the left list (\c NULL denotes an empty list)
+ * @param begin_right the begin of the right list  (\c NULL denotes an empty list)
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the function to compare the elements
+ * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the
+ * right list, positive if the left list is larger than the right list, zero if both lists are equal.
+ */
+int cx_linked_list_compare(
+        void const *begin_left,
+        void const *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Reverses the order of the nodes in a linked list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__(1)));
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LINKED_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/list.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,650 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file list.h
+ * \brief Interface for list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LIST_H
+#define UCX_LIST_H
+
+#include "common.h"
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * List class type.
+ */
+typedef struct cx_list_class_s cx_list_class;
+
+/**
+ * Structure for holding the base data of a list.
+ */
+struct cx_list_s {
+    CX_COLLECTION_MEMBERS
+    /**
+     * The list class definition.
+     */
+    cx_list_class const *cl;
+    /**
+     * The actual implementation in case the list class is delegating.
+     */
+    cx_list_class const *climpl;
+};
+
+/**
+ * The class definition for arbitrary lists.
+ */
+struct cx_list_class_s {
+    /**
+     * Destructor function.
+     *
+     * Implementations SHALL invoke the content destructor functions if provided
+     * and SHALL deallocate the list memory, if an allocator is provided.
+     */
+    void (*destructor)(struct cx_list_s *list);
+
+    /**
+     * Member function for inserting a single element.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     */
+    int (*insert_element)(
+            struct cx_list_s *list,
+            size_t index,
+            void const *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     */
+    size_t (*insert_array)(
+            struct cx_list_s *list,
+            size_t index,
+            void const *data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting an element relative to an iterator position.
+     */
+    int (*insert_iter)(
+            struct cx_mut_iterator_s *iter,
+            void const *elem,
+            int prepend
+    );
+
+    /**
+     * Member function for removing an element.
+     */
+    int (*remove)(
+            struct cx_list_s *list,
+            size_t index
+    );
+
+    /**
+     * Member function for removing all elements.
+     */
+    void (*clear)(struct cx_list_s *list);
+
+    /**
+     * Member function for swapping two elements.
+     */
+    int (*swap)(
+            struct cx_list_s *list,
+            size_t i,
+            size_t j
+    );
+
+    /**
+     * Member function for element lookup.
+     */
+    void *(*at)(
+            struct cx_list_s const *list,
+            size_t index
+    );
+
+    /**
+     * Member function for finding an element.
+     */
+    ssize_t (*find)(
+            struct cx_list_s const *list,
+            void const *elem
+    );
+
+    /**
+     * Member function for sorting the list in-place.
+     */
+    void (*sort)(struct cx_list_s *list);
+
+    /**
+     * Member function for comparing this list to another list of the same type.
+     */
+    int (*compare)(
+            struct cx_list_s const *list,
+            struct cx_list_s const *other
+    );
+
+    /**
+     * Member function for reversing the order of the items.
+     */
+    void (*reverse)(struct cx_list_s *list);
+
+    /**
+     * Member function for returning an iterator pointing to the specified index.
+     */
+    struct cx_iterator_s (*iterator)(
+            struct cx_list_s const *list,
+            size_t index,
+            bool backward
+    );
+};
+
+/**
+ * Common type for all list implementations.
+ */
+typedef struct cx_list_s CxList;
+
+/**
+ * Advises the list to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this list will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param list the list
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+void cxListStoreObjects(CxList *list);
+
+/**
+ * Advises the list to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty list that already stores copies of
+ * objects is undefined.
+ *
+ * @param list the list
+ * @see cxListStoreObjects()
+ */
+__attribute__((__nonnull__))
+void cxListStorePointers(CxList *list);
+
+/**
+ * Returns true, if this list is storing pointers instead of the actual data.
+ *
+ * @param list
+ * @return true, if this list is storing pointers
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxListIsStoringPointers(CxList const *list) {
+    return list->store_pointer;
+}
+
+/**
+ * Returns the number of elements currently stored in the list.
+ *
+ * @param list the list
+ * @return the number of currently stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListSize(CxList const *list) {
+    return list->size;
+}
+
+/**
+ * Adds an item to the end of the list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListAddArray()
+ */
+__attribute__((__nonnull__))
+static inline int cxListAdd(
+        CxList *list,
+        void const *elem
+) {
+    return list->cl->insert_element(list, list->size, elem);
+}
+
+/**
+ * Adds multiple items to the end of the list.
+ *
+ * This method is more efficient than invoking cxListAdd() multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListAddArray(
+        CxList *list,
+        void const *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, list->size, array, n);
+}
+
+/**
+ * Inserts an item at the specified index.
+ *
+ * If \p index equals the list \c size, this is effectively cxListAdd().
+ *
+ * @param list the list
+ * @param index the index the element shall have
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * or when the index is out of bounds
+ * @see cxListInsertAfter()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsert(
+        CxList *list,
+        size_t index,
+        void const *elem
+) {
+    return list->cl->insert_element(list, index, elem);
+}
+
+/**
+ * Inserts multiple items to the list at the specified index.
+ * If \p index equals the list size, this is effectively cxListAddArray().
+ *
+ * This method is usually more efficient than invoking cxListInsert()
+ * multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param index the index where to add the elements
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertArray(
+        CxList *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, index, array, n);
+}
+
+/**
+ * Inserts an element after the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertAfter(
+        CxMutIterator *iter,
+        void const *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 0);
+}
+
+/**
+ * Inserts an element before the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertAfter()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertBefore(
+        CxMutIterator *iter,
+        void const *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 1);
+}
+
+/**
+ * Removes the element at the specified index.
+ *
+ * If an element destructor function is specified, it is called before
+ * removing the element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return zero on success, non-zero if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListRemove(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->remove(list, index);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If an element destructor function is specified, it is called for each
+ * element before removing them.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListClear(CxList *list) {
+    list->cl->clear(list);
+}
+
+/**
+ * Swaps two items in the list.
+ *
+ * Implementations should only allocate temporary memory for the swap, if
+ * it is necessary.
+ *
+ * @param list the list
+ * @param i the index of the first element
+ * @param j the index of the second element
+ * @return zero on success, non-zero if one of the indices is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListSwap(
+        CxList *list,
+        size_t i,
+        size_t j
+) {
+    return list->cl->swap(list, i, j);
+}
+
+/**
+ * Returns a pointer to the element at the specified index.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return a pointer to the element or \c NULL if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline void *cxListAt(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->at(list, index);
+}
+
+/**
+ * Returns an iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIteratorAt(
+        CxList const *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, false);
+}
+
+/**
+ * Returns a backwards iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIteratorAt(
+        CxList const *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, true);
+}
+
+/**
+ * Returns a mutating iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns a mutating backwards iterator pointing to the item at the
+ * specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns an iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIterator(CxList const *list) {
+    return list->cl->iterator(list, 0, false);
+}
+
+/**
+ * Returns a mutating iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxMutIterator cxListMutIterator(CxList *list) {
+    return cxListMutIteratorAt(list, 0);
+}
+
+
+/**
+ * Returns a backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIterator(CxList const *list) {
+    return list->cl->iterator(list, list->size - 1, true);
+}
+
+/**
+ * Returns a mutating backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxMutIterator cxListMutBackwardsIterator(CxList *list) {
+    return cxListMutBackwardsIteratorAt(list, list->size - 1);
+}
+
+/**
+ * Returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find
+ * @return the index of the element or a negative
+ * value when the element is not found
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFind(
+        CxList const *list,
+        void const *elem
+) {
+    return list->cl->find(list, elem);
+}
+
+/**
+ * Sorts the list in-place.
+ *
+ * \remark The underlying sort algorithm is implementation defined.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListSort(CxList *list) {
+    list->cl->sort(list);
+}
+
+/**
+ * Reverses the order of the items.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListReverse(CxList *list) {
+    list->cl->reverse(list);
+}
+
+/**
+ * Compares a list to another list of the same type.
+ *
+ * First, the list sizes are compared.
+ * If they match, the lists are compared element-wise.
+ *
+ * @param list the list
+ * @param other the list to compare to
+ * @return zero, if both lists are equal element wise,
+ * negative if the first list is smaller, positive if the first list is larger
+ */
+__attribute__((__nonnull__))
+int cxListCompare(
+        CxList const *list,
+        CxList const *other
+);
+
+/**
+ * Deallocates the memory of the specified list structure.
+ *
+ * Also calls content a destructor function, depending on the configuration
+ * in CxList.content_destructor_type.
+ *
+ * This function itself is a destructor function for the CxList.
+ *
+ * @param list the list which shall be destroyed
+ */
+__attribute__((__nonnull__))
+void cxListDestroy(CxList *list);
+
+/**
+ * A shared instance of an empty list.
+ *
+ * Writing to that list is undefined.
+ */
+extern CxList * const cxEmptyList;
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/map.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1134 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file map.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MAP_H
+#define UCX_MAP_H
+
+#include "common.h"
+#include "collection.h"
+#include "string.h"
+#include "hash_key.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/** Type for the UCX map. */
+typedef struct cx_map_s CxMap;
+
+/** Type for a map entry. */
+typedef struct cx_map_entry_s CxMapEntry;
+
+/** Type for map class definitions. */
+typedef struct cx_map_class_s cx_map_class;
+
+/** Structure for the UCX map. */
+struct cx_map_s {
+    CX_COLLECTION_MEMBERS
+    /** The map class definition. */
+    cx_map_class *cl;
+};
+
+/**
+ * The type of iterator for a map.
+ */
+enum cx_map_iterator_type {
+    /**
+     * Iterates over key/value pairs.
+     */
+    CX_MAP_ITERATOR_PAIRS,
+    /**
+     * Iterates over keys only.
+     */
+    CX_MAP_ITERATOR_KEYS,
+    /**
+     * Iterates over values only.
+     */
+    CX_MAP_ITERATOR_VALUES
+};
+
+/**
+ * The class definition for arbitrary maps.
+ */
+struct cx_map_class_s {
+    /**
+     * Deallocates the entire memory.
+     */
+    __attribute__((__nonnull__))
+    void (*destructor)(struct cx_map_s *map);
+
+    /**
+     * Removes all elements.
+     */
+    __attribute__((__nonnull__))
+    void (*clear)(struct cx_map_s *map);
+
+    /**
+     * Add or overwrite an element.
+     */
+    __attribute__((__nonnull__))
+    int (*put)(
+            CxMap *map,
+            CxHashKey key,
+            void *value
+    );
+
+    /**
+     * Returns an element.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    void *(*get)(
+            CxMap const *map,
+            CxHashKey key
+    );
+
+    /**
+     * Removes an element.
+     */
+    __attribute__((__nonnull__))
+    void *(*remove)(
+            CxMap *map,
+            CxHashKey key,
+            bool destroy
+    );
+
+    /**
+     * Creates an iterator for this map.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    CxIterator (*iterator)(CxMap const *map, enum cx_map_iterator_type type);
+};
+
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+    /**
+     * A pointer to the key.
+     */
+    CxHashKey const *key;
+    /**
+     * A pointer to the value.
+     */
+    void *value;
+};
+
+/**
+ * A shared instance of an empty map.
+ *
+ * Writing to that map is undefined.
+ */
+extern CxMap *const cxEmptyMap;
+
+/**
+ * Advises the map to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this map will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param map the map
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStoreObjects(CxMap *map) {
+    map->store_pointer = false;
+}
+
+/**
+ * Advises the map to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty map that already stores copies of
+ * objects is undefined.
+ *
+ * @param map the map
+ * @see cxMapStoreObjects()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStorePointers(CxMap *map) {
+    map->store_pointer = true;
+    map->item_size = sizeof(void *);
+}
+
+
+/**
+ * Deallocates the memory of the specified map.
+ *
+ * @param map the map to be destroyed
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDestroy(CxMap *map) {
+    map->cl->destructor(map);
+}
+
+
+/**
+ * Clears a map by removing all elements.
+ *
+ * @param map the map to be cleared
+ */
+__attribute__((__nonnull__))
+static inline void cxMapClear(CxMap *map) {
+    map->cl->clear(map);
+}
+
+
+// TODO: set-like map operations (union, intersect, difference)
+
+/**
+ * Creates a value iterator for a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorValues(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+}
+
+/**
+ * Creates a key iterator for a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorKeys(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+}
+
+/**
+ * Creates an iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapIteratorKeys()
+ * @see cxMapIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIterator(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+}
+
+
+/**
+ * Creates a mutating iterator over the values of a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxMapMutIteratorValues(CxMap *map);
+
+/**
+ * Creates a mutating iterator over the keys of a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxMapMutIteratorKeys(CxMap *map);
+
+/**
+ * Creates a mutating iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapMutIteratorKeys()
+ * @see cxMapMutIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxMapMutIterator(CxMap *map);
+
+#ifdef __cplusplus
+} // end the extern "C" block here, because we want to start overloading
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        CxHashKey const &key,
+        void *value
+) {
+    return map->cl->put(map, key, value);
+}
+
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxstring const &key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxmutstr const &key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+        CxMap *map,
+        char const *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        CxHashKey const &key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        cxstring const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        cxmutstr const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        char const *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        CxHashKey const &key
+) {
+    (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        cxstring const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        CxHashKey const &key
+) {
+    (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        cxstring const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        char const *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
+}
+
+#else // __cplusplus
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    return map->cl->put(map, key, value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_cxstr(
+        CxMap *map,
+        cxstring key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_mustr(
+        CxMap *map,
+        cxmutstr key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_str(
+        CxMap *map,
+        char const *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+#define cxMapPut(map, key, value) _Generic((key), \
+    CxHashKey: cx_map_put,                        \
+    cxstring: cx_map_put_cxstr,                   \
+    cxmutstr: cx_map_put_mustr,                   \
+    char*: cx_map_put_str,                        \
+    char const*: cx_map_put_str)                  \
+    (map, key, value)
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get(
+        CxMap const *map,
+        CxHashKey key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_cxstr(
+        CxMap const *map,
+        cxstring key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_mustr(
+        CxMap const *map,
+        cxmutstr key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_str(
+        CxMap const *map,
+        char const *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+#define cxMapGet(map, key) _Generic((key), \
+    CxHashKey: cx_map_get,                 \
+    cxstring: cx_map_get_cxstr,            \
+    cxmutstr: cx_map_get_mustr,            \
+    char*: cx_map_get_str,                 \
+    char const*: cx_map_get_str)           \
+    (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_str(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+#define cxMapRemove(map, key) _Generic((key), \
+    CxHashKey: cx_map_remove,                 \
+    cxstring: cx_map_remove_cxstr,            \
+    cxmutstr: cx_map_remove_mustr,            \
+    char*: cx_map_remove_str,                 \
+    char const*: cx_map_remove_str)           \
+    (map, key)
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_str(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+#define cxMapDetach(map, key) _Generic((key), \
+    CxHashKey: cx_map_detach,                 \
+    cxstring: cx_map_detach_cxstr,            \
+    cxmutstr: cx_map_detach_mustr,            \
+    char*: cx_map_detach_str,                 \
+    char const*: cx_map_detach_str)           \
+    (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_str(
+        CxMap *map,
+        char const *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+#define cxMapRemoveAndGet(map, key) _Generic((key), \
+    CxHashKey: cx_map_remove_and_get,               \
+    cxstring: cx_map_remove_and_get_cxstr,          \
+    cxmutstr: cx_map_remove_and_get_mustr,          \
+    char*: cx_map_remove_and_get_str,               \
+    char const*: cx_map_remove_and_get_str)         \
+    (map, key)
+
+#endif // __cplusplus
+
+#endif // UCX_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/mempool.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,149 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file mempool.h
+ * \brief Interface for memory pool implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define UCX_MEMPOOL_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for pooled memory. */
+struct cx_mempool_memory_s;
+
+/**
+ * The basic structure of a memory pool.
+ * Should be the first member of an actual memory pool implementation.
+ */
+struct cx_mempool_s {
+    /** The provided allocator. */
+    CxAllocator const *allocator;
+
+    /**
+     * A destructor that shall be automatically registered for newly allocated memory.
+     * This destructor MUST NOT free the memory.
+     */
+    cx_destructor_func auto_destr;
+
+    /** Array of pooled memory. */
+    struct cx_mempool_memory_s **data;
+
+    /** Number of pooled memory items. */
+    size_t size;
+
+    /** Memory pool capacity. */
+    size_t capacity;
+};
+
+/**
+ * Common type for all memory pool implementations.
+ */
+typedef struct cx_mempool_s CxMempool;
+
+/**
+ * Creates an array-based memory pool with a shared destructor function.
+ *
+ * This destructor MUST NOT free the memory.
+ *
+ * @param capacity the initial capacity of the pool
+ * @param destr the destructor function to use for allocated memory
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
+
+/**
+ * Creates a basic array-based memory pool.
+ *
+ * @param capacity the initial capacity of the pool
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+static inline CxMempool *cxBasicMempoolCreate(size_t capacity) {
+    return cxMempoolCreate(capacity, NULL);
+}
+
+/**
+ * Destroys a memory pool and frees the managed memory.
+ *
+ * @param pool the memory pool to destroy
+ */
+__attribute__((__nonnull__))
+void cxMempoolDestroy(CxMempool *pool);
+
+/**
+ * Sets the destructor function for a specific allocated memory object.
+ *
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ * @param fnc the destructor function
+ */
+__attribute__((__nonnull__))
+void cxMempoolSetDestructor(
+        void *memory,
+        cx_destructor_func fnc
+);
+
+/**
+ * Registers foreign memory with this pool.
+ *
+ * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ *
+ * A small portion of memory will be allocated to register the information in the pool.
+ * If that allocation fails, this function will return non-zero.
+ *
+ * @param pool the pool
+ * @param memory the object allocated in the pool
+ * @param destr the destructor function
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxMempoolRegister(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func destr
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_MEMPOOL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/printf.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,166 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file printf.h
+ * \brief Wrapper for write functions with a printf-like interface.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PRINTF_H
+#define UCX_PRINTF_H
+
+#include "common.h"
+#include "string.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A \c fprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ */
+__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        ...
+);
+
+/**
+ * A \c vfprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ap argument list
+ * @return the total number of bytes written
+ * @see cx_fprintf()
+ */
+__attribute__((__nonnull__))
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        va_list ap
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree_a()
+ */
+__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cxmutstr cx_asprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        ...
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree()
+ */
+#define cx_asprintf(fmt, ...) \
+    cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf_a()
+ */
+__attribute__((__nonnull__))
+cxmutstr cx_vasprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        va_list ap
+);
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf()
+ */
+#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap)
+
+/**
+ * A \c printf like function which writes the output to a CxBuffer.
+ *
+ * @param buffer a pointer to the buffer the data is written to
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
+    (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_PRINTF_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/string.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,1078 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+/**
+ * \file string.h
+ * \brief Strings that know their length.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STRING_H
+#define UCX_STRING_H
+
+#include "common.h"
+#include "allocator.h"
+
+/**
+ * The UCX string structure.
+ */
+struct cx_mutstr_s {
+    /**
+     * A pointer to the string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * A mutable string.
+ */
+typedef struct cx_mutstr_s cxmutstr;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+struct cx_string_s {
+    /**
+     * A pointer to the immutable string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    char const *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * An immutable string.
+ */
+typedef struct cx_string_s cxstring;
+
+/**
+ * Context for string tokenizing.
+ */
+struct cx_strtok_ctx_s {
+    /**
+     * The string to tokenize.
+     */
+    cxstring str;
+    /**
+     * The primary delimiter.
+     */
+    cxstring delim;
+    /**
+     * Optional array of more delimiters.
+     */
+    cxstring const *delim_more;
+    /**
+     * Length of the array containing more delimiters.
+     */
+    size_t delim_more_count;
+    /**
+     * Position of the currently active token in the source string.
+     */
+    size_t pos;
+    /**
+     * Position of next delimiter in the source string.
+     *
+     * If the tokenizer has not yet returned a token, the content of this field
+     * is undefined. If the tokenizer reached the end of the string, this field
+     * contains the length of the source string.
+     */
+    size_t delim_pos;
+    /**
+     * The position of the next token in the source string.
+     */
+    size_t next_pos;
+    /**
+     * The number of already found tokens.
+     */
+    size_t found;
+    /**
+     * The maximum number of tokens that shall be returned.
+     */
+    size_t limit;
+};
+
+/**
+ * A string tokenizing context.
+ */
+typedef struct cx_strtok_ctx_s CxStrtokCtx;
+
+#ifdef __cplusplus
+extern "C" {
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1}
+
+#else // __cplusplus
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * The argument MUST be a string (const char*) \em literal.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1}
+
+#endif
+
+
+/**
+ * Wraps a mutable string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_str().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_mutstrn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_mutstr(char *cstring);
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_strn().
+ *
+ * @param cstring  the string to wrap (or \c NULL, only if the length is zero)
+ * @param length   the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_mutstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+);
+
+/**
+ * Wraps a string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstr().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_strn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxstring cx_str(char const *cstring);
+
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstrn().
+ *
+ * @param cstring  the string to wrap (or \c NULL, only if the length is zero)
+ * @param length   the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_str()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strn(
+        char const *cstring,
+        size_t length
+);
+
+/**
+* Casts a mutable string to an immutable string.
+*
+* \note This is not seriously a cast. Instead you get a copy
+* of the struct with the desired pointer type. Both structs still
+* point to the same location, though!
+*
+* @param str the mutable string to cast
+* @return an immutable copy of the string pointer
+*/
+__attribute__((__warn_unused_result__))
+cxstring cx_strcast(cxmutstr str);
+
+/**
+ * Passes the pointer in this string to \c free().
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a \c char \c const* you are really supposed to free. If you
+ * encounter such situation, you should double-check your code.
+ *
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree(cxmutstr *str);
+
+/**
+ * Passes the pointer in this string to the allocators free function.
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a \c char \c const* you are really supposed to free. If you
+ * encounter such situation, you should double-check your code.
+ *
+ * @param alloc the allocator
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree_a(
+        CxAllocator const *alloc,
+        cxmutstr *str
+);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ *
+ * \attention if the count argument is larger than the number of the
+ * specified strings, the behavior is undefined.
+ *
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the accumulated length of all strings
+ */
+__attribute__((__warn_unused_result__))
+size_t cx_strlen(
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param str   the string the other strings shall be concatenated to
+ * @param count the number of the other following strings to concatenate
+ * @param ...   all other strings
+ * @return the concatenated string
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strcat_ma(
+        CxAllocator const *alloc,
+        cxmutstr str,
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param count the number of the other following strings to concatenate
+ * @param ...   all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_a(alloc, count, ...) \
+cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param count   the number of the other following strings to concatenate
+ * @param ...     all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat(count, ...) \
+cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param str     the string the other strings shall be concatenated to
+ * @param count   the number of the other following strings to concatenate
+ * @param ...     all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_m(str, count, ...) \
+cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               \p needle, or an empty string, if the sequence is not
+ *               contained
+ * @see cx_strstr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               \p needle, or an empty string, if the sequence is not
+ *               contained
+ * @see cx_strstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_a(
+        CxAllocator const *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+);
+
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_ma(
+        CxAllocator const *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+);
+
+/**
+ * Compares two strings.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcasecmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcmp_p(
+        void const *s1,
+        void const *s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcasecmp_p(
+        void const *s1,
+        void const *s2
+);
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strdup_a(
+        CxAllocator const *allocator,
+        cxstring string
+);
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_a()
+ */
+#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_m()
+ */
+#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_ma()
+ */
+#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strtrim(cxstring string);
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strtrim_m(cxmutstr string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Converts the string to lower case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strlower(cxmutstr string);
+
+/**
+ * Converts the string to upper case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strupper(cxmutstr string);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strreplacen_a(
+        CxAllocator const *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplacen(str, pattern, replacement, replmax) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace_a(allocator, str, pattern, replacement) \
+cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace(str, pattern, replacement) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Creates a string tokenization context.
+ *
+ * @param str the string to tokenize
+ * @param delim the delimiter (must not be empty)
+ * @param limit the maximum number of tokens that shall be returned
+ * @return a new string tokenization context
+ */
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+* Creates a string tokenization context for a mutable string.
+*
+* @param str the string to tokenize
+* @param delim the delimiter (must not be empty)
+* @param limit the maximum number of tokens that shall be returned
+* @return a new string tokenization context
+*/
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+ * Returns the next token.
+ *
+ * The token will point to the source string.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+);
+
+/**
+ * Returns the next token of a mutable string.
+ *
+ * The token will point to the source string.
+ * If the context was not initialized over a mutable string, modifying
+ * the data of the returned token is undefined behavior.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+);
+
+/**
+ * Defines an array of more delimiters for the specified tokenization context.
+ *
+ * @param ctx the tokenization context
+ * @param delim array of more delimiters
+ * @param count number of elements in the array
+ */
+__attribute__((__nonnull__))
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        cxstring const *delim,
+        size_t count
+);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_STRING_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/utils.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,195 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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.
+ */
+
+/**
+ * \file utils.h
+ *
+ * \brief General purpose utility functions.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Convenience macro for a for loop that counts from zero to n-1.
+ */
+#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++)
+
+/**
+ * Convenience macro for swapping two pointers.
+ */
+#ifdef __cplusplus
+#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#else
+#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#endif
+
+// cx_szmul() definition
+
+#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
+#define CX_SZMUL_BUILTIN
+
+/**
+ * Alias for \c __builtin_mul_overflow.
+ *
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
+
+#else // no GNUC or clang bultin
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+  *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t where the result should be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+int cx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif // cx_szmul
+
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
+ * set this to zero to let the implementation decide
+ * @param n the maximum number of bytes that shall be copied.
+ * If this is larger than \p bufsize, the content is copied over multiple
+ * iterations.
+ * @return the total number of bytes copied
+ */
+__attribute__((__nonnull__(1, 2, 3, 4)))
+size_t cx_stream_bncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        char *buf,
+        size_t bufsize,
+        size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
+ * set this to zero to let the implementation decide
+ * @return total number of bytes copied
+ */
+#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \
+    cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX)
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n the maximum number of bytes that shall be copied.
+ * @return total number of bytes copied
+ */
+__attribute__((__nonnull__))
+size_t cx_stream_ncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @return total number of bytes copied
+ */
+#define cx_stream_copy(src, dest, rfnc, wfnc) \
+    cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_UTILS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_key.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/hash_key.h"
+#include <string.h>
+
+void cx_hash_murmur(CxHashKey *key) {
+    unsigned char const *data = key->data;
+    if (data == NULL) {
+        // extension: special value for NULL
+        key->hash = 1574210520u;
+        return;
+    }
+    size_t len = key->len;
+
+    unsigned m = 0x5bd1e995;
+    unsigned r = 24;
+    unsigned h = 25 ^ len;
+    unsigned i = 0;
+    while (len >= 4) {
+        unsigned k = data[i + 0] & 0xFF;
+        k |= (data[i + 1] & 0xFF) << 8;
+        k |= (data[i + 2] & 0xFF) << 16;
+        k |= (data[i + 3] & 0xFF) << 24;
+
+        k *= m;
+        k ^= k >> r;
+        k *= m;
+
+        h *= m;
+        h ^= k;
+
+        i += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+        case 3:
+            h ^= (data[i + 2] & 0xFF) << 16;
+                    __attribute__((__fallthrough__));
+        case 2:
+            h ^= (data[i + 1] & 0xFF) << 8;
+                    __attribute__((__fallthrough__));
+        case 1:
+            h ^= (data[i + 0] & 0xFF);
+            h *= m;
+                    __attribute__((__fallthrough__));
+        default: // do nothing
+            ;
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    key->hash = h;
+}
+
+CxHashKey cx_hash_key_str(char const *str) {
+    CxHashKey key;
+    key.data = str;
+    key.len = str == NULL ? 0 : strlen(str);
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key_bytes(
+        unsigned char const *bytes,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = bytes;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key(
+        void const *obj,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = obj;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_map.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,489 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/hash_map.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <assert.h>
+
+struct cx_hash_map_element_s {
+    /** A pointer to the next element in the current bucket. */
+    struct cx_hash_map_element_s *next;
+
+    /** The corresponding key. */
+    CxHashKey key;
+
+    /** The value data. */
+    char data[];
+};
+
+static void cx_hash_map_clear(struct cx_map_s *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    cx_for_n(i, hash_map->bucket_count) {
+        struct cx_hash_map_element_s *elem = hash_map->buckets[i];
+        if (elem != NULL) {
+            do {
+                struct cx_hash_map_element_s *next = elem->next;
+                // invoke the destructor
+                cx_invoke_destructor(map, elem->data);
+                // free the key data
+                cxFree(map->allocator, (void *) elem->key.data);
+                // free the node
+                cxFree(map->allocator, elem);
+                // proceed
+                elem = next;
+            } while (elem != NULL);
+
+            // do not leave a dangling pointer
+            hash_map->buckets[i] = NULL;
+        }
+    }
+    map->size = 0;
+}
+
+static void cx_hash_map_destructor(struct cx_map_s *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    // free the buckets
+    cx_hash_map_clear(map);
+    cxFree(map->allocator, hash_map->buckets);
+
+    // free the map structure
+    cxFree(map->allocator, map);
+}
+
+static int cx_hash_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    CxAllocator const *allocator = map->allocator;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+
+    while (elm != NULL && elm->key.hash < hash) {
+        prev = elm;
+        elm = elm->next;
+    }
+
+    if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
+        memcmp(elm->key.data, key.data, key.len) == 0) {
+        // overwrite existing element
+        if (map->store_pointer) {
+            memcpy(elm->data, &value, sizeof(void *));
+        } else {
+            memcpy(elm->data, value, map->item_size);
+        }
+    } else {
+        // allocate new element
+        struct cx_hash_map_element_s *e = cxMalloc(
+                allocator,
+                sizeof(struct cx_hash_map_element_s) + map->item_size
+        );
+        if (e == NULL) {
+            return -1;
+        }
+
+        // write the value
+        if (map->store_pointer) {
+            memcpy(e->data, &value, sizeof(void *));
+        } else {
+            memcpy(e->data, value, map->item_size);
+        }
+
+        // copy the key
+        void *kd = cxMalloc(allocator, key.len);
+        if (kd == NULL) {
+            return -1;
+        }
+        memcpy(kd, key.data, key.len);
+        e->key.data = kd;
+        e->key.len = key.len;
+        e->key.hash = hash;
+
+        // insert the element into the linked list
+        if (prev == NULL) {
+            hash_map->buckets[slot] = e;
+        } else {
+            prev->next = e;
+        }
+        e->next = elm;
+
+        // increase the size
+        map->size++;
+    }
+
+    return 0;
+}
+
+static void cx_hash_map_unlink(
+        struct cx_hash_map_s *hash_map,
+        size_t slot,
+        struct cx_hash_map_element_s *prev,
+        struct cx_hash_map_element_s *elm
+) {
+    // unlink
+    if (prev == NULL) {
+        hash_map->buckets[slot] = elm->next;
+    } else {
+        prev->next = elm->next;
+    }
+    // free element
+    cxFree(hash_map->base.allocator, (void *) elm->key.data);
+    cxFree(hash_map->base.allocator, elm);
+    // decrease size
+    hash_map->base.size--;
+}
+
+/**
+ * Helper function to avoid code duplication.
+ *
+ * @param map the map
+ * @param key the key to look up
+ * @param remove flag indicating whether the looked up entry shall be removed
+ * @param destroy flag indicating whether the destructor shall be invoked
+ * @return a pointer to the value corresponding to the key or \c NULL
+ */
+static void *cx_hash_map_get_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool remove,
+        bool destroy
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+    while (elm && elm->key.hash <= hash) {
+        if (elm->key.hash == hash && elm->key.len == key.len) {
+            if (memcmp(elm->key.data, key.data, key.len) == 0) {
+                void *data = NULL;
+                if (destroy) {
+                    cx_invoke_destructor(map, elm->data);
+                } else {
+                    if (map->store_pointer) {
+                        data = *(void **) elm->data;
+                    } else {
+                        data = elm->data;
+                    }
+                }
+                if (remove) {
+                    cx_hash_map_unlink(hash_map, slot, prev, elm);
+                }
+                return data;
+            }
+        }
+        prev = elm;
+        elm = prev->next;
+    }
+
+    return NULL;
+}
+
+static void *cx_hash_map_get(
+        CxMap const *map,
+        CxHashKey key
+) {
+    // we can safely cast, because we know the map stays untouched
+    return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+}
+
+static void *cx_hash_map_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool destroy
+) {
+    return cx_hash_map_get_remove(map, key, true, destroy);
+}
+
+static void *cx_hash_map_iter_current_entry(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    // struct has to have a compatible signature
+    return (struct cx_map_entry_s *) &(iter->kv_data);
+}
+
+static void *cx_hash_map_iter_current_key(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    return &elm->key;
+}
+
+static void *cx_hash_map_iter_current_value(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_hash_map_s const *map = iter->src_handle;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    if (map->base.store_pointer) {
+        return *(void **) elm->data;
+    } else {
+        return elm->data;
+    }
+}
+
+static bool cx_hash_map_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_hash_map_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+
+    // remove current element, if asked
+    if (iter->base.remove) {
+        // obtain mutable pointer to the map
+        struct cx_mut_iterator_s *miter = it;
+        struct cx_hash_map_s *map = miter->src_handle;
+
+        // clear the flag
+        iter->base.remove = false;
+
+        // determine the next element
+        struct cx_hash_map_element_s *next = elm->next;
+
+        // search the previous element
+        struct cx_hash_map_element_s *prev = NULL;
+        if (map->buckets[iter->slot] != elm) {
+            prev = map->buckets[iter->slot];
+            while (prev->next != elm) {
+                prev = prev->next;
+            }
+        }
+
+        // destroy
+        cx_invoke_destructor((struct cx_map_s *) map, elm->data);
+
+        // unlink
+        cx_hash_map_unlink(map, iter->slot, prev, elm);
+
+        // advance
+        elm = next;
+    } else {
+        // just advance
+        elm = elm->next;
+        iter->index++;
+    }
+
+    // search the next bucket, if required
+    struct cx_hash_map_s const *map = iter->src_handle;
+    while (elm == NULL && ++iter->slot < map->bucket_count) {
+        elm = map->buckets[iter->slot];
+    }
+
+    // fill the struct with the next element
+    iter->elem_handle = elm;
+    if (elm == NULL) {
+        iter->kv_data.key = NULL;
+        iter->kv_data.value = NULL;
+    } else {
+        iter->kv_data.key = &elm->key;
+        if (map->base.store_pointer) {
+            iter->kv_data.value = *(void **) elm->data;
+        } else {
+            iter->kv_data.value = elm->data;
+        }
+    }
+}
+
+static bool cx_hash_map_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static CxIterator cx_hash_map_iterator(
+        CxMap const *map,
+        enum cx_map_iterator_type type
+) {
+    CxIterator iter;
+
+    iter.src_handle = map;
+    iter.base.valid = cx_hash_map_iter_valid;
+    iter.base.next = cx_hash_map_iter_next;
+
+    switch (type) {
+        case CX_MAP_ITERATOR_PAIRS:
+            iter.base.current = cx_hash_map_iter_current_entry;
+            break;
+        case CX_MAP_ITERATOR_KEYS:
+            iter.base.current = cx_hash_map_iter_current_key;
+            break;
+        case CX_MAP_ITERATOR_VALUES:
+            iter.base.current = cx_hash_map_iter_current_value;
+            break;
+        default:
+            assert(false);
+    }
+
+    iter.base.flag_removal = cx_hash_map_iter_flag_rm;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    iter.slot = 0;
+    iter.index = 0;
+
+    if (map->size > 0) {
+        struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+        struct cx_hash_map_element_s *elm = hash_map->buckets[0];
+        while (elm == NULL) {
+            elm = hash_map->buckets[++iter.slot];
+        }
+        iter.elem_handle = elm;
+        iter.kv_data.key = &elm->key;
+        if (map->store_pointer) {
+            iter.kv_data.value = *(void **) elm->data;
+        } else {
+            iter.kv_data.value = elm->data;
+        }
+    } else {
+        iter.elem_handle = NULL;
+        iter.kv_data.key = NULL;
+        iter.kv_data.value = NULL;
+    }
+
+    return iter;
+}
+
+static cx_map_class cx_hash_map_class = {
+        cx_hash_map_destructor,
+        cx_hash_map_clear,
+        cx_hash_map_put,
+        cx_hash_map_get,
+        cx_hash_map_remove,
+        cx_hash_map_iterator,
+};
+
+CxMap *cxHashMapCreate(
+        CxAllocator const *allocator,
+        size_t itemsize,
+        size_t buckets
+) {
+    if (buckets == 0) {
+        // implementation defined default
+        buckets = 16;
+    }
+
+    struct cx_hash_map_s *map = cxCalloc(allocator, 1,
+                                         sizeof(struct cx_hash_map_s));
+    if (map == NULL) return NULL;
+
+    // initialize hash map members
+    map->bucket_count = buckets;
+    map->buckets = cxCalloc(allocator, buckets,
+                            sizeof(struct cx_hash_map_element_s *));
+    if (map->buckets == NULL) {
+        cxFree(allocator, map);
+        return NULL;
+    }
+
+    // initialize base members
+    map->base.cl = &cx_hash_map_class;
+    map->base.allocator = allocator;
+
+    if (itemsize > 0) {
+        map->base.store_pointer = false;
+        map->base.item_size = itemsize;
+    } else {
+        map->base.store_pointer = true;
+        map->base.item_size = sizeof(void *);
+    }
+
+    return (CxMap *) map;
+}
+
+int cxMapRehash(CxMap *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
+
+        size_t new_bucket_count = (map->size * 5) >> 1;
+        struct cx_hash_map_element_s **new_buckets = cxCalloc(
+                map->allocator,
+                new_bucket_count, sizeof(struct cx_hash_map_element_s *)
+        );
+
+        if (new_buckets == NULL) {
+            return 1;
+        }
+
+        // iterate through the elements and assign them to their new slots
+        cx_for_n(slot, hash_map->bucket_count) {
+            struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+            while (elm != NULL) {
+                struct cx_hash_map_element_s *next = elm->next;
+                size_t new_slot = elm->key.hash % new_bucket_count;
+
+                // find position where to insert
+                struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
+                struct cx_hash_map_element_s *bucket_prev = NULL;
+                while (bucket_next != NULL &&
+                       bucket_next->key.hash < elm->key.hash) {
+                    bucket_prev = bucket_next;
+                    bucket_next = bucket_next->next;
+                }
+
+                // insert
+                if (bucket_prev == NULL) {
+                    elm->next = new_buckets[new_slot];
+                    new_buckets[new_slot] = elm;
+                } else {
+                    bucket_prev->next = elm;
+                    elm->next = bucket_next;
+                }
+
+                // advance
+                elm = next;
+            }
+        }
+
+        // assign result to the map
+        hash_map->bucket_count = new_bucket_count;
+        cxFree(map->allocator, hash_map->buckets);
+        hash_map->buckets = new_buckets;
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/linked_list.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,927 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/linked_list.h"
+#include "cx/utils.h"
+#include <string.h>
+#include <assert.h>
+
+// LOW LEVEL LINKED LIST FUNCTIONS
+
+#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define ll_prev(node) CX_LL_PTR(node, loc_prev)
+#define ll_next(node) CX_LL_PTR(node, loc_next)
+#define ll_advance(node) CX_LL_PTR(node, loc_advance)
+#define ll_data(node) (((char*)(node))+loc_data)
+
+void *cx_linked_list_at(
+        void const *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    size_t i = start_index;
+    void const *cur = start;
+    while (i != index && cur != NULL) {
+        cur = ll_advance(cur);
+        i < index ? i++ : i--;
+    }
+    return (void *) cur;
+}
+
+ssize_t cx_linked_list_find(
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    void const *node = start;
+    ssize_t index = 0;
+    do {
+        void *current = ll_data(node);
+        if (cmp_func(current, elem) == 0) {
+            return index;
+        }
+        node = ll_advance(node);
+        index++;
+    } while (node != NULL);
+    return -1;
+}
+
+void *cx_linked_list_first(
+        void const *node,
+        ptrdiff_t loc_prev
+) {
+    return cx_linked_list_last(node, loc_prev);
+}
+
+void *cx_linked_list_last(
+        void const *node,
+        ptrdiff_t loc_next
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+
+    void const *cur = node;
+    void const *last;
+    do {
+        last = cur;
+    } while ((cur = ll_next(cur)) != NULL);
+
+    return (void *) last;
+}
+
+void *cx_linked_list_prev(
+        void const *begin,
+        ptrdiff_t loc_next,
+        void const *node
+) {
+    assert(begin != NULL);
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    if (begin == node) return NULL;
+    void const *cur = begin;
+    void const *next;
+    while (1) {
+        next = ll_next(cur);
+        if (next == node) return (void *) cur;
+        cur = next;
+    }
+}
+
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    ll_next(left) = right;
+    if (loc_prev >= 0) {
+        ll_prev(right) = left;
+    }
+}
+
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert (loc_next >= 0);
+    assert(ll_next(left) == right);
+    ll_next(left) = NULL;
+    if (loc_prev >= 0) {
+        assert(ll_prev(right) == left);
+        ll_prev(right) = NULL;
+    }
+}
+
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    void *last;
+    if (end == NULL) {
+        assert(begin != NULL);
+        last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next);
+    } else {
+        last = *end;
+    }
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
+}
+
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node);
+}
+
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
+}
+
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+) {
+    // find the end of the chain, if not specified
+    if (insert_end == NULL) {
+        insert_end = cx_linked_list_last(insert_begin, loc_next);
+    }
+
+    // determine the successor
+    void *successor;
+    if (node == NULL) {
+        assert(begin != NULL || (end != NULL && loc_prev >= 0));
+        if (begin != NULL) {
+            successor = *begin;
+            *begin = insert_begin;
+        } else {
+            successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev);
+        }
+    } else {
+        successor = ll_next(node);
+        cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
+    }
+
+    if (successor == NULL) {
+        // the list ends with the new chain
+        if (end != NULL) {
+            *end = insert_end;
+        }
+    } else {
+        cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
+    }
+}
+
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    assert(loc_prev >= 0 || begin != NULL);
+
+    // find adjacent nodes
+    void *next = ll_next(node);
+    void *prev;
+    if (loc_prev >= 0) {
+        prev = ll_prev(node);
+    } else {
+        prev = cx_linked_list_prev(*begin, loc_next, node);
+    }
+
+    // update next pointer of prev node, or set begin
+    if (prev == NULL) {
+        if (begin != NULL) {
+            *begin = next;
+        }
+    } else {
+        ll_next(prev) = next;
+    }
+
+    // update prev pointer of next node, or set end
+    if (next == NULL) {
+        if (end != NULL) {
+            *end = prev;
+        }
+    } else if (loc_prev >= 0) {
+        ll_prev(next) = prev;
+    }
+}
+
+size_t cx_linked_list_size(
+        void const *node,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    size_t size = 0;
+    while (node != NULL) {
+        node = ll_next(node);
+        size++;
+    }
+    return size;
+}
+
+#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
+#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
+#endif
+
+static void cx_linked_list_sort_merge(
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        size_t length,
+        void *ls,
+        void *le,
+        void *re,
+        cx_compare_func cmp_func,
+        void **begin,
+        void **end
+) {
+    void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
+    void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
+                    malloc(sizeof(void *) * length) : sbo;
+    if (sorted == NULL) abort();
+    void *rc, *lc;
+
+    lc = ls;
+    rc = le;
+    size_t n = 0;
+    while (lc && lc != le && rc != re) {
+        if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
+            sorted[n] = lc;
+            lc = ll_next(lc);
+        } else {
+            sorted[n] = rc;
+            rc = ll_next(rc);
+        }
+        n++;
+    }
+    while (lc && lc != le) {
+        sorted[n] = lc;
+        lc = ll_next(lc);
+        n++;
+    }
+    while (rc && rc != re) {
+        sorted[n] = rc;
+        rc = ll_next(rc);
+        n++;
+    }
+
+    // Update pointer
+    if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
+    cx_for_n (i, length - 1) {
+        cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
+    }
+    ll_next(sorted[length - 1]) = NULL;
+
+    *begin = sorted[0];
+    *end = sorted[length-1];
+    if (sorted != sbo) {
+        free(sorted);
+    }
+}
+
+void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    void *lc, *ls, *le, *re;
+
+    // set start node
+    ls = *begin;
+
+    // early exit when this list is empty
+    if (ls == NULL) return;
+
+    // check how many elements are already sorted
+    lc = ls;
+    size_t ln = 1;
+    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
+        lc = ll_next(lc);
+        ln++;
+    }
+    le = ll_next(lc);
+
+    // if first unsorted node is NULL, the list is already completely sorted
+    if (le != NULL) {
+        void *rc;
+        size_t rn = 1;
+        rc = le;
+        // skip already sorted elements
+        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
+            rc = ll_next(rc);
+            rn++;
+        }
+        re = ll_next(rc);
+
+        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+        void *sorted_begin, *sorted_end;
+        cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                  ln + rn, ls, le, re, cmp_func,
+                                  &sorted_begin, &sorted_end);
+
+        // Something left? Sort it!
+        size_t remainder_length = cx_linked_list_size(re, loc_next);
+        if (remainder_length > 0) {
+            void *remainder = re;
+            cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);
+
+            // merge sorted list with (also sorted) remainder
+            cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                      ln + rn + remainder_length,
+                                      sorted_begin, remainder, NULL, cmp_func,
+                                      &sorted_begin, &sorted_end);
+        }
+        *begin = sorted_begin;
+        if (end) *end = sorted_end;
+    }
+}
+
+int cx_linked_list_compare(
+        void const *begin_left,
+        void const *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    void const *left = begin_left, *right = begin_right;
+
+    while (left != NULL && right != NULL) {
+        void const *left_data = ll_data(left);
+        void const *right_data = ll_data(right);
+        int result = cmp_func(left_data, right_data);
+        if (result != 0) return result;
+        left = ll_advance(left);
+        right = ll_advance(right);
+    }
+
+    if (left != NULL) { return 1; }
+    else if (right != NULL) { return -1; }
+    else { return 0; }
+}
+
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+
+    // swap all links
+    void *prev = NULL;
+    void *cur = *begin;
+    while (cur != NULL) {
+        void *next = ll_next(cur);
+
+        ll_next(cur) = prev;
+        if (loc_prev >= 0) {
+            ll_prev(cur) = next;
+        }
+
+        prev = cur;
+        cur = next;
+    }
+
+    // update begin and end
+    if (end != NULL) {
+        *end = *begin;
+    }
+    *begin = prev;
+}
+
+// HIGH LEVEL LINKED LIST IMPLEMENTATION
+
+bool CX_DISABLE_LINKED_LIST_SWAP_SBO = false;
+
+typedef struct cx_linked_list_node cx_linked_list_node;
+struct cx_linked_list_node {
+    cx_linked_list_node *prev;
+    cx_linked_list_node *next;
+    char payload[];
+};
+
+#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
+#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
+#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)
+
+typedef struct {
+    struct cx_list_s base;
+    cx_linked_list_node *begin;
+    cx_linked_list_node *end;
+} cx_linked_list;
+
+static cx_linked_list_node *cx_ll_node_at(
+        cx_linked_list const *list,
+        size_t index
+) {
+    if (index >= list->base.size) {
+        return NULL;
+    } else if (index > list->base.size / 2) {
+        return cx_linked_list_at(list->end, list->base.size - 1, CX_LL_LOC_PREV, index);
+    } else {
+        return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
+    }
+}
+
+static int cx_ll_insert_at(
+        struct cx_list_s *list,
+        cx_linked_list_node *node,
+        void const *elem
+) {
+
+    // create the new new_node
+    cx_linked_list_node *new_node = cxMalloc(list->allocator,
+                                             sizeof(cx_linked_list_node) + list->item_size);
+
+    // sortir if failed
+    if (new_node == NULL) return 1;
+
+    // initialize new new_node
+    new_node->prev = new_node->next = NULL;
+    memcpy(new_node->payload, elem, list->item_size);
+
+    // insert
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_insert_chain(
+            (void **) &ll->begin, (void **) &ll->end,
+            CX_LL_LOC_PREV, CX_LL_LOC_NEXT,
+            node, new_node, new_node
+    );
+
+    // increase the size and return
+    list->size++;
+    return 0;
+}
+
+static size_t cx_ll_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    // out-of bounds and corner case check
+    if (index > list->size || n == 0) return 0;
+
+    // find position efficiently
+    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+    // perform first insert
+    if (0 != cx_ll_insert_at(list, node, array)) {
+        return 1;
+    }
+
+    // is there more?
+    if (n == 1) return 1;
+
+    // we now know exactly where we are
+    node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;
+
+    // we can add the remaining nodes and immedately advance to the inserted node
+    char const *source = array;
+    for (size_t i = 1; i < n; i++) {
+        source += list->item_size;
+        if (0 != cx_ll_insert_at(list, node, source)) {
+            return i;
+        }
+        node = node->next;
+    }
+    return n;
+}
+
+static int cx_ll_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return 1 != cx_ll_insert_array(list, index, element, 1);
+}
+
+static int cx_ll_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = cx_ll_node_at(ll, index);
+
+    // out-of-bounds check
+    if (node == NULL) return 1;
+
+    // element destruction
+    cx_invoke_destructor(list, node->payload);
+
+    // remove
+    cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                          CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+
+    // adjust size
+    list->size--;
+
+    // free and return
+    cxFree(list->allocator, node);
+
+    return 0;
+}
+
+static void cx_ll_clear(struct cx_list_s *list) {
+    if (list->size == 0) return;
+
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = ll->begin;
+    while (node != NULL) {
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_node *next = node->next;
+        cxFree(list->allocator, node);
+        node = next;
+    }
+    ll->begin = ll->end = NULL;
+    list->size = 0;
+}
+
+#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
+#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
+#endif
+
+static int cx_ll_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->size || j >= list->size) return 1;
+    if (i == j) return 0;
+
+    // perform an optimized search that finds both elements in one run
+    cx_linked_list *ll = (cx_linked_list *) list;
+    size_t mid = list->size / 2;
+    size_t left, right;
+    if (i < j) {
+        left = i;
+        right = j;
+    } else {
+        left = j;
+        right = i;
+    }
+    cx_linked_list_node *nleft, *nright;
+    if (left < mid && right < mid) {
+        // case 1: both items left from mid
+        nleft = cx_ll_node_at(ll, left);
+        nright = nleft;
+        for (size_t c = left; c < right; c++) {
+            nright = nright->next;
+        }
+    } else if (left >= mid && right >= mid) {
+        // case 2: both items right from mid
+        nright = cx_ll_node_at(ll, right);
+        nleft = nright;
+        for (size_t c = right; c > left; c--) {
+            nleft = nleft->prev;
+        }
+    } else {
+        // case 3: one item left, one item right
+
+        // chose the closest to begin / end
+        size_t closest;
+        size_t other;
+        size_t diff2boundary = list->size - right - 1;
+        if (left <= diff2boundary) {
+            closest = left;
+            other = right;
+            nleft = cx_ll_node_at(ll, left);
+        } else {
+            closest = right;
+            other = left;
+            diff2boundary = left;
+            nright = cx_ll_node_at(ll, right);
+        }
+
+        // is other element closer to us or closer to boundary?
+        if (right - left <= diff2boundary) {
+            // search other element starting from already found element
+            if (closest == left) {
+                nright = nleft;
+                for (size_t c = left; c < right; c++) {
+                    nright = nright->next;
+                }
+            } else {
+                nleft = nright;
+                for (size_t c = right; c > left; c--) {
+                    nleft = nleft->prev;
+                }
+            }
+        } else {
+            // search other element starting at the boundary
+            if (closest == left) {
+                nright = cx_ll_node_at(ll, other);
+            } else {
+                nleft = cx_ll_node_at(ll, other);
+            }
+        }
+    }
+
+    if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE || CX_DISABLE_LINKED_LIST_SWAP_SBO) {
+        cx_linked_list_node *prev = nleft->prev;
+        cx_linked_list_node *next = nright->next;
+        cx_linked_list_node *midstart = nleft->next;
+        cx_linked_list_node *midend = nright->prev;
+
+        if (prev == NULL) {
+            ll->begin = nright;
+        } else {
+            prev->next = nright;
+        }
+        nright->prev = prev;
+        if (midstart == nright) {
+            // special case: both nodes are adjacent
+            nright->next = nleft;
+            nleft->prev = nright;
+        } else {
+            // likely case: a chain is between the two nodes
+            nright->next = midstart;
+            midstart->prev = nright;
+            midend->next = nleft;
+            nleft->prev = midend;
+        }
+        nleft->next = next;
+        if (next == NULL) {
+            ll->end = nleft;
+        } else {
+            next->prev = nleft;
+        }
+    } else {
+        // swap payloads to avoid relinking
+        char buf[CX_LINKED_LIST_SWAP_SBO_SIZE];
+        memcpy(buf, nleft->payload, list->item_size);
+        memcpy(nleft->payload, nright->payload, list->item_size);
+        memcpy(nright->payload, buf, list->item_size);
+    }
+
+    return 0;
+}
+
+static void *cx_ll_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = cx_ll_node_at(ll, index);
+    return node == NULL ? NULL : node->payload;
+}
+
+static ssize_t cx_ll_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    return cx_linked_list_find(((cx_linked_list *) list)->begin,
+                               CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                               list->cmpfunc, elem);
+}
+
+static void cx_ll_sort(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
+                        CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                        list->cmpfunc);
+}
+
+static void cx_ll_reverse(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT);
+}
+
+static int cx_ll_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    cx_linked_list *left = (cx_linked_list *) list;
+    cx_linked_list *right = (cx_linked_list *) other;
+    return cx_linked_list_compare(left->begin, right->begin,
+                                  CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                                  list->cmpfunc);
+}
+
+static bool cx_ll_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_ll_iter_next(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        itbase->remove = false;
+        struct cx_mut_iterator_s *iter = it;
+        struct cx_list_s *list = iter->src_handle;
+        cx_linked_list *ll = iter->src_handle;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->size--;
+        cxFree(list->allocator, node);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index++;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+    }
+}
+
+static void cx_ll_iter_prev(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        itbase->remove = false;
+        struct cx_mut_iterator_s *iter = it;
+        struct cx_list_s *list = iter->src_handle;
+        cx_linked_list *ll = iter->src_handle;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+        iter->index--;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->size--;
+        cxFree(list->allocator, node);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index--;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+    }
+}
+
+static void *cx_ll_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    cx_linked_list_node *node = iter->elem_handle;
+    return node->payload;
+}
+
+static bool cx_ll_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static CxIterator cx_ll_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    CxIterator iter;
+    iter.index = index;
+    iter.src_handle = list;
+    iter.elem_handle = cx_ll_node_at((cx_linked_list const *) list, index);
+    iter.base.valid = cx_ll_iter_valid;
+    iter.base.current = cx_ll_iter_current;
+    iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
+    iter.base.flag_removal = cx_ll_iter_flag_rm;
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    return iter;
+}
+
+static int cx_ll_insert_iter(
+        CxMutIterator *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    cx_linked_list_node *node = iter->elem_handle;
+    if (node != NULL) {
+        assert(prepend >= 0 && prepend <= 1);
+        cx_linked_list_node *choice[2] = {node, node->prev};
+        int result = cx_ll_insert_at(list, choice[prepend], elem);
+        iter->index += prepend * (0 == result);
+        return result;
+    } else {
+        int result = cx_ll_insert_element(list, list->size, elem);
+        iter->index = list->size;
+        return result;
+    }
+}
+
+static void cx_ll_destructor(CxList *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+
+    cx_linked_list_node *node = ll->begin;
+    while (node) {
+        cx_invoke_destructor(list, node->payload);
+        void *next = node->next;
+        cxFree(list->allocator, node);
+        node = next;
+    }
+
+    cxFree(list->allocator, list);
+}
+
+static cx_list_class cx_linked_list_class = {
+        cx_ll_destructor,
+        cx_ll_insert_element,
+        cx_ll_insert_array,
+        cx_ll_insert_iter,
+        cx_ll_remove,
+        cx_ll_clear,
+        cx_ll_swap,
+        cx_ll_at,
+        cx_ll_find,
+        cx_ll_sort,
+        cx_ll_compare,
+        cx_ll_reverse,
+        cx_ll_iterator,
+};
+
+CxList *cxLinkedListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_linked_list_class;
+    list->base.allocator = allocator;
+    list->base.cmpfunc = comparator;
+
+    if (item_size > 0) {
+        list->base.item_size = item_size;
+    } else {
+        cxListStorePointers((CxList *) list);
+    }
+
+    return (CxList *) list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/list.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,342 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/list.h"
+
+#include <string.h>
+
+// <editor-fold desc="Store Pointers Functionality">
+
+static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
+
+static int cx_pl_cmpfunc(
+        void const *l,
+        void const *r
+) {
+    void *const *lptr = l;
+    void *const *rptr = r;
+    void const *left = lptr == NULL ? NULL : *lptr;
+    void const *right = rptr == NULL ? NULL : *rptr;
+    return cx_pl_cmpfunc_impl(left, right);
+}
+
+static void cx_pl_hack_cmpfunc(struct cx_list_s const *list) {
+    // cast away const - this is the hacky thing
+    struct cx_list_s *l = (struct cx_list_s *) list;
+    cx_pl_cmpfunc_impl = l->cmpfunc;
+    l->cmpfunc = cx_pl_cmpfunc;
+}
+
+static void cx_pl_unhack_cmpfunc(struct cx_list_s const *list) {
+    // cast away const - this is the hacky thing
+    struct cx_list_s *l = (struct cx_list_s *) list;
+    l->cmpfunc = cx_pl_cmpfunc_impl;
+}
+
+static void cx_pl_destructor(struct cx_list_s *list) {
+    list->climpl->destructor(list);
+}
+
+static int cx_pl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return list->climpl->insert_element(list, index, &element);
+}
+
+static size_t cx_pl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    return list->climpl->insert_array(list, index, array, n);
+}
+
+static int cx_pl_insert_iter(
+        struct cx_mut_iterator_s *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    return list->climpl->insert_iter(iter, &elem, prepend);
+}
+
+static int cx_pl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    return list->climpl->remove(list, index);
+}
+
+static void cx_pl_clear(struct cx_list_s *list) {
+    list->climpl->clear(list);
+}
+
+static int cx_pl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    return list->climpl->swap(list, i, j);
+}
+
+static void *cx_pl_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    void **ptr = list->climpl->at(list, index);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static ssize_t cx_pl_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    cx_pl_hack_cmpfunc(list);
+    ssize_t ret = list->climpl->find(list, &elem);
+    cx_pl_unhack_cmpfunc(list);
+    return ret;
+}
+
+static void cx_pl_sort(struct cx_list_s *list) {
+    cx_pl_hack_cmpfunc(list);
+    list->climpl->sort(list);
+    cx_pl_unhack_cmpfunc(list);
+}
+
+static int cx_pl_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    cx_pl_hack_cmpfunc(list);
+    int ret = list->climpl->compare(list, other);
+    cx_pl_unhack_cmpfunc(list);
+    return ret;
+}
+
+static void cx_pl_reverse(struct cx_list_s *list) {
+    list->climpl->reverse(list);
+}
+
+static void *cx_pl_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    void **ptr = iter->base.current_impl(it);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static struct cx_iterator_s cx_pl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards);
+    iter.base.current_impl = iter.base.current;
+    iter.base.current = cx_pl_iter_current;
+    return iter;
+}
+
+static cx_list_class cx_pointer_list_class = {
+        cx_pl_destructor,
+        cx_pl_insert_element,
+        cx_pl_insert_array,
+        cx_pl_insert_iter,
+        cx_pl_remove,
+        cx_pl_clear,
+        cx_pl_swap,
+        cx_pl_at,
+        cx_pl_find,
+        cx_pl_sort,
+        cx_pl_compare,
+        cx_pl_reverse,
+        cx_pl_iterator,
+};
+
+void cxListStoreObjects(CxList *list) {
+    list->store_pointer = false;
+    if (list->climpl != NULL) {
+        list->cl = list->climpl;
+        list->climpl = NULL;
+    }
+}
+
+void cxListStorePointers(CxList *list) {
+    list->item_size = sizeof(void *);
+    list->store_pointer = true;
+    list->climpl = list->cl;
+    list->cl = &cx_pointer_list_class;
+}
+
+// </editor-fold>
+
+// <editor-fold desc="empty list implementation">
+
+static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+    // this is a noop, but MUST be implemented
+}
+
+static void *cx_emptyl_at(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        __attribute__((__unused__)) size_t index
+) {
+    return NULL;
+}
+
+static ssize_t cx_emptyl_find(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        __attribute__((__unused__)) void const *elem
+) {
+    return -1;
+}
+
+static int cx_emptyl_compare(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    if (other->size == 0) return 0;
+    return -1;
+}
+
+static bool cx_emptyl_iter_valid(__attribute__((__unused__)) void const *iter) {
+    return false;
+}
+
+static CxIterator cx_emptyl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        __attribute__((__unused__)) bool backwards
+) {
+    CxIterator iter = {0};
+    iter.src_handle = list;
+    iter.index = index;
+    iter.base.valid = cx_emptyl_iter_valid;
+    return iter;
+}
+
+static cx_list_class cx_empty_list_class = {
+        cx_emptyl_noop,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        cx_emptyl_noop,
+        NULL,
+        cx_emptyl_at,
+        cx_emptyl_find,
+        cx_emptyl_noop,
+        cx_emptyl_compare,
+        cx_emptyl_noop,
+        cx_emptyl_iterator,
+};
+
+CxList cx_empty_list = {
+        NULL,
+        NULL,
+        0,
+        0,
+        NULL,
+        NULL,
+        NULL,
+        false,
+        &cx_empty_list_class,
+        NULL
+};
+
+CxList *const cxEmptyList = &cx_empty_list;
+
+// </editor-fold>
+
+void cxListDestroy(CxList *list) {
+    list->cl->destructor(list);
+}
+
+int cxListCompare(
+        CxList const *list,
+        CxList const *other
+) {
+    if (
+        // if one is storing pointers but the other is not
+        (list->store_pointer ^ other->store_pointer) ||
+
+        // if one class is wrapped but the other is not
+        ((list->climpl == NULL) ^ (other->climpl == NULL)) ||
+
+        // if the resolved compare functions are not the same
+        ((list->climpl != NULL ? list->climpl->compare : list->cl->compare) !=
+         (other->climpl != NULL ? other->climpl->compare : other->cl->compare))
+    ) {
+        // lists are definitely different - cannot use internal compare function
+        if (list->size == other->size) {
+            CxIterator left = cxListIterator(list);
+            CxIterator right = cxListIterator(other);
+            for (size_t i = 0; i < list->size; i++) {
+                void *leftValue = cxIteratorCurrent(left);
+                void *rightValue = cxIteratorCurrent(right);
+                int d = list->cmpfunc(leftValue, rightValue);
+                if (d != 0) {
+                    return d;
+                }
+                cxIteratorNext(left);
+                cxIteratorNext(right);
+            }
+            return 0;
+        } else {
+            return list->size < other->size ? -1 : 1;
+        }
+    } else {
+        // lists are compatible
+        return list->cl->compare(list, other);
+    }
+}
+
+CxMutIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, false);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, true);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/map.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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 "cx/map.h"
+#include <string.h>
+
+// <editor-fold desc="empty map implementation">
+
+static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {
+    // this is a noop, but MUST be implemented
+}
+
+static void *cx_empty_map_get(
+        __attribute__((__unused__)) CxMap const *map,
+        __attribute__((__unused__)) CxHashKey key
+) {
+    return NULL;
+}
+
+static bool cx_empty_map_iter_valid(__attribute__((__unused__)) void const *iter) {
+    return false;
+}
+
+static CxIterator cx_empty_map_iterator(
+        struct cx_map_s const *map,
+        __attribute__((__unused__)) enum cx_map_iterator_type type
+) {
+    CxIterator iter = {0};
+    iter.src_handle = map;
+    iter.base.valid = cx_empty_map_iter_valid;
+    return iter;
+}
+
+static struct cx_map_class_s cx_empty_map_class = {
+        cx_empty_map_noop,
+        cx_empty_map_noop,
+        NULL,
+        cx_empty_map_get,
+        NULL,
+        cx_empty_map_iterator
+};
+
+CxMap cx_empty_map = {
+        NULL,
+        NULL,
+        0,
+        0,
+        NULL,
+        NULL,
+        NULL,
+        false,
+        &cx_empty_map_class
+};
+
+CxMap *const cxEmptyMap = &cx_empty_map;
+
+// </editor-fold>
+
+CxMutIterator cxMapMutIteratorValues(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxMapMutIteratorKeys(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxMapMutIterator(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/mempool.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,232 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/mempool.h"
+#include "cx/utils.h"
+#include <string.h>
+
+struct cx_mempool_memory_s {
+    /** The destructor. */
+    cx_destructor_func destructor;
+    /** The actual memory. */
+    char c[];
+};
+
+static void *cx_mempool_malloc(
+        void *p,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
+
+    if (pool->size >= pool->capacity) {
+        size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
+        struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*));
+        if (newdata == NULL) {
+            return NULL;
+        }
+        pool->data = newdata;
+        pool->capacity = newcap;
+    }
+
+    struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
+    if (mem == NULL) {
+        return NULL;
+    }
+
+    mem->destructor = pool->auto_destr;
+    pool->data[pool->size] = mem;
+    pool->size++;
+
+    return mem->c;
+}
+
+static void *cx_mempool_calloc(
+        void *p,
+        size_t nelem,
+        size_t elsize
+) {
+    size_t msz;
+    if (cx_szmul(nelem, elsize, &msz)) {
+        return NULL;
+    }
+    void *ptr = cx_mempool_malloc(p, msz);
+    if (ptr == NULL) {
+        return NULL;
+    }
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+static void *cx_mempool_realloc(
+        void *p,
+        void *ptr,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
+
+    struct cx_mempool_memory_s *mem, *newm;
+    mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
+    newm = realloc(mem, n + sizeof(cx_destructor_func));
+
+    if (newm == NULL) {
+        return NULL;
+    }
+    if (mem != newm) {
+        cx_for_n(i, pool->size) {
+            if (pool->data[i] == mem) {
+                pool->data[i] = newm;
+                return ((char*)newm) + sizeof(cx_destructor_func);
+            }
+        }
+        abort();
+    } else {
+        return ptr;
+    }
+}
+
+static void cx_mempool_free(
+        void *p,
+        void *ptr
+) {
+    struct cx_mempool_s *pool = p;
+
+    struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
+            ((char *) ptr - sizeof(cx_destructor_func));
+
+    cx_for_n(i, pool->size) {
+        if (mem == pool->data[i]) {
+            if (mem->destructor) {
+                mem->destructor(mem->c);
+            }
+            free(mem);
+            size_t last_index = pool->size - 1;
+            if (i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->size--;
+            return;
+        }
+    }
+    abort();
+}
+
+void cxMempoolDestroy(CxMempool *pool) {
+    struct cx_mempool_memory_s *mem;
+    cx_for_n(i, pool->size) {
+        mem = pool->data[i];
+        if (mem->destructor) {
+            mem->destructor(mem->c);
+        }
+        free(mem);
+    }
+    free(pool->data);
+    free((void*) pool->allocator);
+    free(pool);
+}
+
+void cxMempoolSetDestructor(
+        void *ptr,
+        cx_destructor_func func
+) {
+    *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
+}
+
+struct cx_mempool_foreign_mem_s {
+    cx_destructor_func destr;
+    void* mem;
+};
+
+static void cx_mempool_destr_foreign_mem(void* ptr) {
+    struct cx_mempool_foreign_mem_s *fm = ptr;
+    fm->destr(fm->mem);
+}
+
+int cxMempoolRegister(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func destr
+) {
+    struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
+            pool,
+            sizeof(struct cx_mempool_foreign_mem_s)
+    );
+    if (fm == NULL) return 1;
+
+    fm->mem = memory;
+    fm->destr = destr;
+    *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+
+    return 0;
+}
+
+static cx_allocator_class cx_mempool_allocator_class = {
+        cx_mempool_malloc,
+        cx_mempool_realloc,
+        cx_mempool_calloc,
+        cx_mempool_free
+};
+
+CxMempool *cxMempoolCreate(
+        size_t capacity,
+        cx_destructor_func destr
+) {
+    size_t poolsize;
+    if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+        return NULL;
+    }
+
+    struct cx_mempool_s *pool =
+            malloc(sizeof(struct cx_mempool_s));
+    if (pool == NULL) {
+        return NULL;
+    }
+
+    CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
+    if (provided_allocator == NULL) {
+        free(pool);
+        return NULL;
+    }
+    provided_allocator->cl = &cx_mempool_allocator_class;
+    provided_allocator->data = pool;
+
+    pool->allocator = provided_allocator;
+
+    pool->data = malloc(poolsize);
+    if (pool->data == NULL) {
+        free(provided_allocator);
+        free(pool);
+        return NULL;
+    }
+
+    pool->size = 0;
+    pool->capacity = capacity;
+    pool->auto_destr = destr;
+
+    return (CxMempool *) pool;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/printf.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,129 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/printf.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef CX_PRINTF_SBO_SIZE
+#define CX_PRINTF_SBO_SIZE 512
+#endif
+
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        ...
+) {
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = cx_vfprintf(stream, wfc, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        va_list ap
+) {
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < CX_PRINTF_SBO_SIZE) {
+        return (int) wfc(buf, 1, ret, stream);
+    } else {
+        int len = ret + 1;
+        char *newbuf = malloc(len);
+        if (!newbuf) {
+            return -1;
+        }
+
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        if (ret > 0) {
+            ret = (int) wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+}
+
+cxmutstr cx_asprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        ...
+) {
+    va_list ap;
+    cxmutstr ret;
+    va_start(ap, fmt);
+    ret = cx_vasprintf_a(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+cxmutstr cx_vasprintf_a(
+        CxAllocator const *a,
+        char const *fmt,
+        va_list ap
+) {
+    cxmutstr s;
+    s.ptr = NULL;
+    s.length = 0;
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret > 0 && ret < CX_PRINTF_SBO_SIZE) {
+        s.ptr = cxMalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t) ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else {
+        int len = ret + 1;
+        s.ptr = cxMalloc(a, len);
+        if (s.ptr) {
+            ret = vsnprintf(s.ptr, len, fmt, ap2);
+            if (ret < 0) {
+                free(s.ptr);
+                s.ptr = NULL;
+            } else {
+                s.length = (size_t) ret;
+            }
+        }
+    }
+    return s;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/string.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,785 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/string.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+
+#include <strings.h> // for strncasecmp()
+
+#endif // _WIN32
+
+cxmutstr cx_mutstr(char *cstring) {
+    return (cxmutstr) {cstring, strlen(cstring)};
+}
+
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+) {
+    return (cxmutstr) {cstring, length};
+}
+
+cxstring cx_str(const char *cstring) {
+    return (cxstring) {cstring, strlen(cstring)};
+}
+
+cxstring cx_strn(
+        const char *cstring,
+        size_t length
+) {
+    return (cxstring) {cstring, length};
+}
+
+cxstring cx_strcast(cxmutstr str) {
+    return (cxstring) {str.ptr, str.length};
+}
+
+void cx_strfree(cxmutstr *str) {
+    free(str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
+}
+
+void cx_strfree_a(
+        CxAllocator const *alloc,
+        cxmutstr *str
+) {
+    cxFree(alloc, str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
+}
+
+size_t cx_strlen(
+        size_t count,
+        ...
+) {
+    if (count == 0) return 0;
+
+    va_list ap;
+    va_start(ap, count);
+    size_t size = 0;
+    cx_for_n(i, count) {
+        cxstring str = va_arg(ap, cxstring);
+        size += str.length;
+    }
+    va_end(ap);
+
+    return size;
+}
+
+cxmutstr cx_strcat_ma(
+        CxAllocator const *alloc,
+        cxmutstr str,
+        size_t count,
+        ...
+) {
+    if (count == 0) return str;
+
+    cxstring *strings = calloc(count, sizeof(cxstring));
+    if (!strings) abort();
+
+    va_list ap;
+    va_start(ap, count);
+
+    // get all args and overall length
+    size_t slen = str.length;
+    cx_for_n(i, count) {
+        cxstring s = va_arg (ap, cxstring);
+        strings[i] = s;
+        slen += s.length;
+    }
+    va_end(ap);
+
+    // reallocate or create new string
+    if (str.ptr == NULL) {
+        str.ptr = cxMalloc(alloc, slen + 1);
+    } else {
+        str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
+    }
+    if (str.ptr == NULL) abort();
+
+    // concatenate strings
+    size_t pos = str.length;
+    str.length = slen;
+    cx_for_n(i, count) {
+        cxstring s = strings[i];
+        memcpy(str.ptr + pos, s.ptr, s.length);
+        pos += s.length;
+    }
+
+    // terminate string
+    str.ptr[str.length] = '\0';
+
+    // free temporary array
+    free(strings);
+
+    return str;
+}
+
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+) {
+    return cx_strsubsl(string, start, string.length - start);
+}
+
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+) {
+    return cx_strsubsl_m(string, start, string.length - start);
+}
+
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+) {
+    if (start > string.length) {
+        return (cxstring) {NULL, 0};
+    }
+
+    size_t rem_len = string.length - start;
+    if (length > rem_len) {
+        length = rem_len;
+    }
+
+    return (cxstring) {string.ptr + start, length};
+}
+
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+) {
+    cxstring result = cx_strsubsl(cx_strcast(string), start, length);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    // TODO: improve by comparing multiple bytes at once
+    cx_for_n(i, string.length) {
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
+        }
+    }
+    return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    size_t i = string.length;
+    while (i > 0) {
+        i--;
+        // TODO: improve by comparing multiple bytes at once
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
+        }
+    }
+    return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strrchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+#ifndef CX_STRSTR_SBO_SIZE
+#define CX_STRSTR_SBO_SIZE 512
+#endif
+
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+) {
+    if (needle.length == 0) {
+        return haystack;
+    }
+
+    // optimize for single-char needles
+    if (needle.length == 1) {
+        return cx_strchr(haystack, *needle.ptr);
+    }
+
+    /*
+     * IMPORTANT:
+     * Our prefix table contains the prefix length PLUS ONE
+     * this is our decision, because we want to use the full range of size_t.
+     * The original algorithm needs a (-1) at one single place,
+     * and we want to avoid that.
+     */
+
+    // local prefix table
+    size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
+
+    // check needle length and use appropriate prefix table
+    // if the pattern exceeds static prefix table, allocate on the heap
+    bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+    register size_t *ptable = useheap ? calloc(needle.length + 1,
+                                               sizeof(size_t)) : s_prefix_table;
+
+    // keep counter in registers
+    register size_t i, j;
+
+    // fill prefix table
+    i = 0;
+    j = 0;
+    ptable[i] = j;
+    while (i < needle.length) {
+        while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
+            j = ptable[j - 1];
+        }
+        i++;
+        j++;
+        ptable[i] = j;
+    }
+
+    // search
+    cxstring result = {NULL, 0};
+    i = 0;
+    j = 1;
+    while (i < haystack.length) {
+        while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
+            j = ptable[j - 1];
+        }
+        i++;
+        j++;
+        if (j - 1 == needle.length) {
+            size_t start = i - needle.length;
+            result.ptr = haystack.ptr + start;
+            result.length = haystack.length - start;
+            break;
+        }
+    }
+
+    // if prefix table was allocated on the heap, free it
+    if (ptable != s_prefix_table) {
+        free(ptable);
+    }
+
+    return result;
+}
+
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+) {
+    cxstring result = cx_strstr(cx_strcast(haystack), needle);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+) {
+    // special case: output limit is zero
+    if (limit == 0) return 0;
+
+    // special case: delimiter is empty
+    if (delim.length == 0) {
+        output[0] = string;
+        return 1;
+    }
+
+    // special cases: delimiter is at least as large as the string
+    if (delim.length >= string.length) {
+        // exact match
+        if (cx_strcmp(string, delim) == 0) {
+            output[0] = cx_strn(string.ptr, 0);
+            output[1] = cx_strn(string.ptr + string.length, 0);
+            return 2;
+        } else {
+            // no match possible
+            output[0] = string;
+            return 1;
+        }
+    }
+
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                // copy the current string to the array
+                cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
+                output[n - 1] = item;
+                size_t processed = item.length + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached, copy the _full_ remaining string
+                output[n - 1] = curpos;
+                break;
+            }
+        } else {
+            // no more matches, copy last string
+            output[n - 1] = curpos;
+            break;
+        }
+    }
+
+    return n;
+}
+
+size_t cx_strsplit_a(
+        CxAllocator const *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+) {
+    // find out how many splits we're going to make and allocate memory
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                size_t processed = match.ptr - curpos.ptr + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached
+                break;
+            }
+        } else {
+            // no more matches
+            break;
+        }
+    }
+    *output = cxCalloc(allocator, n, sizeof(cxstring));
+    return cx_strsplit(string, delim, n, *output);
+}
+
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+) {
+    return cx_strsplit(cx_strcast(string),
+                       delim, limit, (cxstring *) output);
+}
+
+size_t cx_strsplit_ma(
+        CxAllocator const *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+) {
+    return cx_strsplit_a(allocator, cx_strcast(string),
+                         delim, limit, (cxstring **) output);
+}
+
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+) {
+    if (s1.length == s2.length) {
+        return memcmp(s1.ptr, s2.ptr, s1.length);
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+int cx_strcasecmp(
+        cxstring s1,
+        cxstring s2
+) {
+    if (s1.length == s2.length) {
+#ifdef _WIN32
+        return _strnicmp(s1.ptr, s2.ptr, s1.length);
+#else
+        return strncasecmp(s1.ptr, s2.ptr, s1.length);
+#endif
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+int cx_strcmp_p(
+        void const *s1,
+        void const *s2
+) {
+    cxstring const *left = s1;
+    cxstring const *right = s2;
+    return cx_strcmp(*left, *right);
+}
+
+int cx_strcasecmp_p(
+        void const *s1,
+        void const *s2
+) {
+    cxstring const *left = s1;
+    cxstring const *right = s2;
+    return cx_strcasecmp(*left, *right);
+}
+
+cxmutstr cx_strdup_a(
+        CxAllocator const *allocator,
+        cxstring string
+) {
+    cxmutstr result = {
+            cxMalloc(allocator, string.length + 1),
+            string.length
+    };
+    if (result.ptr == NULL) {
+        result.length = 0;
+        return result;
+    }
+    memcpy(result.ptr, string.ptr, string.length);
+    result.ptr[string.length] = '\0';
+    return result;
+}
+
+cxstring cx_strtrim(cxstring string) {
+    cxstring result = string;
+    // TODO: optimize by comparing multiple bytes at once
+    while (result.length > 0 && isspace(*result.ptr)) {
+        result.ptr++;
+        result.length--;
+    }
+    while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
+        result.length--;
+    }
+    return result;
+}
+
+cxmutstr cx_strtrim_m(cxmutstr string) {
+    cxstring result = cx_strtrim(cx_strcast(string));
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+    return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+}
+
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+    return memcmp(string.ptr + string.length - suffix.length,
+                  suffix.ptr, suffix.length) == 0;
+}
+
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#else
+    return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#endif
+}
+
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr+string.length-suffix.length,
+                  suffix.ptr, suffix.length) == 0;
+#else
+    return strncasecmp(string.ptr + string.length - suffix.length,
+                       suffix.ptr, suffix.length) == 0;
+#endif
+}
+
+void cx_strlower(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) tolower(string.ptr[i]);
+    }
+}
+
+void cx_strupper(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) toupper(string.ptr[i]);
+    }
+}
+
+#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
+#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
+#endif
+
+struct cx_strreplace_ibuf {
+    size_t *buf;
+    struct cx_strreplace_ibuf *next;
+    unsigned int len;
+};
+
+static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
+    while (buf) {
+        struct cx_strreplace_ibuf *next = buf->next;
+        free(buf->buf);
+        free(buf);
+        buf = next;
+    }
+}
+
+cxmutstr cx_strreplacen_a(
+        CxAllocator const *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+) {
+
+    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+        return cx_strdup_a(allocator, str);
+
+    // Compute expected buffer length
+    size_t ibufmax = str.length / pattern.length;
+    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+    if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
+        ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
+    }
+
+    // Allocate first index buffer
+    struct cx_strreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
+    if (!firstbuf) return cx_mutstrn(NULL, 0);
+    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+    if (!firstbuf->buf) {
+        free(firstbuf);
+        return cx_mutstrn(NULL, 0);
+    }
+
+    // Search occurrences
+    cxstring searchstr = str;
+    size_t found = 0;
+    do {
+        cxstring match = cx_strstr(searchstr, pattern);
+        if (match.length > 0) {
+            // Allocate next buffer in chain, if required
+            if (curbuf->len == ibuflen) {
+                struct cx_strreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct cx_strreplace_ibuf));
+                if (!nextbuf) {
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
+                }
+                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+                if (!nextbuf->buf) {
+                    free(nextbuf);
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
+                }
+                curbuf->next = nextbuf;
+                curbuf = nextbuf;
+            }
+
+            // Record match index
+            found++;
+            size_t idx = match.ptr - str.ptr;
+            curbuf->buf[curbuf->len++] = idx;
+            searchstr.ptr = match.ptr + pattern.length;
+            searchstr.length = str.length - idx - pattern.length;
+        } else {
+            break;
+        }
+    } while (searchstr.length > 0 && found < replmax);
+
+    // Allocate result string
+    cxmutstr result;
+    {
+        ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
+        size_t rcount = 0;
+        curbuf = firstbuf;
+        do {
+            rcount += curbuf->len;
+            curbuf = curbuf->next;
+        } while (curbuf);
+        result.length = str.length + rcount * adjlen;
+        result.ptr = cxMalloc(allocator, result.length + 1);
+        if (!result.ptr) {
+            cx_strrepl_free_ibuf(firstbuf);
+            return cx_mutstrn(NULL, 0);
+        }
+    }
+
+    // Build result string
+    curbuf = firstbuf;
+    size_t srcidx = 0;
+    char *destptr = result.ptr;
+    do {
+        for (size_t i = 0; i < curbuf->len; i++) {
+            // Copy source part up to next match
+            size_t idx = curbuf->buf[i];
+            size_t srclen = idx - srcidx;
+            if (srclen > 0) {
+                memcpy(destptr, str.ptr + srcidx, srclen);
+                destptr += srclen;
+                srcidx += srclen;
+            }
+
+            // Copy the replacement and skip the source pattern
+            srcidx += pattern.length;
+            memcpy(destptr, replacement.ptr, replacement.length);
+            destptr += replacement.length;
+        }
+        curbuf = curbuf->next;
+    } while (curbuf);
+    memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
+
+    // Result is guaranteed to be zero-terminated
+    result.ptr[result.length] = '\0';
+
+    // Free index buffer
+    cx_strrepl_free_ibuf(firstbuf);
+
+    return result;
+}
+
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+) {
+    CxStrtokCtx ctx;
+    ctx.str = str;
+    ctx.delim = delim;
+    ctx.limit = limit;
+    ctx.pos = 0;
+    ctx.next_pos = 0;
+    ctx.delim_pos = 0;
+    ctx.found = 0;
+    ctx.delim_more = NULL;
+    ctx.delim_more_count = 0;
+    return ctx;
+}
+
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+) {
+    return cx_strtok(cx_strcast(str), delim, limit);
+}
+
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+) {
+    // abortion criteria
+    if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
+        return false;
+    }
+
+    // determine the search start
+    cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
+
+    // search the next delimiter
+    cxstring delim = cx_strstr(haystack, ctx->delim);
+
+    // if found, make delim capture exactly the delimiter
+    if (delim.length > 0) {
+        delim.length = ctx->delim.length;
+    }
+
+    // if more delimiters are specified, check them now
+    if (ctx->delim_more_count > 0) {
+        cx_for_n(i, ctx->delim_more_count) {
+            cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
+            if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
+                delim.ptr = d.ptr;
+                delim.length = ctx->delim_more[i].length;
+            }
+        }
+    }
+
+    // store the token information and adjust the context
+    ctx->found++;
+    ctx->pos = ctx->next_pos;
+    token->ptr = &ctx->str.ptr[ctx->pos];
+    ctx->delim_pos = delim.length == 0 ?
+                     ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
+    token->length = ctx->delim_pos - ctx->pos;
+    ctx->next_pos = ctx->delim_pos + delim.length;
+
+    return true;
+}
+
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+) {
+    return cx_strtok_next(ctx, (cxstring *) token);
+}
+
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        cxstring const *delim,
+        size_t count
+) {
+    ctx->delim_more = delim;
+    ctx->delim_more_count = count;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/szmul.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, 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.
+ */
+
+int cx_szmul_impl(
+        size_t a,
+        size_t b,
+        size_t *result
+) {
+    if (a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    size_t r = a * b;
+    if (r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/utils.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,99 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, 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 "cx/utils.h"
+
+#ifndef CX_STREAM_BCOPY_BUF_SIZE
+#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#endif
+
+#ifndef CX_STREAM_COPY_BUF_SIZE
+#define CX_STREAM_COPY_BUF_SIZE 1024
+#endif
+
+size_t cx_stream_bncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        char *buf,
+        size_t bufsize,
+        size_t n
+) {
+    if (n == 0) {
+        return 0;
+    }
+
+    char *lbuf;
+    size_t ncp = 0;
+
+    if (buf) {
+        if (bufsize == 0) return 0;
+        lbuf = buf;
+    } else {
+        if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
+        lbuf = malloc(bufsize);
+        if (lbuf == NULL) {
+            return 0;
+        }
+    }
+
+    size_t r;
+    size_t rn = bufsize > n ? n : bufsize;
+    while ((r = rfnc(lbuf, 1, rn, src)) != 0) {
+        r = wfnc(lbuf, 1, r, dest);
+        ncp += r;
+        n -= r;
+        rn = bufsize > n ? n : bufsize;
+        if (r == 0 || n == 0) {
+            break;
+        }
+    }
+
+    if (lbuf != buf) {
+        free(lbuf);
+    }
+
+    return ncp;
+}
+
+size_t cx_stream_ncopy(
+        void *src,
+        void *dest,
+        cx_read_func rfnc,
+        cx_write_func wfnc,
+        size_t n
+) {
+    char buf[CX_STREAM_COPY_BUF_SIZE];
+    return cx_stream_bncopy(src, dest, rfnc, wfnc,
+                            buf, CX_STREAM_COPY_BUF_SIZE, n);
+}
+
+#ifndef CX_SZMUL_BUILTIN
+#include "szmul.c"
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+OBJ_DIR = ../build/
+
+include common/objs.mk
+
+UI_LIB = ../build/lib/libuitk$(LIB_EXT)
+
+include $(TOOLKIT)/objs.mk
+OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
+
+all: $(UI_LIB)
+
+include $(TOOLKIT)/Makefile
+
+$(COMMON_OBJPRE)%.o: common/%.c
+	$(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $<
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+$(COCOA_OBJPRE)%.o: cocoa/%.m
+	$(CC) -o $@ -c $(CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolkit.h"
+#import "toolkit.h"
+
+typedef void(*ui_container_add_f)(UiContainer*, NSView*);
+
+struct UiContainer {
+    NSView* widget;
+    void   (*add)(UiContainer*, NSView*);
+    NSRect (*getframe)(UiContainer*);
+};
+
+UiContainer* ui_window_container(UiObject *obj, NSWindow *window);
+
+NSRect ui_container_getframe(UiContainer *ct);
+void ui_container_add(UiContainer *ct, NSView *view);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/container.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,106 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+
+#import "container.h"
+
+
+UiContainer* ui_window_container(UiObject *obj, NSWindow *window) {
+    UiContainer *ct = ucx_mempool_malloc(
+                                         obj->ctx->mempool,
+                                         sizeof(UiContainer));
+    ct->widget = [window contentView];
+    ct->add = ui_container_add;
+    ct->getframe = ui_container_getframe;
+    return ct;
+}
+
+UIWIDGET ui_sidebar(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    NSRect frame = ct->getframe(ct);
+    
+    // create and add views
+    NSSplitView *splitview = [[NSSplitView alloc] initWithFrame:frame];
+    [splitview setVertical:YES];
+    [splitview setDividerStyle:NSSplitViewDividerStyleThin];
+    ct->add(ct, splitview);
+    
+    NSRect lframe;
+    lframe.origin.x = 0;
+    lframe.origin.y = 0;
+    lframe.size.width = 200;
+    lframe.size.height = frame.size.height;
+    
+    NSRect rframe;
+    rframe.origin.x = 0;
+    rframe.origin.y = 0;
+    rframe.size.width = frame.size.width - 201;
+    rframe.size.height = frame.size.height;
+    
+    NSView *sidebar = [[NSView alloc]initWithFrame:lframe];
+    NSView *contentarea = [[NSView alloc]initWithFrame:rframe];
+    
+    [splitview addSubview:sidebar];
+    [splitview addSubview:contentarea];
+    
+    // add ui objects for the sidebar and contentarea
+    // the sidebar is added last, so that new views are added first to it
+    UiObject *left = uic_object_new(obj, sidebar);
+    UiContainer *ct1 = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiContainer));
+    ct1->widget = sidebar;
+    ct1->add = ui_container_add;
+    ct1->getframe = ui_container_getframe;
+    left->container = ct1;
+    
+    UiObject *right = uic_object_new(obj, sidebar);
+    UiContainer *ct2 = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiContainer));
+    ct2->widget = contentarea;
+    ct2->add = ui_container_add;
+    ct2->getframe = ui_container_getframe;
+    right->container = ct2;
+    
+    uic_obj_add(obj, right);
+    uic_obj_add(obj, left);
+    
+    return splitview;
+}
+
+
+NSRect ui_container_getframe(UiContainer *ct) {
+    return [ct->widget frame];
+}
+
+void ui_container_add(UiContainer *ct, NSView *view) {
+    [ct->widget addSubview: view];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/graphics.h"
+#import "toolkit.h"
+
+
+@interface UiCanvas : NSView {
+    UiObject            *object;
+    ui_drawfunc         callback;
+    void                *userdata;
+}
+
+- (UiObject*) object;
+- (void) setObject:(void*)obj;
+
+- (void*) userdata;
+- (void) setUserdata:(void*)d;
+
+- (ui_drawfunc) callback;
+- (void) setCallback: (ui_drawfunc)f;
+
+
+- (void)drawRect:(NSRect)rect;
+
+@end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/graphics.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,124 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+
+#import "graphics.h"
+#import "container.h"
+#import "../common/context.h"
+
+
+
+@implementation UiCanvas
+
+- (UiObject*) object {
+    return object;
+}
+
+- (void) setObject:(void*)obj {
+    object = obj;
+}
+
+- (void*) userdata {
+    return userdata;
+}
+
+- (void) setUserdata:(void*)d {
+    userdata = d;
+}
+
+- (ui_drawfunc) callback {
+    return callback;
+}
+- (void) setCallback: (ui_drawfunc)f {
+    callback = f;
+}
+
+- (void) drawRect:(NSRect)rect {
+    UiGraphics g;
+    NSRect bounds = [self bounds];
+    g.width = bounds.size.width;
+    g.height = bounds.size.height;
+    
+    UiEvent ev;
+    ev.obj = object;
+    ev.window = object->window;
+    ev.document = object->ctx->document;
+    
+    callback(&ev, &g, userdata);
+}
+
+@end
+
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    NSRect frame = ct->getframe(ct);
+    
+    UiCanvas *canvas = [[UiCanvas alloc]initWithFrame:frame];
+    [canvas setObject: obj];
+    [canvas setCallback: f];
+    [canvas setUserdata: userdata];
+    ct->add(ct, canvas);
+    
+    return canvas;
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *gr, int red, int green, int blue) {
+    float r = ((float)red) / 255.f;
+    float g = ((float)green) / 255.f;
+    float b = ((float)blue) / 255.f;
+    
+    NSColor *color = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1];
+    [color set];
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    // translate y
+    y = g->height - y - h;
+    
+    NSRect bounds;
+    bounds.origin.x = x;
+    bounds.origin.y = y;
+    bounds.size.width = w;
+    bounds.size.height = h;
+    
+    if(fill) {
+        NSRectFill(bounds);
+    } else {
+        NSFrameRect(bounds);
+    }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,94 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/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;
+
+typedef struct UiStateItem {
+    NSMenuItem  *item;
+    char        *var;
+} UiStateItem;
+
+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*
+}
+
+- (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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/menu.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,319 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+#import <stdarg.h>
+
+#import "menu.h"
+#import "window.h"
+#import "stock.h"
+
+@implementation UiMenuDelegate 
+
+- (UiMenuDelegate*) init {
+    items = NULL;
+    itemlists = NULL;
+    return self;
+}
+
+- (void) menuNeedsUpdate:(NSMenu *) menu {
+    NSWindow *activeWindow = [NSApp keyWindow];
+    [(UiCocoaWindow*)activeWindow updateMenu: menu];
+}
+
+- (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) 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);
+}
+
+- (UcxList*) items {
+    return items;
+}
+
+- (UcxList*) lists {
+    return itemlists;
+    
+}
+
+@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) checkGroups:(int*)g count:(int)n {
+    int c = [groups count];
+    
+    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];
+}
+
+UiMenuDelegate* ui_menu_delegate() {
+    return delegate;
+}
+
+
+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];
+    }
+    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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,45 @@
+#
+# 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.
+#
+
+COCOA_SRC_DIR = ui/cocoa/
+COCOA_OBJPRE = $(OBJ_DIR)/$(COCOA_SRC_DIR)
+
+COCOAOBJ = toolkit.o
+COCOAOBJ += window.o
+COCOAOBJ += menu.o
+COCOAOBJ += stock.o
+COCOAOBJ += toolbar.o
+COCOAOBJ += container.o
+COCOAOBJ += text.o
+COCOAOBJ += resource.o
+COCOAOBJ += tree.o
+COCOAOBJ += graphics.o
+
+
+TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
+TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/resource.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#import "../ui/toolkit.h"
+#import "../ui/properties.h"
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/resource.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+
+#import "resource.h"
+#import "../common/properties.h"
+
+
+
+void ui_load_lang_def(char *locale, char *default_locale) {
+    NSString *localeString = nil;
+    char tmp[6];
+    if(!locale) {
+        NSString* lang = [[NSLocale currentLocale] localeIdentifier];
+        if(lang) {
+            localeString = lang;
+        } else {
+            [[NSString alloc]initWithUTF8String:default_locale];
+        }
+    } else {
+        localeString = [[NSString alloc]initWithUTF8String:locale];
+    }
+    
+    NSString *path = [[NSBundle mainBundle] pathForResource:localeString ofType:@"properties" inDirectory:@"locales"];
+    
+    const char *p = [path UTF8String];
+    
+    if(uic_load_language_file((char*)p)) {
+        if(default_locale) {
+            ui_load_lang_def(default_locale, NULL);
+        } else {
+            // cannot find any language file
+            fprintf(stderr, "Ui Error: Cannot load language.\n");
+            exit(-1);
+        }
+    }
+}
+
+void ui_locales_dir(char *path) {
+    // empty
+}
+
+void ui_pixmaps_dir(char *path) {
+    // empty
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/stock.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#import "toolkit.h"
+#import "../ui/stock.h"
+#import <ucx/map.h>
+
+typedef struct UiStockItem {
+    NSString *label;
+    NSString *keyEquivalent;
+    NSImage  *image;
+} UiStockItem;
+
+void ui_stock_init();
+
+void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image);
+
+UiStockItem* ui_get_stock_item(char *stock_id);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/stock.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+
+#import "stock.h"
+#import "../common/properties.h"
+
+static UcxMap *stock_items;
+
+void ui_stock_init() {
+    stock_items = ucx_map_new(64);
+    
+    ui_add_stock_item(UI_STOCK_NEW, @"New", @"n", nil);
+    ui_add_stock_item(UI_STOCK_OPEN, @"Open", @"o", nil);
+    ui_add_stock_item(UI_STOCK_SAVE, @"Save", @"s", nil);
+    ui_add_stock_item(UI_STOCK_SAVE_AS, @"Save as ...", @"", nil);
+    ui_add_stock_item(UI_STOCK_CLOSE, @"Close", @"w", nil);
+    ui_add_stock_item(UI_STOCK_UNDO, @"Undo", @"z", nil);
+    ui_add_stock_item(UI_STOCK_REDO, @"Redo", @"", nil);
+    ui_add_stock_item(UI_STOCK_CUT, @"Cut", @"x", nil);
+    ui_add_stock_item(UI_STOCK_COPY, @"Copy", @"c", nil);
+    ui_add_stock_item(UI_STOCK_PASTE, @"Paste", @"v", nil);
+    ui_add_stock_item(UI_STOCK_DELETE, @"Delete", @"", nil);
+    
+    ui_add_stock_item(UI_STOCK_GO_BACK, @"Back", @"", [NSImage imageNamed: NSImageNameGoLeftTemplate]);
+    ui_add_stock_item(UI_STOCK_GO_FORWARD, @"Forward", @"", [NSImage imageNamed: NSImageNameGoRightTemplate]);
+}
+
+void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image) {
+    UiStockItem *i = malloc(sizeof(UiStockItem));
+    i->label = label;
+    i->keyEquivalent = keyEquivalent;
+    i->image = image;
+    
+    ucx_map_cstr_put(stock_items, stock_id, i);
+}
+
+UiStockItem* ui_get_stock_item(char *stock_id) {
+    UiStockItem *item = ucx_map_cstr_get(stock_items, stock_id);
+    if(item) {
+        char *label = uistr_n(stock_id);
+        if(label) {
+            NSString *str = [[NSString alloc]initWithUTF8String:label];
+            item->label = str;
+        }
+    }
+    return item;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,63 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/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;
+
+
+
+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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/text.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,190 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <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
+
+
+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;
+    }
+    
+    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;
+    
+    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;
+}
+
+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];
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,131 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolbar.h"
+#import "toolkit.h"
+#import <stdarg.h>
+
+
+@protocol UiToolItem
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+                    identifier:(NSString*)identifier
+                        object:(UiObject*)obj;
+
+- (void) addGroup:(int)group;
+
+- (UcxList*) groups;
+
+@end
+
+
+/*
+ * UiToolbarStockItem
+ *
+ * creates a toolbar item from stock description
+ */
+@interface UiToolbarStockItem : NSObject <UiToolItem> {
+    char           *name;
+    char           *stockid;
+    ui_callback    callback;
+    void           *userdata;
+    UcxList        *groups;
+    BOOL           isToggleButton;
+}
+
+- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
+                             stockID:(char*)sid
+                            callback:(ui_callback)f
+                            userdata:(void*)data;
+
+- (void) setIsToggleButton:(BOOL)t;
+
+
+@end
+
+/*
+ * UiToolbarItem
+ *
+ * toolbar item with label and icon
+ */
+@interface UiToolbarItem : NSObject <UiToolItem> {
+    char           *name;
+    char           *label;
+    // icon
+    ui_callback    callback;
+    void           *userdata;
+    UcxList        *groups;
+    BOOL           isToggleButton;
+}
+
+- (UiToolbarItem*) initWithIdentifier:(char*)identifier
+                                     label:(char*)lbl
+                                  callback:(ui_callback)f
+                                  userdata:(void*)data;
+
+- (void) setIsToggleButton:(BOOL)t;
+
+
+@end
+
+
+
+/*
+ * UiToolbarDelegate
+ */
+@interface UiToolbarDelegate : NSObject <NSToolbarDelegate> {
+    NSMutableArray      *allowedItems;
+    NSMutableArray      *defaultItems;
+    NSMutableDictionary *items;
+}
+
+- (UiToolbarDelegate*) init;
+
+- (void) addDefault:(NSString*)identifier;
+
+- (void) addItem: (NSString*) identifier
+            item: (NSObject<UiToolItem>*) item;
+
+@end
+
+
+/*
+ * UiToolbar
+ */
+@interface UiToolbar : NSToolbar {
+    UiObject *obj;
+}
+
+- (UiToolbar*) initWithObject:(UiObject*)object;
+
+- (UiObject*) object;
+
+@end
+
+void ui_toolbar_init();
+void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap);
+NSToolbar* ui_create_toolbar(UiObject *obj);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/toolbar.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,358 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+#import <inttypes.h>
+#import <stdarg.h>
+
+#import "toolbar.h"
+#import "window.h"
+#import "stock.h"
+
+
+static UiToolbarDelegate* toolbar_delegate;
+
+/* ---------------------      UiToolbarStockItem     --------------------- */
+
+@implementation UiToolbarStockItem
+
+- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
+                                   stockID:(char*)sid
+                                  callback:(ui_callback)f
+                                  userdata:(void*)data
+{
+    name = identifier;
+    stockid = sid;
+    callback = f;
+    userdata = data;
+    groups = NULL;
+    isToggleButton = NO;
+    return self;
+}
+
+- (void) setIsToggleButton:(BOOL)t {
+    isToggleButton = t;
+}
+
+- (void) addGroup:(int)group {
+    groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+                    identifier:(NSString*)identifier
+                        object:(UiObject*)obj
+{
+    UiStockItem *s = ui_get_stock_item(stockid);
+    if(s == nil) {
+        printf("cannot find stock item\n");
+        return nil;
+    }
+    
+    NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
+                            identifier] autorelease];
+    //[item setLabel:[s label]];
+    //[item setPaletteLabel:[s label]];
+    [item setLabel:s->label];
+    [item setPaletteLabel:@"Operation"];
+    
+    // create button ...
+    NSRect frame = NSMakeRect(0, 0, 40, 22);
+    //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
+    NSButton *button = [[NSButton alloc]initWithFrame:frame];
+    //[button setImage:[s buttonImage]];
+    //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
+    if(s->image) {
+        [button setImage:s->image];
+    } else {
+        [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
+    }
+    [button setBezelStyle: NSTexturedRoundedBezelStyle];
+    
+    // event
+    EventWrapper *event = [[EventWrapper alloc]
+                           initWithData:userdata callback:callback];
+    if(isToggleButton) {
+        [button setButtonType: NSPushOnPushOffButton];
+        [button setAction:@selector(handleToggleEvent:)];
+    } else {
+        [button setAction:@selector(handleEvent:)];
+    }
+    [button setTarget:event];
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, item, groups);
+    }
+    
+    [item setView:button];
+    return item;
+}
+
+- (UcxList*) groups {
+    return groups;
+}
+
+@end
+
+
+/* ---------------------      UiToolbarItem     --------------------- */
+
+@implementation UiToolbarItem
+
+- (UiToolbarItem*) initWithIdentifier:(char*)identifier
+                                     label:(char*)lbl
+                                  callback:(ui_callback)f
+                                  userdata:(void*)data
+{
+    name = identifier;
+    label = lbl;
+    callback = f;
+    userdata = data;
+    groups = NULL;
+    isToggleButton = NO;
+    return self;
+}
+
+- (void) setIsToggleButton:(BOOL)t {
+    isToggleButton = t;
+}
+
+- (void) addGroup:(int)group {
+    groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+                    identifier:(NSString*)identifier
+                        object:(UiObject*)obj
+{
+    NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
+                            identifier] autorelease];
+    //[item setLabel:[s label]];
+    //[item setPaletteLabel:[s label]];
+    NSString *l = [[NSString alloc]initWithUTF8String:label];
+    [item setLabel:l];
+    [item setPaletteLabel:@"Operation"];
+    
+    // create button ...
+    NSRect frame = NSMakeRect(0, 0, 40, 22);
+    //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
+    NSButton *button = [[NSButton alloc]initWithFrame:frame];
+    //[button setImage:[s buttonImage]];
+    //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
+    
+    // TODO: image
+    [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
+    
+    [button setBezelStyle: NSTexturedRoundedBezelStyle];
+    
+    // event
+    EventWrapper *event = [[EventWrapper alloc]
+                           initWithData:userdata callback:callback];
+    if(isToggleButton) {
+        [button setButtonType: NSPushOnPushOffButton];
+        [button setAction:@selector(handleToggleEvent:)];
+    } else {
+        [button setAction:@selector(handleEvent:)];
+    }
+    [button setTarget:event];
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, item, groups);
+    }
+    
+    [item setView:button];
+    return item;
+}
+
+- (UcxList*) groups {
+    return groups;
+}
+
+@end
+
+
+/* ---------------------      UiToolbarDelegate      --------------------- */
+
+@implementation UiToolbarDelegate
+
+- (UiToolbarDelegate*) init {
+    allowedItems = [[NSMutableArray alloc]initWithCapacity: 16];
+    defaultItems = [[NSMutableArray alloc]initWithCapacity: 16];
+    items = [[NSMutableDictionary alloc] init];
+    return self;
+}
+
+- (void) addDefault:(NSString*)identifier {
+    [defaultItems addObject: identifier];
+}
+
+- (void) addItem: (NSString*) identifier
+            item: (NSObject<UiToolItem>*) item
+{
+    [allowedItems addObject: identifier];
+    [items setObject: item forKey:identifier];
+}
+
+/*
+- (void) addStockItem:(char*)name
+              stockID:(char*)sid
+             callback:(ui_callback)f
+                 data:(void*)userdata
+{
+    UiToolbarStockItem *item = [[UiToolbarStockItem alloc]initWithData:name
+                                                               stockID:sid callback:f data:userdata];
+    
+    NSString *s = [[NSString alloc]initWithUTF8String:name];
+    [allowedItems addObject: s];
+    [items setObject: item forKey:s];
+}
+*/
+
+// implementation of NSToolbarDelegate methods
+- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
+    NSMutableArray *i = [[NSMutableArray alloc]
+                         initWithCapacity:[allowedItems count] + 3];
+    [i addObject: NSToolbarFlexibleSpaceItemIdentifier];
+    [i addObject: NSToolbarSpaceItemIdentifier];
+    [i addObject: NSToolbarSeparatorItemIdentifier];
+    for(id item in allowedItems) {
+        [i addObject: item];
+    }
+    
+    return i;
+}
+
+- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
+    return defaultItems;
+}
+
+- (NSToolbarItem *) toolbar:(NSToolbar*)toolbar
+         itemForItemIdentifier:(NSString*)identifier
+     willBeInsertedIntoToolbar:(BOOL)flag
+{
+    Protocol *item = @protocol(UiToolItem);
+    item = [items objectForKey: identifier];
+    
+    // get UiObject from toolbar
+    UiObject *obj = [(UiToolbar*)toolbar object];
+    
+    // create new NSToolbarItem
+    return [item createItem:toolbar identifier:identifier object:obj];
+}
+
+@end
+
+
+@implementation UiToolbar
+
+- (UiToolbar*) initWithObject:(UiObject*)object {
+    [self initWithIdentifier: @"MainToolbar"];
+    obj = object;
+    return self;
+}
+
+- (UiObject*) object {
+    return obj;
+}
+
+@end
+
+
+/* ---------------------          functions          --------------------- */
+
+void ui_toolbar_init() {
+    toolbar_delegate = [[UiToolbarDelegate alloc]init];
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
+    UiToolbarItem *item = [[UiToolbarItem alloc]
+                                initWithIdentifier: name
+                                label: label
+                                callback: f
+                                userdata: udata];
+    
+    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+    [toolbar_delegate addItem: identifier item: item];
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata) {
+    ui_toolitem_stgr(name, stockid, f, udata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+    va_list ap;
+    va_start(ap, udata);
+    ui_toolbar_stock_button(name, stockid, NO, f, udata, ap);
+    va_end(ap);
+}
+
+void ui_toolitem_toggle_st(char *name, char *stockid, ui_callback f, void *udata) {
+    ui_toolitem_toggle_stgr(name, stockid, f, udata, -1);
+}
+
+void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+    va_list ap;
+    va_start(ap, udata);
+    ui_toolbar_stock_button(name, stockid, YES, f, udata, ap);
+    va_end(ap);
+}
+
+
+void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap) {
+    UiToolbarStockItem *item = [[UiToolbarStockItem alloc]
+                                initWithIdentifier: name
+                                stockID: stockid
+                                callback: f
+                                userdata: udata];
+    [item setIsToggleButton: toggle];
+    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+    [toolbar_delegate addItem: identifier item: item];
+    
+    // add groups
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        [item addGroup: group];
+    }
+}
+
+
+void ui_toolbar_add_default(char *name) {
+    NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+    [toolbar_delegate addDefault: identifier];
+}
+
+NSToolbar* ui_create_toolbar(UiObject *obj) {
+    UiToolbar *toolbar = [[UiToolbar alloc] initWithObject:obj];
+    [toolbar setDelegate: toolbar_delegate];
+    [toolbar setAllowsUserCustomization: true];
+    return toolbar;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,88 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+@interface UiApplicationDelegate : NSObject<NSApplicationDelegate> {
+    
+}
+
+- (void)applicationWillTerminate:(NSNotification*)notification;
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible;
+
+- (BOOL)application:(NSApplication *)application openFile:(NSString *)filename;
+
+@end
+
+@interface EventWrapper : NSObject {
+    void         *data;
+    ui_callback  callback;
+    int          value;
+}
+
+- (EventWrapper*) initWithData: (void*)data callback:(ui_callback) f;
+
+- (void*) data;
+- (void) setData:(void*)d;
+- (ui_callback) callback;
+- (void) setCallback: (ui_callback)f;
+- (int) intval;
+- (void) setIntval:(int)i;
+
+- (BOOL)handleEvent:(id)sender;
+- (BOOL)handleStateEvent:(id)sender;
+- (BOOL)handleToggleEvent:(id)sender;
+
+@end
+
+@interface UiThread : NSObject {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+}
+
+- (id) initWithObject:(UiObject*)object;
+- (void) setJobFunction:(ui_threadfunc)func;
+- (void) setJobData:(void*)data;
+- (void) setFinishCallback:(ui_callback)callback;
+- (void) setFinishData:(void*)data;
+
+- (void) start;
+- (void) runJob:(id)n;
+- (void) finish:(id)n;
+
+@end
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/toolkit.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <sys/stat.h>
+#import <sys/types.h>
+#import <errno.h>
+
+#import "../common/context.h"
+#import "../common/document.h"
+#import "../common/properties.h"
+
+#import "toolkit.h"
+#import "window.h"
+#import "menu.h"
+#import "toolbar.h"
+#import "stock.h"
+
+NSAutoreleasePool *pool;
+
+static char *application_name;
+
+static ui_callback   appclose_fnc;
+static void          *appclose_udata;
+
+static ui_callback   openfile_fnc;
+static void          *openfile_udata;
+
+void ui_init(char *appname, int argc, char **argv) {
+    pool = [[NSAutoreleasePool alloc] init];
+    [NSApplication sharedApplication];
+    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+    
+    UiApplicationDelegate *delegate = [[UiApplicationDelegate alloc]init];
+    [NSApp setDelegate: delegate];
+    
+    
+    uic_docmgr_init();
+    ui_menu_init();
+    ui_toolbar_init();
+    ui_stock_init();
+    
+    uic_load_app_properties();
+}
+
+char* ui_appname() {
+    return application_name;
+}
+
+void ui_exitfunc(ui_callback f, void *userdata) {
+    appclose_fnc = f;
+    appclose_udata = userdata;
+}
+
+void ui_openfilefunc(ui_callback f, void *userdata) {
+    openfile_fnc = f;
+    openfile_udata = userdata;
+}
+
+void ui_show(UiObject *obj) {
+    uic_check_group_widgets(obj->ctx);
+    if([obj->widget class] == [UiCocoaWindow class]) {
+        UiCocoaWindow *window = (UiCocoaWindow*)obj->widget;
+        [window makeKeyAndOrderFront:nil];
+    } else {
+        printf("Error: ui_show: Object is not a Window!\n");
+    }
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    // TODO
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    // TODO
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    [(id)widget setEnabled: enabled];
+}
+
+
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+    UiThread *thread = [[UiThread alloc]initWithObject:obj];
+    [thread setJobFunction:tf];
+    [thread setJobData:td];
+    [thread setFinishCallback:f];
+    [thread setFinishData:fd];
+    [thread start];
+}
+
+void ui_main() {
+    [NSApp run];
+    [pool release];
+}
+
+
+void ui_clipboard_set(char *str) {
+    NSString *string = [[NSString alloc] initWithUTF8String:str];
+    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
+    [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+    [pasteBoard setString:string forType:NSStringPboardType];
+}
+
+char* ui_clipboard_get() {
+    NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
+    NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];
+    NSDictionary *options = [NSDictionary dictionary];
+    NSArray *data = [pasteBoard readObjectsForClasses:classes options:options];
+    
+    if(data != nil) {
+        NSString *str = [data componentsJoinedByString: @""];
+        
+        // copy C string
+        size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        const char *cstr = [str UTF8String];
+        char *value = malloc(length + 1);
+        memcpy(value, cstr, length);
+        value[length] = '\0';
+        
+        return value;
+    } else {
+        return NULL;
+    }
+}
+
+
+@implementation UiApplicationDelegate
+
+- (void)applicationWillTerminate:(NSNotification*)notification {
+    printf("terminate\n");
+}
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible; {
+    if(!visible) {
+        printf("reopen\n");
+    }
+    return NO;
+}
+
+- (BOOL)application:(NSApplication*)application openFile:(NSString*)filename {
+    if(openfile_fnc) {
+        UiEvent event;
+        event.obj = NULL;
+        event.document = NULL;
+        event.window = NULL;
+        event.eventdata = (void*)[filename UTF8String];
+        event.intval = 0;
+        openfile_fnc(&event, openfile_udata);
+    }
+    
+    return NO;
+}
+
+@end
+
+
+@implementation EventWrapper
+
+- (EventWrapper*) initWithData: (void*)d callback:(ui_callback) f {
+    data = d;
+    callback = f;
+    value = 0;
+    return self;
+}
+
+
+- (void*) data {
+    return data;
+}
+
+- (void) setData:(void*)d {
+    data = d;
+}
+
+
+- (ui_callback) callback {
+    return callback;
+}
+
+- (void) setCallback: (ui_callback)f {
+    callback = f;
+}
+
+- (int) intval {
+    return value;
+}
+
+- (void) setIntval:(int)i {
+    value = i;
+}
+
+
+- (BOOL)handleEvent:(id)sender {
+    NSWindow *activeWindow = [NSApp keyWindow];
+    
+    UiEvent event;
+    event.eventdata = NULL;
+    if([activeWindow class] == [UiCocoaWindow class]) {
+        event.obj = [(UiCocoaWindow*)activeWindow object];
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.intval = value;
+    }
+    if(callback) {
+        callback(&event, data);
+    }
+    
+    return true;
+}
+
+- (BOOL)handleStateEvent:(id)sender {
+    NSWindow *activeWindow = [NSApp keyWindow];
+    int state = [sender state] ? NSOffState : NSOnState;
+    
+    UiEvent event;
+    event.intval = state;
+    event.eventdata = NULL;
+    if([activeWindow class] == [UiCocoaWindow class]) {
+        event.obj = [(UiCocoaWindow*)activeWindow object];
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        // if the sender is a menu item, we have to save the state for this
+        // window
+        UiMenuItem *wmi = [(UiCocoaWindow*)activeWindow getMenuItem: sender];
+        if(wmi) {
+            // update state in window data
+            wmi->state = state;
+        }
+    } else {
+        event.window = NULL;
+        event.document = NULL;
+    }
+    if(callback) {
+        callback(&event, data);
+    }
+    [sender setState: state];
+    
+    return true;
+}
+
+- (BOOL)handleToggleEvent:(id)sender {
+    NSWindow *activeWindow = [NSApp keyWindow];
+    
+    UiEvent event;
+    event.intval = [sender state];
+    event.eventdata = NULL;
+    if([activeWindow class] == [UiCocoaWindow class]) {
+        event.obj = [(UiCocoaWindow*)activeWindow object];
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+    } else {
+        event.window = NULL;
+        event.document = NULL;
+    }
+    if(callback) {
+        callback(&event, data);
+    }
+    
+    return true;
+}
+
+@end
+
+@implementation UiThread
+
+- (id) initWithObject:(UiObject*)object {
+    obj = object;
+    job_func = NULL;
+    job_data = NULL;
+    finish_callback = NULL;
+    finish_data = NULL;
+    return self;
+}
+
+- (void) setJobFunction:(ui_threadfunc)func {
+    job_func = func;
+}
+
+- (void) setJobData:(void*)data {
+    job_data = data;
+}
+
+- (void) setFinishCallback:(ui_callback)callback {
+    finish_callback = callback;
+}
+
+- (void) setFinishData:(void*)data {
+    finish_data = data;
+}
+
+- (void) start {
+    [NSThread detachNewThreadSelector:@selector(runJob:)
+                             toTarget:self
+                           withObject:nil];
+}
+
+- (void) runJob:(id)n {
+    int result = job_func(job_data);
+    if(!result) {
+        [self performSelectorOnMainThread:@selector(finish:)
+                               withObject:nil
+                            waitUntilDone:NO];
+    }
+}
+
+- (void) finish:(id)n {
+    UiEvent event;
+    event.obj = obj;
+    event.window = obj->window;
+    event.document = obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = 0;
+    finish_callback(&event, finish_data);
+}
+
+@end
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/tree.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+ 
+ #import "../ui/tree.h"
+ #import "toolkit.h"
+ 
+ 
+@interface UiTableDataSource : NSObject<NSTableViewDataSource, NSTableViewDelegate> {
+    UiList          *data;
+    UiModelInfo     *info;
+    UiListSelection *lastSelection;
+}
+
+- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo;
+
+- (void)handleDoubleAction:(id)sender;
+
+@end
+
+
+char* ui_type_to_string(UiModelType type, void *data, BOOL *free);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/tree.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+ 
+#import <stdio.h>
+#import <stdlib.h>
+ 
+#import "tree.h"
+#import "container.h"
+#import "window.h"
+#import "../common/context.h"
+#import <ucx/utils.h>
+
+@implementation UiTableDataSource
+
+- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo {
+    data = list;
+    info = modelinfo;
+    lastSelection = NULL;
+    return self;
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableview {
+    return data->count(data);
+}
+
+- (id)tableView:                  (NSTableView*)tableview
+        objectValueForTableColumn:(NSTableColumn*)column
+                              row:(NSInteger)row
+{
+    int column_index = [[column identifier]intValue];
+    
+    void *row_data = data->get(data, row);
+    void *cell_data = info->getvalue(row_data, column_index);
+    
+    BOOL f = false;
+    char *str = ui_type_to_string(info->types[column_index], cell_data, &f);
+    NSString *s = [[NSString alloc]initWithUTF8String:str];
+    return s;
+}
+
+- (void)tableView:(NSTableView *)tableview
+        setObjectValue:(id)object
+        forTableColumn:(NSTableColumn *)column
+                   row:(NSInteger)row
+{
+    int column_index = [[column identifier]intValue];
+    
+    // TODO
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)notification {
+    NSTableView *tableview = (NSTableView*)notification.object;
+    
+    // create selection object
+    UiListSelection *selection = malloc(sizeof(UiListSelection));
+    selection->count = [tableview numberOfSelectedRows];
+    
+    selection->rows = calloc(selection->count, sizeof(int));
+    NSIndexSet *indices = [tableview selectedRowIndexes];
+    NSUInteger index = [indices firstIndex];
+    int i=0;
+    while (index!=NSNotFound) {
+        selection->rows[i] = index;
+        index = [indices indexGreaterThanIndex:index];
+        i++;
+    }
+    
+    // create event object
+    UiEvent event;
+    NSWindow *activeWindow = [NSApp keyWindow];
+    if([activeWindow class] == [UiCocoaWindow class]) {
+        event.obj = [(UiCocoaWindow*)activeWindow object];
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+    } else {
+        event.window = NULL;
+        event.document = NULL;
+    }
+    event.eventdata = selection;
+    event.intval = selection->count == 0 ? -1 : selection->rows[0];
+    
+    // callback
+    info->selection(&event, info->userdata);
+    
+    // cleanup
+    if(lastSelection) {
+        free(lastSelection->rows);
+        free(lastSelection);
+    }
+    lastSelection = selection;
+}
+
+- (void)handleDoubleAction:(id)sender {
+    // create event object
+    UiEvent event;
+    NSWindow *activeWindow = [NSApp keyWindow];
+    if([activeWindow class] == [UiCocoaWindow class]) {
+        event.obj = [(UiCocoaWindow*)activeWindow object];
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+    } else {
+        event.window = NULL;
+        event.document = NULL;
+    }
+    event.eventdata = lastSelection;
+    event.intval = lastSelection->count == 0 ? -1 : lastSelection->rows[0];
+    
+    info->activate(&event, info->userdata);
+}
+
+- (BOOL)tableView:(NSTableView *)tableview isGroupRow:(NSInteger)row {
+    return NO;
+}
+
+@end
+
+
+UIWIDGET ui_table(UiObject *obj, UiList *model, UiModelInfo *modelinfo) {
+    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];
+    
+    NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
+    [scrollview setDocumentView:tableview];
+    [tableview setAllowsMultipleSelection: YES];
+    //[tableview setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
+    
+    // add columns
+    for(int i=0;i<modelinfo->columns;i++) {
+        NSString *cid = [[NSString alloc]initWithFormat: @"%d", i];
+        NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:cid];
+        
+        NSString *title = [[NSString alloc]initWithUTF8String: modelinfo->titles[i]];
+        [[column headerCell] setStringValue:title];
+        
+        [tableview addTableColumn:column];
+    }
+    
+    UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:model modelInfo:modelinfo];
+    [tableview setDataSource:source];
+    [tableview setDelegate:source];
+
+    [tableview setDoubleAction:@selector(handleDoubleAction:)];
+    [tableview setTarget:source];
+    
+    
+    ct->add(ct, scrollview);
+    return scrollview;
+}
+
+
+UIWIDGET ui_listview_var(UiObject *obj, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+    UiContainer *ct = uic_get_current_container(obj);
+    NSRect frame = ct->getframe(ct);
+    
+    NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
+    [scrollview setHasVerticalScroller:YES];
+    
+    [scrollview setBorderType:NSNoBorder];
+    
+    NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
+    [scrollview setDocumentView:tableview];
+    [tableview setAllowsMultipleSelection: NO];
+    
+    // add single column
+    NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:@"c"];
+    [tableview addTableColumn:column];
+    
+    // create model info
+    UiModelInfo *modelinfo = ui_model_info(obj->ctx, UI_STRING, -1);
+    
+    // add source
+    UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:list->list modelInfo:modelinfo];
+    
+    [tableview setDataSource:source];
+    [tableview setDelegate:source];
+
+    [tableview setDoubleAction:@selector(handleDoubleAction:)];
+    [tableview setTarget:source];
+    
+    ct->add(ct, scrollview);
+    return scrollview;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+    UiListPtr *listptr = 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 = var->value;
+        return ui_listview_var(obj, value->listptr, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+
+// TODO: motif code duplicate
+char* ui_type_to_string(UiModelType type, void *data, BOOL *free) {
+    switch(type) {
+        case UI_STRING: *free = FALSE; return data;
+        case UI_INTEGER: {
+            *free = TRUE;
+            int *val = data;
+            sstr_t str = ucx_asprintf(ucx_default_allocator(), "%d", *val);
+            return str.ptr;
+        }
+    }
+    *free = FALSE;
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "../ui/window.h"
+#import <ucx/list.h>
+#import <ucx/map.h>
+
+#import "menu.h"
+
+
+
+@interface UiCocoaWindow : NSWindow {
+    UiObject *uiobj;
+    UcxMap   *menus; // key: NSMenu value: UcxList of UiMenuItem
+    UcxMap   *items; // key: NSMenuItem value: UiMenuItem
+}
+
+- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj;
+- (UiObject*) object;
+- (void) setObject:(UiObject*)obj;
+- (void) setMenuItems:(UcxList*)menuItems;
+- (void) setMenuItemLists:(UcxList*)itemLists;
+- (UiMenuItem*) getMenuItem:(NSMenuItem*)item;
+- (void) updateMenu:(NSMenu*)menu;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/window.m	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#import "window.h"
+#import "menu.h"
+#import "toolbar.h"
+#import "container.h"
+#import <ucx/mempool.h>
+#import "../common/context.h"
+
+static int window_default_width = 600;
+static int window_default_height = 500;
+
+@implementation UiCocoaWindow
+
+- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj {
+    self = [self initWithContentRect:frame
+                           styleMask:NSTitledWindowMask |
+                                     NSResizableWindowMask |
+                                     NSClosableWindowMask |
+                                     NSMiniaturizableWindowMask
+                             backing:NSBackingStoreBuffered
+                               defer:false];
+    
+    uiobj = obj;
+    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+    menus = ucx_map_new_a(allocator, 8);
+    items = ucx_map_new_a(allocator, 64);
+    
+    return self;
+}
+
+- (UiObject*) object {
+    return uiobj;
+}
+
+- (void)  setObject:(UiObject*)obj {
+    uiobj = obj;
+}
+
+- (void) setMenuItems:(UcxList*)menuItems {
+    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+    
+    UCX_FOREACH(elm, menuItems) {
+        UiStateItem *item = elm->data;
+        NSMenu *menu = [item->item menu];
+        
+        // create UiMenuItem which represents an NSMenuItem for a Window
+        UiMenuItem *windowItem = ucx_mempool_malloc(uiobj->ctx->mempool, sizeof(UiMenuItem));
+        windowItem->item = item->item;
+        windowItem->state = 0;
+        if(item->var) {
+            // bind value
+            UiVar *var = uic_connect_var(uiobj->ctx, item->var, UI_VAR_INTEGER);
+            if(var) {
+                UiInteger *value = var->value;
+                value->obj = windowItem;
+                value->get = ui_menuitem_get;
+                value->set = ui_menuitem_set;
+                value = 0;
+            } else {
+                // TODO: error
+            }
+        }
+        
+        // add item
+        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
+        abstractItem->update = ui_update_item;
+        abstractItem->item_data = windowItem;
+        UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
+        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
+        ucx_map_put(menus, ucx_key(&menu, sizeof(void*)), itemList);
+        
+        ucx_map_put(items, ucx_key(&windowItem->item, sizeof(void*)), windowItem);
+    }
+}
+
+- (void) setMenuItemLists:(UcxList*)itemLists {
+    UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+    
+    UCX_FOREACH(elm, itemLists) {
+        UiMenuItemList *list = elm->data;
+        
+        UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
+        abstractItem->update = ui_update_item_list;
+        abstractItem->item_data = list;
+        
+        UcxList *itemList = ucx_map_get(menus, ucx_key(&list->menu, sizeof(void*)));
+        itemList = ucx_list_append_a(allocator, itemList, abstractItem);
+        ucx_map_put(menus, ucx_key(&list->menu, sizeof(void*)), itemList);
+        
+    }
+}
+
+- (UiMenuItem*) getMenuItem:(NSMenuItem*)item {
+    return ucx_map_get(items, ucx_key(&item, sizeof(void*)));
+}
+
+- (void) updateMenu:(NSMenu*)menu {
+    UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
+    UCX_FOREACH(elm, itemList) {
+        UiAbstractMenuItem *item = elm->data;
+        item->update(self, item->item_data);
+    }
+    
+    // update group items
+    // TODO: use only one loop for all items
+    int ngroups = 0;
+    int *groups = ui_active_groups(uiobj->ctx, &ngroups);
+    
+    NSArray *groupItems = [menu itemArray];
+    int count = [groupItems count];
+    for(int i=0;i<count;i++) {
+        id item = [groupItems objectAtIndex:i];
+        if([item class] == [UiGroupMenuItem class]) {
+            [item checkGroups: groups count:ngroups];
+        }
+    }
+    free(groups);
+}
+
+@end
+
+
+/* ------------------------------ public API ------------------------------ */
+
+UiObject* ui_window(char *title, void *window_data) {
+    UcxMempool *mp = ucx_mempool_new(256);
+    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));
+    obj->ctx = uic_context(obj, mp);
+    
+    // create native window
+    NSRect frame = NSMakeRect(
+                              300,
+                              200,
+                              window_default_width,
+                              window_default_height);
+    
+    /*
+    UiCocoaWindow *window = [[UiCocoaWindow alloc] initWithContentRect:frame
+                                styleMask:NSTitledWindowMask | NSResizableWindowMask |
+                                NSClosableWindowMask | NSMiniaturizableWindowMask
+                                backing:NSBackingStoreBuffered
+                                defer:false];
+    */
+    UiCocoaWindow *window = [[UiCocoaWindow alloc] init:frame object:obj];
+    
+    NSString *titleStr = [[NSString alloc] initWithUTF8String:title];
+    [window setTitle:titleStr];
+    
+    UiMenuDelegate *menuDelegate = ui_menu_delegate();
+    [window setMenuItems: [menuDelegate items]];
+    [window setMenuItemLists: [menuDelegate lists]];
+    
+    NSToolbar *toolbar = ui_create_toolbar(obj);
+    [window setToolbar: toolbar];
+    
+    obj->widget = (NSView*)window;
+    obj->window = window_data;
+    obj->container = ui_window_container(obj, window);
+    
+    
+    return obj;
+}
+
+void ui_close(UiObject *obj) {
+    // TODO
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+    NSOpenPanel* op = [NSOpenPanel openPanel];
+    if ([op runModal] == NSOKButton) {
+        NSArray *urls = [op URLs];
+        NSURL *url = [urls objectAtIndex:0];
+        
+        const char *str = [[url path] UTF8String];
+        return (char*)strdup(str);
+    }
+    return NULL;
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+    NSSavePanel* sp = [NSSavePanel savePanel];
+    if ([sp runModal] == NSOKButton) {
+        NSURL *url = [sp URL];
+        
+        const char *str = [[url path] UTF8String];
+        return (char*)strdup(str);
+    }
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/context.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,532 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include <cx/array_list.h>
+#include <cx/compare.h>
+#include <cx/mempool.h>
+
+#include "context.h"
+#include "../ui/window.h"
+#include "document.h"
+#include "types.h"
+
+static UiContext* global_context;
+
+void uic_init_global_context(void) {
+    CxMempool *mp = cxBasicMempoolCreate(32);
+    global_context = uic_context(NULL, mp);
+}
+
+UiContext* ui_global_context(void) {
+    return global_context;
+}
+
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp) {
+    UiContext *ctx = cxMalloc(mp->allocator, sizeof(UiContext));
+    memset(ctx, 0, sizeof(UiContext));
+    ctx->mp = mp;
+    ctx->allocator = mp->allocator;
+    ctx->obj = toplevel;
+    ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16);
+    
+    ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS);
+    ctx->group_widgets = cxLinkedListCreate(mp->allocator, NULL, sizeof(UiGroupWidget));
+    ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32);
+    
+    ctx->attach_document = uic_context_attach_document;
+    ctx->detach_document2 = uic_context_detach_document2;
+    
+#ifdef UI_GTK
+    if(toplevel && toplevel->widget) {
+        ctx->accel_group = gtk_accel_group_new();
+        gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group);
+    }
+#endif
+    
+    return ctx;
+}
+
+UiContext* uic_root_context(UiContext *ctx) {
+    return ctx->parent ? uic_root_context(ctx->parent) : ctx;
+}
+
+void uic_context_attach_document(UiContext *ctx, void *document) {
+    cxListAdd(ctx->documents, document);
+    ctx->document = document;
+    
+    UiContext *doc_ctx = ui_document_context(document);
+    
+    // check if any parent context has an unbound variable with the same name
+    // as any document variable
+    UiContext *var_ctx = ctx;
+    while(var_ctx) {
+        if(var_ctx->vars_unbound && var_ctx->vars_unbound->size > 0) {
+            CxIterator i = cxMapIterator(var_ctx->vars_unbound);
+            cx_foreach(CxMapEntry*, entry, i) {
+                UiVar *var = entry->value;
+                UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
+                if(docvar) {
+                    // bind var to document var
+                    uic_copy_binding(var, docvar, TRUE);
+                    cxIteratorFlagRemoval(i);
+                }
+            }
+        }
+        
+        var_ctx = ctx->parent;
+    }
+}
+
+static void uic_context_unbind_vars(UiContext *ctx) {
+    CxIterator i = cxMapIterator(ctx->vars);
+    cx_foreach(CxMapEntry*, entry, i) {
+        UiVar *var = entry->value;
+        if(var->from && var->from_ctx) {
+            uic_save_var2(var);
+            uic_copy_binding(var, var->from, FALSE);
+            cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from);
+            var->from_ctx = ctx;
+        }
+    }
+    
+    if(ctx->documents) {
+        i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
+            uic_context_unbind_vars(subctx);
+        }
+    }
+}
+
+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) {
+        return;
+    }
+    
+    cxListRemove(ctx->documents, docIndex);
+    ctx->document = cxListAt(ctx->documents, 0);
+    
+    UiContext *docctx = ui_document_context(document);
+    uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
+}
+
+void uic_context_detach_all(UiContext *ctx) {
+    // copy list
+    CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    CxIterator i = cxListIterator(ctx->documents);
+    cx_foreach(void *, doc, i) {
+        cxListAdd(ls, doc);
+    }
+    
+    // detach documents
+    i = cxListIterator(ls);
+    cx_foreach(void *, doc, i) {
+        ctx->detach_document2(ctx, doc);
+    }
+    
+    cxListDestroy(ls);
+}
+
+static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
+    UiVar *var = cxMapGet(ctx->vars, key);
+    if(!var) {
+        CxIterator i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
+            var = ctx_getvar(subctx, key);
+            if(var) {
+                break;
+            }
+        }
+    }
+    return var;
+}
+
+UiVar* uic_get_var(UiContext *ctx, const char *name) {
+    CxHashKey key = cx_hash_key(name, strlen(name));
+    return ctx_getvar(ctx, key);
+}
+
+UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) {
+    UiVar *var = uic_get_var(ctx, name);
+    if(var) {
+        if(var->type == type) {
+            return var;
+        } else {
+            fprintf(stderr, "UiError: var '%s' already bound with different type\n", name);
+        }
+    }
+    
+    var = ui_malloc(ctx, sizeof(UiVar));
+    var->type = type;
+    var->value = uic_create_value(ctx, type);
+    var->from = NULL;
+    var->from_ctx = ctx;
+
+    cxMempoolRegister(ctx->mp, var, (cx_destructor_func)uic_unbind_var);
+
+    if(!ctx->vars_unbound) {
+        ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
+    }
+    cxMapPut(ctx->vars_unbound, name, var);
+    
+    return var;
+}
+
+UiVar* uic_create_value_var(UiContext* ctx, void* value) {
+    UiVar *var = (UiVar*)ui_malloc(ctx, sizeof(UiVar));
+    var->from = NULL;
+    var->from_ctx = NULL;
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return var;
+}
+
+void* uic_create_value(UiContext *ctx, UiVarType type) {
+    void *val = NULL;
+    switch(type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: {
+            val = ui_int_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_DOUBLE: {
+            val = ui_double_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_STRING: {
+            val = ui_string_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_TEXT: {
+            val = ui_text_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_LIST: {
+            val = ui_list_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_RANGE: {
+            val = ui_range_new(ctx, NULL);
+            break;
+        }
+    }
+    return val;
+}
+
+
+UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) {
+    if (value) {
+        return uic_create_value_var(current, value);
+    }
+    if (varname) {
+        return uic_create_var(toplevel, varname, type);
+    }
+    return NULL;
+}
+
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) {
+    // check type
+    if(from->type != to->type) {
+        fprintf(stderr, "UI Error: var has incompatible type.\n");
+        return;
+    }
+    
+    void *fromvalue = from->value;
+    // update var
+    if(copytodoc) {
+        to->from = from;
+        to->from_ctx = from->from_ctx;
+    }
+    
+    // copy binding
+    // we don't copy the observer, because the from var has never one
+    switch(from->type) {
+        default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break;
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: {
+            UiInteger *f = fromvalue;
+            UiInteger *t = to->value;
+            if(!f->obj) break;
+            uic_int_copy(f, t);
+            t->set(t, t->value);
+            break;
+        }
+        case UI_VAR_DOUBLE: {
+            UiDouble *f = fromvalue;
+            UiDouble *t = to->value;
+            if(!f->obj) break;
+            uic_double_copy(f, t);
+            t->set(t, t->value);
+            break;
+        }
+        case UI_VAR_STRING: {
+            UiString *f = fromvalue;
+            UiString *t = to->value;
+            if(!f->obj) break;
+            uic_string_copy(f, t);
+            char *tvalue = t->value.ptr ? t->value.ptr : "";
+            t->set(t, tvalue);
+            break;
+        }
+        case UI_VAR_TEXT: {
+            UiText *f = fromvalue;
+            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);
+            break;
+        }
+        case UI_VAR_LIST: {
+            UiList *f = fromvalue;
+            UiList *t = to->value;
+            if(!f->obj) break;
+            uic_list_copy(f, t);
+            t->update(t, -1);
+            break;
+        }
+        case UI_VAR_RANGE: {
+            UiRange *f = fromvalue;
+            UiRange *t = to->value;
+            if(!f->obj) break;
+            uic_range_copy(f, t);
+            t->setextent(t, t->extent);
+            t->setrange(t, t->min, t->max);
+            t->set(t, t->value);
+            break;
+        }
+    }
+}
+
+void uic_save_var2(UiVar *var) {
+    switch(var->type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: uic_int_save(var->value); break;
+        case UI_VAR_DOUBLE: uic_double_save(var->value); break;
+        case UI_VAR_STRING: uic_string_save(var->value); break;
+        case UI_VAR_TEXT: uic_text_save(var->value); break;
+        case UI_VAR_LIST: break;
+        case UI_VAR_RANGE: uic_range_save(var->value); break;
+    }
+}
+
+void uic_unbind_var(UiVar *var) {
+    switch(var->type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: uic_int_unbind(var->value); break;
+        case UI_VAR_DOUBLE: uic_double_unbind(var->value); break;
+        case UI_VAR_STRING: uic_string_unbind(var->value); break;
+        case UI_VAR_TEXT: uic_text_unbind(var->value); break;
+        case UI_VAR_LIST: uic_list_unbind(var->value); break;
+        case UI_VAR_RANGE: uic_range_unbind(var->value); break;
+    }
+}
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) {
+    // TODO: do we need/want this? Why adding vars to a context after
+    // widgets reference these? Workarounds:
+    // 1. add vars to ctx before creating ui
+    // 2. create ui, create new document with vars, attach doc
+    // also it would be possible to create a function, that scans unbound vars
+    // and connects them to available vars
+    /*
+    UiContext *rootctx = uic_root_context(ctx); 
+    UiVar *b = NULL;
+    if(rootctx->bound) {
+        // some widgets are already bound to some vars
+        b = ucx_map_cstr_get(rootctx->bound, name);
+        if(b) {
+            // a widget is bound to a var with this name
+            // if ctx is the root context we can remove the var from bound
+            // because set_doc or detach can't fuck things up
+            if(ctx == rootctx) {
+                ucx_map_cstr_remove(ctx->bound, name);
+                // TODO: free stuff
+            }
+        }
+    }
+    */
+    
+    // create new var and add it to doc's vars
+    UiVar *var = ui_malloc(ctx, sizeof(UiVar));
+    var->type = type;
+    var->value = value;
+    var->from = NULL;
+    var->from_ctx = ctx;
+    size_t oldcount = ctx->vars->size;
+    cxMapPut(ctx->vars, name, var);
+    if(ctx->vars->size != oldcount + 1) {
+        fprintf(stderr, "UiError: var '%s' already exists\n", name);
+    }
+    
+    // TODO: remove?
+    // a widget is already bound to a var with this name
+    // copy the binding (like uic_context_set_document)
+    /*
+    if(b) {
+        uic_copy_binding(b, var, TRUE);
+    }
+    */
+}
+
+void uic_remove_bound_var(UiContext *ctx, UiVar *var) {
+    // TODO
+}
+
+
+// public API
+
+void ui_attach_document(UiContext *ctx, void *document) {
+    uic_context_attach_document(ctx, document);
+}
+
+void ui_detach_document2(UiContext *ctx, void *document) {
+    uic_context_detach_document2(ctx, document);
+}
+
+void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) {
+    ctx->close_callback = fnc;
+    ctx->close_data = udata;
+}
+
+
+void ui_set_group(UiContext *ctx, int group) {
+    if(cxListFind(ctx->groups, &group) == -1) {
+        cxListAdd(ctx->groups, &group);
+    }
+    
+    // enable/disable group widgets
+    uic_check_group_widgets(ctx);
+}
+
+void ui_unset_group(UiContext *ctx, int group) {
+    int i = cxListFind(ctx->groups, &group);
+    if(i != -1) {
+        cxListRemove(ctx->groups, i);
+    }
+    
+    // enable/disable group widgets
+    uic_check_group_widgets(ctx);
+}
+
+int* ui_active_groups(UiContext *ctx, int *ngroups) {
+    *ngroups = ctx->groups->size;
+    return cxListAt(ctx->groups, 0);
+}
+
+void uic_check_group_widgets(UiContext *ctx) {
+    int ngroups = 0;
+    int *groups = ui_active_groups(ctx, &ngroups);
+    
+    CxIterator i = cxListIterator(ctx->group_widgets);
+    cx_foreach(UiGroupWidget *, gw, i) {
+        char *check = calloc(1, gw->numgroups);
+        
+        for(int i=0;i<ngroups;i++) {
+            for(int k=0;k<gw->numgroups;k++) {
+                if(groups[i] == gw->groups[k]) {
+                    check[k] = 1;
+                }
+            }
+        }
+        
+        int enable = 1;
+        for(int i=0;i<gw->numgroups;i++) {
+            if(check[i] == 0) {
+                enable = 0;
+                break;
+            }
+        }
+        free(check);
+        gw->enable(gw->widget, enable);
+    }
+}
+
+void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
+    // get groups
+    CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+    va_list ap;
+    va_start(ap, enable);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        cxListAdd(groups, &group);
+    }
+    va_end(ap);
+    
+    uic_add_group_widget(ctx, widget, enable, groups);
+    
+    cxListDestroy(groups);
+}
+
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) {
+    const CxAllocator *a = ctx->allocator;
+    UiGroupWidget gw;
+    
+    gw.widget = widget;
+    gw.enable = enable;
+    gw.numgroups = groups->size;
+    gw.groups = cxCalloc(a, gw.numgroups, sizeof(int));
+    
+    // copy groups
+    int *intgroups = cxListAt(groups, 0);
+    if(intgroups) {
+        memcpy(gw.groups, intgroups, gw.numgroups * sizeof(int));
+    }
+    
+    cxListAdd(ctx->group_widgets, &gw);
+}
+
+void* ui_malloc(UiContext *ctx, size_t size) {
+    return ctx ? cxMalloc(ctx->allocator, size) : NULL;
+}
+
+void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) {
+    return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL;
+}
+
+void ui_free(UiContext *ctx, void *ptr) {
+    if(ctx) {
+        cxFree(ctx->allocator, ptr);
+    }
+}
+
+void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
+    return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/context.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,139 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_CONTEXT_H
+#define	UIC_CONTEXT_H
+
+#include "../ui/toolkit.h"
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+#include <cx/list.h>
+#include <cx/linked_list.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiVar         UiVar;
+typedef struct UiListPtr     UiListPtr;
+typedef struct UiListVar     UiListVar;
+typedef struct UiGroupWidget UiGroupWidget;
+
+typedef enum UiVarType UiVarType;
+
+enum UiVarType {
+    UI_VAR_SPECIAL = 0,
+    UI_VAR_INTEGER,
+    UI_VAR_DOUBLE,
+    UI_VAR_STRING,
+    UI_VAR_TEXT,
+    UI_VAR_LIST,
+    UI_VAR_RANGE
+};
+
+struct UiContext {
+    UiContext     *parent;
+    UiObject      *obj;
+    CxMempool *mp;
+    const CxAllocator *allocator;
+    
+    void          *document;
+    CxList        *documents;
+    
+    CxMap         *vars; // manually created context vars
+    CxMap         *vars_unbound; // unbound vars created by widgets
+    
+    CxList        *groups; // int list
+    CxList        *group_widgets; // UiGroupWidget list
+    
+    void (*attach_document)(UiContext *ctx, void *document);
+    void (*detach_document2)(UiContext *ctx, void *document); 
+    
+    char          *title;
+    
+#ifdef UI_GTK
+    GtkAccelGroup *accel_group;
+#endif
+    
+    ui_callback   close_callback;
+    void          *close_data;
+};
+
+// UiVar replacement, rename it to UiVar when finished
+struct UiVar {
+    void      *value;
+    UiVarType type;
+    UiVar    *from;
+    UiContext *from_ctx;
+};
+
+struct UiGroupWidget {
+    void          *widget;
+    ui_enablefunc enable;
+    int           *groups;
+    int           numgroups;
+};
+
+
+void uic_init_global_context(void);
+
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp);
+UiContext* uic_root_context(UiContext *ctx);
+void uic_context_set_document(UiContext *ctx, void *document); // deprecated
+void uic_context_detach_document(UiContext *ctx); // deprecated
+
+void uic_context_attach_document(UiContext *ctx, void *document);
+void uic_context_detach_document2(UiContext *ctx, void *document);
+void uic_context_detach_all(UiContext *ctx);
+
+UiVar* uic_get_var(UiContext *ctx, const char *name);
+UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type);
+UiVar* uic_create_value_var(UiContext *ctx, void *value);
+void* uic_create_value(UiContext *ctx, UiVarType type);
+
+UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type);
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc);
+void uic_save_var2(UiVar *var);
+void uic_unbind_var(UiVar *var);
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value);
+
+void uic_remove_bound_var(UiContext *ctx, UiVar *var);
+
+void uic_check_group_widgets(UiContext *ctx);
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UIC_CONTEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/document.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,106 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "document.h"
+
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+
+static CxMap *documents;
+
+void uic_docmgr_init() {
+    documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+}
+
+void ui_set_document(UiObject *obj, void *document) {
+    uic_context_detach_all(obj->ctx);
+    obj->ctx->attach_document(obj->ctx, document);
+}
+
+void ui_detach_document(UiObject *obj) {
+    uic_context_detach_all(obj->ctx);
+}
+
+void* ui_get_document(UiObject *obj) {
+    return obj->ctx->document;
+}
+
+void ui_set_subdocument(void *document, void *sub) {
+    UiContext *ctx = ui_document_context(document);
+    if(!ctx) {
+        fprintf(stderr, "UI Error: pointer is not a document\n");
+    }
+    // TODO
+}
+
+void ui_detach_subdocument(void *document, void *sub) {
+    UiContext *ctx = ui_document_context(document);
+    if(!ctx) {
+        fprintf(stderr, "UI Error: pointer is not a document\n");
+    }
+    // TODO
+}
+
+void* ui_get_subdocument(void *document) {
+    UiContext *ctx = ui_document_context(document);
+    if(!ctx) {
+        fprintf(stderr, "UI Error: pointer is not a document\n");
+    }
+    // TODO
+    return NULL;
+}
+
+void* ui_document_new(size_t size) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    const CxAllocator *a = mp->allocator;
+    UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
+    ctx->attach_document = uic_context_attach_document;
+    ctx->detach_document2 = uic_context_detach_document2;
+    ctx->allocator = a;
+    ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
+    
+    void *document = cxCalloc(a, 1, size);
+    cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
+    return document;
+}
+
+void ui_document_destroy(void *doc) {
+    // TODO
+}
+
+UiContext* ui_document_context(void *doc) {
+    if(doc) {
+        return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
+    } else {
+        return NULL;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/document.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_DOCUMENT_H
+#define	UIC_DOCUMENT_H
+
+#include "../ui/toolkit.h"
+#include "context.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+void uic_docmgr_init();
+void uic_document_addvar(void *doc, char *name, int type, size_t vs);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UIC_DOCUMENT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/menu.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,259 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "menu.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+static UiMenu *menus_begin;
+static UiMenu *menus_end;
+static CxList *current;
+
+static void add_menu(UiMenu *menu) {
+    cx_linked_list_add(
+            (void**)&menus_begin,
+            (void**)&menus_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            menu);
+}
+
+static void add_item(UiMenuItemI *item) {
+    UiMenu *menu = cxListAt(current, 0);
+    cx_linked_list_add(
+            (void**)&menu->items_begin,
+            (void**)&menu->items_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            item);
+}
+
+static char* nl_strdup(const char* s) {
+    return s ? strdup(s) : NULL;
+}
+
+static int* copy_groups(int* groups, size_t *ngroups) {
+    *ngroups = 0;
+    if (!groups) {
+        return NULL;
+    }
+
+    size_t n;
+    for (n = 0; groups[n] > -1; n++) { }
+
+    if (ngroups > 0) {
+        int* newarray = calloc(n, sizeof(int));
+        memcpy(newarray, groups, n);
+        *ngroups = n;
+        return newarray;
+    }
+    return NULL;
+}
+
+void ui_menu_create(const char *label) {
+    if(!current) {
+        current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    }
+    
+    // create menu
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.prev = NULL;
+    menu->item.next = NULL;
+    menu->item.type = UI_MENU;
+    
+    menu->label        = label;
+    menu->items_begin  = NULL;
+    menu->items_end    = NULL;
+    menu->parent       = NULL;    
+
+    menu->end = 0;
+
+    if (current->size == 0) {
+        add_menu(menu);
+    }
+    else {
+        add_item((UiMenuItemI*)menu);
+    }
+    uic_add_menu_to_stack(menu);
+}
+
+UIEXPORT void ui_menu_end(void) {
+    cxListRemove(current, 0);
+}
+
+
+
+void ui_menuitem_create(UiMenuItemArgs args) {
+    if (!current) {
+        return; // error?
+    }
+
+    UiMenuItem* item = malloc(sizeof(UiMenuItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->userdata = args.onclickdata;
+    item->callback = args.onclick;
+    item->groups = copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menuseparator() {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->prev = NULL;
+    item->next = NULL;
+    item->type = UI_MENU_SEPARATOR;
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_CHECK_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->varname = nl_strdup(args.varname);
+    item->userdata = args.onchangedata;
+    item->callback = args.onchange;
+    item->groups = copy_groups(args.groups, &item->ngroups);
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitem_create(UiMenuToggleItemArgs args) {
+    if (!current) {
+        return;
+    }
+
+    UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_CHECK_ITEM;
+
+    item->label = nl_strdup(args.label);
+    item->stockid = nl_strdup(args.stockid);
+    item->icon = nl_strdup(args.icon);
+    item->varname = nl_strdup(args.varname);
+    item->userdata = args.onchangedata;
+    item->callback = args.onchange;
+    item->groups = copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_itemlist_create(UiMenuItemListArgs args) {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemList*item = malloc(sizeof(UiMenuItemList));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM_LIST;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_checkitemlist_create(UiMenuItemListArgs args) {
+    if (!current) {
+        return;
+    }
+
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM_LIST;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitemlist_create(UiMenuItemListArgs args) {
+    if (!current) {
+        return;
+    }
+
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM_LIST;
+    item->callback = args.onselect;
+    item->userdata = args.onselectdata;
+    item->varname = nl_strdup(args.varname);
+
+    add_item((UiMenuItemI*)item);
+}
+
+
+void uic_add_menu_to_stack(UiMenu* menu) {
+    cxListInsert(current, 0, menu);
+}
+
+UiMenu* uic_get_menu_list(void) {
+    return menus_begin;
+}
+
+UIEXPORT void ui_menu_close(void) {
+    UiMenu* menu = cxListAt(current, 0);
+    menu->end = 1;
+}
+
+UIEXPORT int ui_menu_is_open(void) {
+    UiMenu* menu = cxListAt(current, 0);
+    if (menu->end) {
+        ui_menu_end();
+        return 0;
+    }
+    return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,126 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 UIC_MENU_H
+#define UIC_MENU_H
+
+#include "../ui/menu.h"
+
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiMenuItemI      UiMenuItemI;
+typedef struct UiMenu           UiMenu;
+typedef struct UiMenuItem       UiMenuItem;
+typedef struct UiMenuCheckItem  UiMenuCheckItem;
+typedef struct UiMenuRadioItem  UiMenuRadioItem;
+typedef struct UiMenuItemList   UiMenuItemList;
+    
+enum UiMenuItemType {
+    UI_MENU = 0,
+    UI_MENU_ITEM,
+    UI_MENU_CHECK_ITEM,
+    UI_MENU_RADIO_ITEM,
+    UI_MENU_ITEM_LIST,
+    UI_MENU_CHECKITEM_LIST,
+    UI_MENU_RADIOITEM_LIST,
+    UI_MENU_SEPARATOR
+};
+
+typedef enum UiMenuItemType UiMenuItemType;
+    
+struct UiMenuItemI {
+    UiMenuItemI    *prev;
+    UiMenuItemI    *next;
+    UiMenuItemType type;
+};
+
+struct UiMenu {
+    UiMenuItemI    item;
+    char           *label;
+    UiMenuItemI    *items_begin;
+    UiMenuItemI    *items_end;
+    UiMenu         *parent;
+    int            end;
+};
+
+struct UiMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    const char     *label;
+    const char     *stockid;
+    const char     *icon;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuCheckItem {
+    UiMenuItemI    item;
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    const char* varname;
+    ui_callback    callback;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuRadioItem {
+    UiMenuItemI    item;
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    ui_callback    callback;
+    void* userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuItemList {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    void           *userdata;
+    const char* varname;
+};
+
+
+UiMenu* uic_get_menu_list(void);
+
+void uic_add_menu_to_stack(UiMenu* menu);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/object.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,81 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "object.h"
+#include "context.h"
+
+void ui_end(UiObject *obj) {
+    if(!obj->next) {
+        return;
+    }
+    
+    UiObject *prev = NULL;
+    while(obj->next) {
+        prev = obj;
+        obj = obj->next;
+    }
+    
+    if(prev) {
+        // TODO: free last obj
+        prev->next = NULL;
+    }
+}
+
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
+    UiContext *ctx = toplevel->ctx;
+    
+    UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject));
+    newobj->ctx = ctx;
+    newobj->widget = widget;
+    
+    return newobj;
+}
+
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj) {
+    UiObject *current = uic_current_obj(toplevel);
+    current->next = ctobj;
+}
+
+UiObject* uic_current_obj(UiObject *toplevel) {
+    if(!toplevel) {
+        return NULL;
+    }
+    UiObject *obj = toplevel;
+    while(obj->next) {
+        obj = obj->next;
+    }
+    return obj;
+}
+
+UiContainer* uic_get_current_container(UiObject *obj) {
+    return uic_current_obj(obj)->container;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/object.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_OBJECT_H
+#define	UIC_OBJECT_H
+
+#include "../ui/toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
+UiObject* uic_current_obj(UiObject *toplevel);
+
+UiContainer* uic_get_current_container(UiObject *obj);;
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UIC_OBJECT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,42 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+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 += ucx_properties.o
+
+TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
+TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/properties.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,299 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "properties.h"
+#include <cx/string.h>
+#include <cx/buffer.h>
+
+#include "ucx_properties.h"
+
+static CxMap *application_properties;
+static CxMap *language;
+
+#ifndef UI_COCOA
+
+static char *locales_dir;
+static char *pixmaps_dir;
+
+#endif
+
+char* ui_getappdir() {
+    if(ui_appname() == NULL) {
+        return NULL;
+    }
+    
+    return ui_configfile(NULL);
+}
+
+char* ui_configfile(char *name) {
+    char *appname = ui_appname();
+    if(!appname) {
+        return NULL;
+    }
+    
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // add base dir
+    char *homeenv = getenv("HOME");
+    if(homeenv == NULL) {
+        cxBufferDestroy(&buf);
+        return NULL;
+    }
+    cxstring home = cx_str(homeenv);
+    
+    cxBufferWrite(home.ptr, 1, home.length, &buf);
+    if(home.ptr[home.length-1] != '/') {
+        cxBufferPut(&buf, '/');
+    }
+    
+#ifdef UI_COCOA
+    // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/
+    ucx_buffer_puts(buf, "Library/Application Support/");
+#else
+    // app dir is $HOME/.$APPNAME/
+    cxBufferPut(&buf, '.');
+#endif
+    cxBufferPutString(&buf, appname);
+    cxBufferPut(&buf, '/');
+    
+    // add file name
+    if(name) {
+        cxBufferPutString(&buf, name);
+    }
+    
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
+}
+
+static int ui_mkdir(char *path) {
+#ifdef _WIN32
+    return mkdir(path);
+#else
+    return mkdir(path, S_IRWXU);
+#endif
+} 
+
+void uic_load_app_properties() {
+    application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128);
+    
+    if(!ui_appname()) {
+        // applications without name cannot load app properties
+        return;
+    }
+    
+    char *dir = ui_configfile(NULL);
+    if(!dir) {
+        return;
+    }
+    if(ui_mkdir(dir)) {
+        if(errno != EEXIST) {
+            fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir);
+            free(dir);
+            return;
+        }
+    }
+    free(dir);
+    
+    char *path = ui_configfile("application.properties");
+    if(!path) {
+        return;
+    }
+    
+    FILE *file = fopen(path, "r");
+    if(!file) {
+        free(path);
+        return;
+    }
+    
+    if(ucx_properties_load(application_properties, file)) {
+        fprintf(stderr, "Ui Error: Cannot load application properties.\n");
+    }
+    
+    fclose(file);
+    free(path);
+}
+
+void uic_store_app_properties() {
+    char *path = ui_configfile("application.properties");
+    if(!path) {
+        return;
+    }
+    
+    FILE *file = fopen(path, "w");
+    if(!file) {
+        fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path);
+        free(path);
+        return;
+    }
+    
+    if(ucx_properties_store(application_properties, file)) {
+        fprintf(stderr, "Ui Error: Cannot store application properties.\n");
+    }
+    
+    fclose(file);
+    free(path);
+}
+
+
+char* ui_get_property(char *name) {
+    return cxMapGet(application_properties, name);
+}
+
+void ui_set_property(char *name, char *value) {
+    cxMapPut(application_properties, name, value);
+}
+
+void ui_set_default_property(char *name, char *value) {
+    char *v = cxMapGet(application_properties, name);
+    if(!v) {
+        cxMapPut(application_properties, name, value);
+    }
+}
+
+
+
+static char* uic_concat_path(const char *base, const char *p, const char *ext) {
+    size_t baselen = strlen(base);
+    
+    CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(baselen > 0) {
+        cxBufferWrite(base, 1, baselen, buf);
+        if(base[baselen - 1] != '/') {
+            cxBufferPut(buf, '/');
+        }
+    }
+    cxBufferWrite(p, 1, strlen(p), buf);
+    if(ext) {
+        cxBufferWrite(ext, 1, strlen(ext), buf);
+    }
+    
+    char *str = buf->space;
+    free(buf);
+    return str;
+}
+
+#ifndef UI_COCOA
+
+void ui_locales_dir(char *path) {
+    locales_dir = path;
+}
+
+void ui_pixmaps_dir(char *path) {
+    pixmaps_dir = path;
+}
+
+char* uic_get_image_path(const char *imgfilename) {
+    if(pixmaps_dir) {
+        return uic_concat_path(pixmaps_dir, imgfilename, NULL);
+    } else {
+        return NULL;
+    }
+}
+
+void ui_load_lang(char *locale) {
+    if(!locale) {
+        locale = "en_EN";
+    }
+    
+    char *path = uic_concat_path(locales_dir, locale, ".properties");
+    
+    uic_load_language_file(path);
+    free(path);
+}
+
+void ui_load_lang_def(char *locale, char *default_locale) {
+    char tmp[6];
+    if(!locale) {
+        char *lang = getenv("LANG");
+        if(lang && strlen(lang) >= 5) {
+            memcpy(tmp, lang, 5);
+            tmp[5] = '\0';
+            locale = tmp;
+        } else {
+            locale = default_locale;
+        }
+    }
+    
+    char *path = uic_concat_path(locales_dir, locale, ".properties");
+    
+    if(uic_load_language_file(path)) {
+        if(default_locale) {
+            ui_load_lang_def(default_locale, NULL);
+        } else {
+            // cannot find any language file
+            fprintf(stderr, "Ui Error: Cannot load language.\n");
+            free(path);
+            exit(-1);
+        }
+    }
+    free(path);
+}
+
+#endif
+
+int uic_load_language_file(const char *path) {
+    CxMap *lang = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256);
+    
+    FILE *file = fopen(path, "r");
+    if(!file) {
+        return 1;
+    }
+    
+    if(ucx_properties_load(lang, file)) {
+        fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path);
+    }
+    
+    fclose(file);
+    
+    cxMapRehash(lang);
+    
+    language = lang;
+    
+    return 0;
+}
+
+char* uistr(char *name) {
+    char *value = uistr_n(name);
+    return value ? value : "missing string";
+}
+
+char* uistr_n(char *name) {
+    if(!language) {
+        return NULL;
+    }
+    return cxMapGet(language, name);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/properties.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,57 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_PROPERTIES_H
+#define	UIC_PROPERTIES_H
+
+#include "../ui/properties.h"
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+#define UI_HOME "USERPROFILE"
+#else
+#define UI_HOME "HOME"
+#endif
+
+void uic_load_app_properties();
+void uic_store_app_properties();
+
+int uic_load_language_file(const char *path);
+char* uic_get_image_path(const char *imgfilename);
+    
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UIC_PROPERTIES_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/toolbar.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,144 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "toolbar.h"
+
+#include <string.h>
+
+static CxMap* toolbar_items;
+
+static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right
+
+static UiToolbarMenuItem* ui_appmenu;
+
+void uic_toolbar_init(void) {
+	toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+	toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+	toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+	toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+}
+
+static char* nl_strdup(char* str) {
+	return str ? strdup(str) : NULL;
+}
+
+static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args) {
+	UiToolbarItemArgs newargs;
+	newargs.label = nl_strdup(args.label);
+	newargs.stockid = nl_strdup(args.stockid);
+	newargs.icon = nl_strdup(args.icon);
+	newargs.onclick = args.onclick;
+	newargs.onclickdata = args.onclickdata;
+	return newargs;
+}
+
+void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args) {
+	UiToolbarItem* item = malloc(sizeof(UiToolbarItem));
+	item->item.type = UI_TOOLBAR_ITEM;
+	item->args = itemargs_copy(args);
+	cxMapPut(toolbar_items, name, item);
+}
+
+
+static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args) {
+	UiToolbarToggleItemArgs newargs;
+	newargs.label = nl_strdup(args.label);
+	newargs.stockid = nl_strdup(args.stockid);
+	newargs.icon = nl_strdup(args.icon);
+	newargs.varname = nl_strdup(args.varname);
+	newargs.onchange = args.onchange;
+	newargs.onchangedata = args.onchangedata;
+	return newargs;
+}
+
+void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args) {
+	UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem));
+	item->item.type = UI_TOOLBAR_TOGGLEITEM;
+	item->args = toggleitemargs_copy(args);
+	cxMapPut(toolbar_items, name, item);
+}
+
+static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) {
+	UiToolbarMenuArgs newargs;
+	newargs.label = nl_strdup(args.label);
+	newargs.stockid = nl_strdup(args.stockid);
+	newargs.icon = nl_strdup(args.icon);
+	return newargs;
+}
+
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) {
+	UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem));
+	item->item.type = UI_TOOLBAR_MENU;
+	memset(&item->menu, 0, sizeof(UiMenu));
+	item->args = menuargs_copy(args);
+	
+	item->end = 0;
+
+	if (!name) {
+		// special appmenu
+		ui_appmenu = item;
+	} else {
+		// toplevel menu
+		cxMapPut(toolbar_items, name, item);
+	}
+
+	uic_add_menu_to_stack(&item->menu);
+}
+
+
+CxMap* uic_get_toolbar_items(void) {
+	return toolbar_items;
+}
+
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {
+	if (pos >= 0 && pos < 3) {
+		return toolbar_defaults[pos];
+	}
+}
+
+void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) {
+	char* cp = strdup(name);
+	if (pos >= 0 && pos < 3) {
+		cxListAdd(toolbar_defaults[pos], cp);
+	}
+	else {
+		// TODO: error
+	}
+}
+
+UiBool uic_toolbar_isenabled(void) {
+	return toolbar_defaults[0]->size + toolbar_defaults[1]->size + toolbar_defaults[2]->size > 0;
+}
+
+UiToolbarItemI* uic_toolbar_get_item(const char* name) {
+	return cxMapGet(toolbar_items, name);
+}
+
+UiToolbarMenuItem* uic_get_appmenu(void) {
+	return ui_appmenu;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,96 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 UIC_TOOLBAR_H
+#define UIC_TOOLBAR_H
+
+#include "../ui/toolbar.h"
+
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+
+#include "menu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolbarItemI      UiToolbarItemI;
+
+typedef struct UiToolbarItem       UiToolbarItem;
+typedef struct UiToolbarToggleItem UiToolbarToggleItem;
+
+typedef struct UiToolbarMenuItem   UiToolbarMenuItem;
+
+enum UiToolbarItemType {
+    UI_TOOLBAR_ITEM = 0,
+    UI_TOOLBAR_TOGGLEITEM,
+    UI_TOOLBAR_MENU
+};
+
+typedef enum UiToolbarItemType UiToolbarItemType;
+
+struct UiToolbarItemI {
+    UiToolbarItemType type;
+};
+
+struct UiToolbarItem {
+    UiToolbarItemI item;
+    UiToolbarItemArgs args;
+};
+
+struct UiToolbarToggleItem {
+    UiToolbarItemI item;
+    UiToolbarToggleItemArgs args;
+};
+
+struct UiToolbarMenuItem {
+    UiToolbarItemI item;
+    UiMenu menu;
+    UiToolbarMenuArgs args;
+    int end;
+};
+
+
+void uic_toolbar_init(void);
+
+CxMap* uic_get_toolbar_items(void);
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos);
+
+UiBool uic_toolbar_isenabled(void);
+
+UiToolbarItemI* uic_toolbar_get_item(const char* name);
+
+UiToolbarMenuItem* uic_get_appmenu(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TOOLBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/types.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,398 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <cx/list.h>
+#include <cx/array_list.h>
+#include "../ui/tree.h"
+#include "types.h"
+#include "context.h"
+
+UiObserver* ui_observer_new(ui_callback f, void *data) {
+    UiObserver *observer = malloc(sizeof(UiObserver));
+    observer->callback = f;
+    observer->data = data;
+    observer->next = NULL;
+    return observer;
+}
+
+UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer) {
+    if(!list) {
+        return observer;
+    } else {
+        UiObserver *l = list;
+        while(l->next) {
+            l = l->next;
+        }
+        l->next = observer;
+        return list;
+    }
+}
+
+UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data) {
+    UiObserver *observer = ui_observer_new(f, data);
+    return ui_obsvlist_add(list, observer);
+}
+
+void ui_notify(UiObserver *observer, void *data) {
+    ui_notify_except(observer, NULL, data);
+}
+
+void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data) {
+    UiEvent evt;
+    evt.obj = NULL;
+    evt.window = NULL;
+    evt.document = NULL;
+    evt.eventdata = data;
+    evt.intval = 0;
+    
+    while(observer) {
+        if(observer != exc) { 
+            observer->callback(&evt, observer->data);
+        }
+        observer = observer->next;
+    }
+}
+
+void ui_notify_evt(UiObserver *observer, UiEvent *event) {
+    while(observer) {
+        observer->callback(event, observer->data);
+        observer = observer->next;
+    }
+}
+
+/* --------------------------- UiList --------------------------- */
+
+UiList* ui_list_new(UiContext *ctx, char *name) {
+    UiList *list = malloc(sizeof(UiList));
+    list->first = ui_list_first;
+    list->next = ui_list_next;
+    list->get = ui_list_get;
+    list->count = ui_list_count;
+    list->observers = NULL;
+    
+    list->data = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 32);
+    list->iter = NULL;
+    
+    list->update = NULL;
+    list->obj = NULL;
+    
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_LIST, list);
+    }
+    
+    return list;
+}
+
+void ui_list_free(UiList *list) {
+    cxListDestroy(list->data);
+    free(list);
+}
+
+void* ui_list_first(UiList *list) {
+    list->iter = (void*)(intptr_t)0;
+    return cxListAt(list->data, 0);
+}
+
+void* ui_list_next(UiList *list) {
+    intptr_t iter = (intptr_t)list->iter;
+    iter++;
+    void *elm = cxListAt(list->data, iter);
+    if(elm) {
+        list->iter = (void*)iter;
+    }
+    return elm;
+}
+
+void* ui_list_get(UiList *list, int i) {
+    return cxListAt(list->data, i);
+}
+
+int ui_list_count(UiList *list) {
+    return ((CxList*)list->data)->size;
+}
+
+void ui_list_append(UiList *list, void *data) {
+    cxListAdd(list->data, data);
+}
+
+void ui_list_prepend(UiList *list, void *data) {
+    cxListInsert(list->data, 0, data);
+}
+
+void ui_list_clear(UiList *list) {
+    cxListClear(list->data);
+}
+
+void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
+    list->observers = ui_add_observer(list->observers, f, data);
+}
+
+void ui_list_notify(UiList *list) {
+    ui_notify(list->observers, list);
+}
+
+
+typedef struct {
+    int  type;
+    char *name;
+} UiColumn;
+
+UiModel* ui_model(UiContext *ctx, ...) {
+    UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel));
+    
+    va_list ap;
+    va_start(ap, ctx);
+    
+    CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32);
+    int type;
+    while((type = va_arg(ap, int)) != -1) {
+        char *name = va_arg(ap, char*);
+        
+        UiColumn column;
+        column.type = type;
+        column.name = name;
+        
+        cxListAdd(cols, &column);
+    }
+    
+    va_end(ap);
+    
+    size_t len = cols->size;
+    info->columns = len;
+    info->types = ui_calloc(ctx, len, sizeof(UiModelType));
+    info->titles = ui_calloc(ctx, len, sizeof(char*));
+    
+    int i = 0;
+    CxIterator iter = cxListIterator(cols);
+    cx_foreach(UiColumn*, c, iter) {
+        info->types[i] = c->type;
+        info->titles[i] = c->name;
+        i++;
+    }
+    cxListDestroy(cols);
+    
+    return info;
+}
+
+UiModel* ui_model_copy(UiContext *ctx, UiModel* model) {
+    const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+
+    UiModel* newmodel = cxMalloc(a, sizeof(UiModel));
+    *newmodel = *model;
+
+    newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType));
+    memcpy(newmodel->types, model->types, model->columns);
+
+    newmodel->titles = cxCalloc(a, model->columns, sizeof(char*));
+    for (int i = 0; i < model->columns; i++) {
+        newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL;
+    }
+
+    return newmodel;
+}
+
+void ui_model_free(UiContext *ctx, UiModel *mi) {
+    const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+    cxFree(a, mi->types);
+    cxFree(a, mi->titles);
+    cxFree(a, mi);
+}
+
+// types
+
+// public functions
+UiInteger* ui_int_new(UiContext *ctx, char *name) {
+    UiInteger *i = ui_malloc(ctx, sizeof(UiInteger));
+    memset(i, 0, sizeof(UiInteger));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_INTEGER, i);
+    }
+    return i;
+}
+
+UiDouble* ui_double_new(UiContext *ctx, char *name) {
+    UiDouble *d = ui_malloc(ctx, sizeof(UiDouble));
+    memset(d, 0, sizeof(UiDouble));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_DOUBLE, d);
+    }
+    return d;
+}
+
+UiString* ui_string_new(UiContext *ctx, char *name) {
+    UiString *s = ui_malloc(ctx, sizeof(UiString));
+    memset(s, 0, sizeof(UiString));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_STRING, s);
+    }
+    return s;
+}
+
+UiText* ui_text_new(UiContext *ctx, char *name) {
+    UiText *t = ui_malloc(ctx, sizeof(UiText));
+    memset(t, 0, sizeof(UiText));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_TEXT, t);
+    }
+    return t;
+}
+
+UiRange* ui_range_new(UiContext *ctx, char *name) {
+    UiRange *r = ui_malloc(ctx, sizeof(UiRange));
+    memset(r, 0, sizeof(UiRange));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_RANGE, r);
+    }
+    return r;
+}
+
+
+// private functions
+void uic_int_copy(UiInteger *from, UiInteger *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_double_copy(UiDouble *from, UiDouble *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_string_copy(UiString *from, UiString *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_text_copy(UiText *from, UiText *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->getsubstr = from->getsubstr;
+    to->insert = from->insert;
+    to->setposition = from->setposition;
+    to->position = from->position;
+    to->selection = from->selection;
+    to->length = from->length;
+    to->remove = from->remove;
+    
+    to->obj = from->obj;
+    // do not copy the undo manager
+}
+
+void uic_range_copy(UiRange *from, UiRange *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->setrange = from->setrange;
+    to->setextent = from->setextent;
+    to->obj = from->obj;
+}
+
+void uic_list_copy(UiList *from, UiList *to) {
+    to->update = from->update;
+    to->obj = from->obj;
+}
+
+
+void uic_int_save(UiInteger *i) {
+    if(!i->obj) return;
+    i->value = i->get(i);
+}
+
+void uic_double_save(UiDouble *d) {
+    if(!d->obj) return;
+    d->value = d->get(d);
+}
+
+void uic_string_save(UiString *s) {
+    if(!s->obj) return;
+    s->get(s);
+}
+
+void uic_text_save(UiText *t) {
+    if(!t->obj) return;
+    t->get(t);
+    t->position(t);
+}
+
+void uic_range_save(UiRange *r) {
+    if(!r->obj) return;
+    r->get(r);
+}
+
+
+void uic_int_unbind(UiInteger *i) {
+    i->get = NULL;
+    i->set = NULL;
+    i->obj = NULL;
+}
+
+void uic_double_unbind(UiDouble *d) {
+    d->get = NULL;
+    d->set = NULL;
+    d->obj = NULL;
+}
+
+void uic_string_unbind(UiString *s) {
+    s->get = NULL;
+    s->set = NULL;
+    s->obj = NULL;
+}
+
+void uic_text_unbind(UiText *t) {
+    t->set = NULL;
+    t->get = NULL;
+    t->getsubstr = NULL;
+    t->insert = NULL;
+    t->setposition = NULL;
+    t->position = NULL;
+    t->selection = NULL;
+    t->length = NULL;
+    t->remove = NULL;
+    t->obj = NULL;
+    t->undomgr = NULL;
+}
+
+void uic_range_unbind(UiRange *r) {
+    r->get = NULL;
+    r->set = NULL;
+    r->setextent = NULL;
+    r->setrange = NULL;
+    r->obj = NULL;
+}
+
+void uic_list_unbind(UiList *l) {
+    l->update = NULL;
+    l->obj = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/types.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_TYPES_H
+#define	UIC_TYPES_H
+
+#include "../ui/toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+void uic_int_copy(UiInteger *from, UiInteger *to);
+void uic_double_copy(UiDouble *from, UiDouble *to);
+void uic_string_copy(UiString *from, UiString *to);
+void uic_text_copy(UiText *from, UiText *to);
+void uic_range_copy(UiRange *from, UiRange *to);
+void uic_list_copy(UiList *from, UiList *to);
+
+void uic_int_save(UiInteger *i);
+void uic_double_save(UiDouble *d);
+void uic_string_save(UiString *s);
+void uic_text_save(UiText *t);
+void uic_range_save(UiRange *r);
+
+void uic_int_unbind(UiInteger *i);
+void uic_double_unbind(UiDouble *d);
+void uic_string_unbind(UiString *s);
+void uic_text_unbind(UiText *t);
+void uic_range_unbind(UiRange *r);
+void uic_list_unbind(UiList *l);
+  
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UIC_TYPES_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,263 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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 "ucx_properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxProperties *ucx_properties_new() {
+    UcxProperties *parser = (UcxProperties*)malloc(
+            sizeof(UcxProperties));
+    if(!parser) {
+        return NULL;
+    }
+    
+    parser->buffer = NULL;
+    parser->buflen = 0;
+    parser->pos = 0;
+    parser->tmp = NULL;
+    parser->tmplen = 0;
+    parser->tmpcap = 0;
+    parser->error = 0;
+    parser->delimiter = '=';
+    parser->comment1 = '#';
+    parser->comment2 = 0;
+    parser->comment3 = 0;   
+    
+    return parser;
+}
+
+void ucx_properties_free(UcxProperties *parser) {
+    if(parser->tmp) {
+        free(parser->tmp);
+    }
+    free(parser);
+}
+
+void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
+    parser->buffer = buf;
+    parser->buflen = len;
+    parser->pos = 0;
+}
+
+static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
+    if(parser->tmpcap - parser->tmplen < len) {
+        size_t newcap = parser->tmpcap + len + 64;
+        parser->tmp = (char*)realloc(parser->tmp, newcap);
+        parser->tmpcap = newcap;
+    }
+    memcpy(parser->tmp + parser->tmplen, buf, len);
+    parser->tmplen += len;
+}
+
+int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value)  {   
+    if(parser->tmplen > 0) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        cxstring str = cx_strn(buf, len);
+        cxstring nl = cx_strchr(str, '\n');
+        if(nl.ptr) {
+            size_t newlen = (size_t)(nl.ptr - buf) + 1;
+            parser_tmp_append(parser, buf, newlen);
+            // the tmp buffer contains exactly one line now
+            
+            char *orig_buf = parser->buffer;
+            size_t orig_len = parser->buflen;
+            
+            parser->buffer = parser->tmp;
+            parser->buflen = parser->tmplen;
+            parser->pos = 0;    
+            parser->tmp = NULL;
+            parser->tmpcap = 0;
+            parser->tmplen = 0;
+            // run ucx_properties_next with the tmp buffer as main buffer
+            int ret = ucx_properties_next(parser, name, value);
+            
+            // restore original buffer
+            parser->tmp = parser->buffer;
+            parser->buffer = orig_buf;
+            parser->buflen = orig_len;
+            parser->pos = newlen;
+            
+            /*
+             * if ret == 0 the tmp buffer contained just space or a comment
+             * we parse again with the original buffer to get a name/value
+             * or a new tmp buffer
+             */
+            return ret ? ret : ucx_properties_next(parser, name, value);
+        } else {
+            parser_tmp_append(parser, buf, len);
+            return 0;
+        }
+    } else if(parser->tmp) {
+        free(parser->tmp);
+        parser->tmp = NULL;
+    }
+    
+    char comment1 = parser->comment1;
+    char comment2 = parser->comment2;
+    char comment3 = parser->comment3;
+    char delimiter = parser->delimiter;
+    
+    // get one line and parse it
+    while(parser->pos < parser->buflen) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        
+        /*
+         * First we check if we have at least one line. We also get indices of
+         * delimiter and comment chars
+         */
+        size_t delimiter_index = 0;
+        size_t comment_index = 0;
+        int has_comment = 0;
+
+        size_t i = 0;
+        char c = 0;
+        for(;i<len;i++) {
+            c = buf[i];
+            if(c == comment1 || c == comment2 || c == comment3) {
+                if(comment_index == 0) {
+                    comment_index = i;
+                    has_comment = 1;
+                }
+            } else if(c == delimiter) {
+                if(delimiter_index == 0 && !has_comment) {
+                    delimiter_index = i;
+                }
+            } else if(c == '\n') {
+                break;
+            }
+        }
+
+        if(c != '\n') {
+            // we don't have enough data for a line
+            // store remaining bytes in temporary buffer for next round
+            parser->tmpcap = len + 128;
+            parser->tmp = (char*)malloc(parser->tmpcap);
+            parser->tmplen = len;
+            memcpy(parser->tmp, buf, len);
+            return 0;
+        }
+        
+        cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i);
+        // check line
+        if(delimiter_index == 0) {
+            line = cx_strtrim(line);
+            if(line.length != 0) {
+                parser->error = 1;
+            }
+        } else {
+            cxstring n = cx_strn(buf, delimiter_index);
+            cxstring v = cx_strn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1); 
+            n = cx_strtrim(n);
+            v = cx_strtrim(v);
+            if(n.length != 0 || v.length != 0) {
+                *name = n;
+                *value = v;
+                parser->pos += i + 1;
+                return 1;
+            } else {
+                parser->error = 1;
+            }
+        }
+        
+        parser->pos += i + 1;
+    }
+    
+    return 0;
+}
+
+int ucx_properties2map(UcxProperties *parser, CxMap *map) {
+    cxstring name;
+    cxstring value;
+    while(ucx_properties_next(parser, &name, &value)) {
+        cxmutstr mutvalue = cx_strdup_a(map->allocator, value);
+        if(!mutvalue.ptr) {
+            return 1;
+        }
+        if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
+            cxFree(map->allocator, mutvalue.ptr);
+            return 1;
+        }
+    }
+    if (parser->error) {
+        return parser->error;
+    } else {
+        return 0;
+    }
+}
+
+// buffer size is documented - change doc, when you change bufsize!
+#define UCX_PROPLOAD_BUFSIZE  1024
+int ucx_properties_load(CxMap *map, FILE *file) {
+    UcxProperties *parser = ucx_properties_new();
+    if(!(parser && map && file)) {
+        return 1;
+    }
+    
+    int error = 0;
+    size_t r;
+    char buf[UCX_PROPLOAD_BUFSIZE];
+    while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
+        ucx_properties_fill(parser, buf, r);
+        error = ucx_properties2map(parser, map);
+        if (error) {
+            break;
+        }
+    }
+    ucx_properties_free(parser);
+    return error;
+}
+
+int ucx_properties_store(CxMap *map, FILE *file) {
+    CxIterator iter = cxMapIterator(map);
+    cxstring value;
+    size_t written;
+
+    cx_foreach(CxMapEntry *, v, iter) {
+        value = cx_str(v->value);
+
+        written = 0;
+        written += fwrite(v->key->data, 1, v->key->len, file);
+        written += fwrite(" = ", 1, 3, file);
+        written += fwrite(value.ptr, 1, value.length, file);
+        written += fwrite("\n", 1, 1, file);
+
+        if (written != v->key->len + value.length + 4) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,223 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, 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.
+ */
+/**
+ * @file properties.h
+ * 
+ * Load / store utilities for properties files.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define	UCX_PROPERTIES_H
+
+#include <cx/hash_map.h>
+#include <cx/string.h>
+
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying 
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+    /**
+     * Input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    char   *buffer;
+    
+    /**
+     * Length of the input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    size_t buflen;
+    
+    /**
+     * Current buffer position (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t pos;
+    
+    /**
+     * Internal temporary buffer (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    char   *tmp;
+    
+    /**
+     * Internal temporary buffer length (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmplen;
+    
+    /**
+     * Internal temporary buffer capacity (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmpcap;
+    
+    /**
+     * Parser error code.
+     * This is always 0 on success and a nonzero value on syntax errors.
+     * The value is set by ucx_properties_next().
+     */
+    int    error;
+    
+    /**
+     * The delimiter that shall be used.
+     * This is '=' by default.
+     */
+    char   delimiter;
+    
+    /**
+     * The first comment character.
+     * This is '#' by default.
+     */
+    char   comment1;
+    
+    /**
+     * The second comment character.
+     * This is not set by default.
+     */
+    char   comment2;
+    
+    /**
+     * The third comment character.
+     * This is not set by default.
+     */
+    char   comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ * 
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ * 
+ * 
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ * 
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ * 
+ * <b>Attention:</b> the cxstring.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ * 
+ * @param prop the UcxProperties object
+ * @param name a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ * 
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
+ * 
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, CxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ * 
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ * 
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ * 
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(CxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ * 
+ * The key/value-pairs are written by using the following format:
+ * 
+ * <code>[key] = [value]\\n</code>
+ * 
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(CxMap *map, FILE *file);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_PROPERTIES_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+$(GTK_OBJPRE)%.o: gtk/%.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/gtk/button.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,254 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "button.h"
+#include "container.h"
+#include <cx/allocator.h>
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+    GtkWidget *button = gtk_button_new_with_label(label);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = data;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, button, FALSE);
+    
+    return button;
+}
+
+
+void ui_button_clicked(GtkWidget *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = event->value;
+    event->callback(&e, event->userdata);
+}
+
+int64_t ui_toggle_button_get(UiInteger *integer) {
+    GtkToggleButton *button = integer->obj;
+    integer->value = (int)gtk_toggle_button_get_active(button);
+    return integer->value;
+}
+
+void ui_toggle_button_set(UiInteger *integer, int64_t value) {
+    GtkToggleButton *button = integer->obj;
+    integer->value = value;
+    gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = event->var->value;
+    e.intval = gtk_toggle_tool_button_get_active(widget);
+    
+    UiInteger *i = event->var->value;
+    ui_notify_evt(i->observers, &e);
+}
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var) {
+    GtkWidget *button = gtk_check_button_new_with_label(label);
+    
+    // bind value
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = GTK_TOGGLE_BUTTON(button);
+        value->get = ui_toggle_button_get;
+        value->set = ui_toggle_button_set;
+        gtk_toggle_button_set_active(value->obj, value->value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = NULL;
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_toggled_obs),
+                event);
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, button, FALSE);
+    
+    return button;
+}
+
+UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) {
+    UiVar *var = NULL;
+    if(value) {
+        var = malloc(sizeof(UiVar));
+        var->value = value;
+        var->type = UI_VAR_SPECIAL;
+    }
+    return ui_checkbox_var(obj, label, var);
+}
+
+UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_checkbox_var(obj, label, var);
+}
+
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) {
+    GSList *rg = NULL;
+    UiInteger *rgroup;
+    
+    if(var) {
+        rgroup = var->value;
+        rg = rgroup->obj;
+    }
+    
+    GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label);
+    rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
+    
+    if(rgroup) {
+        rgroup->obj = rg;
+        rgroup->get = ui_radiobutton_get;
+        rgroup->set = ui_radiobutton_set;
+        
+        ui_radiobutton_set(rgroup, rgroup->value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = NULL;
+        
+        g_signal_connect(
+                rbutton,
+                "clicked",
+                G_CALLBACK(ui_radio_obs),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, rbutton, FALSE);
+    
+    return rbutton;
+}
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+    UiVar *var = NULL;
+    if(rgroup) {
+        var = malloc(sizeof(UiVar));
+        var->value = rgroup;
+        var->type = UI_VAR_SPECIAL;
+    }
+    return ui_radiobutton_var(obj, label, var);
+}
+
+UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_radiobutton_var(obj, label, var);
+}
+
+void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+    UiInteger *i = event->var->value;
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = i->get(i);
+    
+    ui_notify_evt(i->observers, &e);
+}
+
+int64_t ui_radiobutton_get(UiInteger *value) {
+    int selection = 0;
+    GSList *ls = value->obj;
+    int i = 0;
+    guint len = g_slist_length(ls);
+    while(ls) {
+        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ls->data))) {
+            selection = len - i - 1;
+            break;
+        }
+        ls = ls->next;
+        i++;
+    }
+    
+    value->value = selection;
+    return selection;
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+    GSList *ls = value->obj;
+    int s = g_slist_length(ls) - 1 - i;
+    int j = 0;
+    while(ls) {
+        if(j == s) {
+            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ls->data), TRUE);
+            break;
+        }
+        ls = ls->next;
+        j++;
+    }
+    
+    value->value = i;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,60 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define	BUTTON_H
+
+#include "../ui/toolkit.h"
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+// event wrapper
+void ui_button_clicked(GtkWidget *widget, UiEventData *event);
+
+
+void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
+
+void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* BUTTON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/container.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,628 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "container.h"
+#include "toolkit.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+void ui_container_begin_close(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(ct->close) {
+        ui_end(obj);
+        return 0;
+    }
+    return 1;
+}
+
+GtkWidget* ui_gtk_vbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+#else
+    return gtk_vbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_gtk_hbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+#else
+    return gtk_hbox_new(FALSE, spacing);
+#endif
+}
+
+/* -------------------- Frame Container (deprecated) -------------------- */
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = frame;
+    ct->add = ui_frame_container_add;
+    return ct;
+}
+
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Box Container -------------------- */
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
+    UiBoxContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiBoxContainer));
+    ct->container.widget = box;
+    ct->container.add = ui_box_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiBoxContainer *bc = (UiBoxContainer*)ct;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
+    
+    if(bc->has_fill && fill) {
+        fprintf(stderr, "UiError: container has 2 filled widgets");
+        fill = FALSE;
+    }
+    if(fill) {
+        bc->has_fill = TRUE;
+    }
+    
+    UiBool expand = fill;
+    gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
+    UiGridContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiGridContainer));
+    ct->container.widget = grid;
+    ct->container.add = ui_grid_container_add;
+#ifdef UI_GTK2
+    ct->width = 0;
+    ct->height = 1;
+#endif
+    return (UiContainer*)ct;
+}
+
+#ifdef UI_GTK3
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    
+    if(hexpand) {
+        gtk_widget_set_hexpand(widget, TRUE);
+    }
+    if(vexpand) {
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+    int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1;
+    
+    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1);
+    grid->x += gwidth;
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+#ifdef UI_GTK2
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    
+    gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
+    grid->x++;
+    int nw = grid->x > grid->width ? grid->x : grid->width;
+    if(grid->x > grid->width || grid->y > grid->height) {
+        grid->width = nw;
+        grid->height = grid->y + 1;
+        gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height);
+    }
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = scrolledwindow;
+    ct->add = ui_scrolledwindow_container_add;
+    return ct;
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: check if the widget implements GtkScrollable
+#ifdef UI_GTK3
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+#else
+    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget);
+#endif
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
+    UiTabViewContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiTabViewContainer));
+    ct->container.widget = tabview;
+    ct->container.add = ui_tabview_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_notebook_append_page(
+            GTK_NOTEBOOK(ct->widget),
+            widget,
+            gtk_label_new(ct->layout.label));
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_vbox_sp(obj, 0, 0);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_hbox_sp(obj, 0, 0);
+}
+
+static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
+    GtkWidget *ret = box;
+#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(box, margin);
+    gtk_widget_set_margin_end(box, margin);
+#else
+    gtk_widget_set_margin_left(box, margin);
+    gtk_widget_set_margin_right(box, margin);
+#endif
+    gtk_widget_set_margin_top(box, margin);
+    gtk_widget_set_margin_bottom(box, margin);
+#elif defined(UI_GTK2)
+    GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+    gtk_container_add(GTK_CONTAINER(a), box);
+    ret = a;
+#endif
+    return ret;
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(spacing);   
+    GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, vbox);
+    newobj->container = ui_box_container(obj, vbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *hbox = ui_gtk_hbox_new(spacing);
+    GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, hbox);
+    newobj->container = ui_box_container(obj, hbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *widget;
+    
+#ifdef UI_GTK3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(grid, margin);
+    gtk_widget_set_margin_end(grid, margin);
+#else
+    gtk_widget_set_margin_left(grid, margin);
+    gtk_widget_set_margin_right(grid, margin);
+#endif
+    gtk_widget_set_margin_top(grid, margin);
+    gtk_widget_set_margin_bottom(grid, margin);
+    
+    widget = grid;
+#elif defined(UI_GTK2)
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    
+    gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+    
+    if(margin > 0) {
+        GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+        gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+        gtk_container_add(GTK_CONTAINER(a), grid);
+        widget = a;
+    } else {
+        widget = grid;
+    }
+#endif
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, grid);
+    newobj->container = ui_grid_container(obj, grid);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
+    ct->add(ct, sw, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, sw);
+    newobj->container = ui_scrolledwindow_container(obj, sw);
+    uic_obj_add(obj, newobj);
+    
+    return sw;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiObject *tabviewobj = uic_object_new(obj, tabview);
+    tabviewobj->container = ui_tabview_container(obj, tabview);
+    uic_obj_add(obj, tabviewobj);
+    
+    return tabview;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.label = title;
+    ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+/* -------------------- Splitpane -------------------- */
+
+static GtkWidget* create_paned(UiOrientation orientation) {
+#ifdef UI_GTK3
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+    }
+#else
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_hpaned_new();
+        case UI_VERTICAL: return gtk_vpaned_new();
+    }
+#endif
+    return NULL;
+}
+
+UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) {
+    GtkWidget *paned = create_paned(orientation);
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    if(max <= 0) max = INT_MAX;
+    
+    UiPanedContainer *pctn = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiPanedContainer));
+    pctn->container.widget = paned;
+    pctn->container.add = ui_paned_container_add;
+    pctn->current_pane = paned;
+    pctn->orientation = orientation;
+    pctn->max = max;
+    pctn->cur = 0;
+    
+    UiObject *pobj = uic_object_new(obj, paned);
+    pobj->container = (UiContainer*)pctn;
+    
+    uic_obj_add(obj, pobj);
+    
+    return paned;
+}
+
+UIWIDGET ui_hsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_HORIZONTAL);
+}
+
+UIWIDGET ui_vsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_VERTICAL);
+}
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiPanedContainer *pctn = (UiPanedContainer*)ct;
+    
+    gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE;
+    int width = ct->layout.width;
+    ui_reset_layout(ct->layout);
+    
+    if(pctn->cur == 0) {
+        gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize);
+    } else if(pctn->cur < pctn->max-1) {
+        GtkWidget *nextPane = create_paned(pctn->orientation);
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE);
+        gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize);
+        pctn->current_pane = nextPane;
+    } else if(pctn->cur == pctn->max-1) {
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize);
+        width = 0; // disable potential call of gtk_paned_set_position
+    } else {
+        fprintf(stderr, "Splitpane max reached: %d\n", pctn->max);
+        return;
+    }
+    
+    if(width > 0) {
+        gtk_paned_set_position(GTK_PANED(pctn->current_pane), width);
+    }
+    
+    pctn->cur++;
+}
+
+
+/* -------------------- Sidebar (deprecated) -------------------- */
+UIWIDGET ui_sidebar(UiObject *obj) {
+#ifdef UI_GTK3
+    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+    GtkWidget *paned = gtk_hpaned_new();
+#endif
+    gtk_paned_set_position(GTK_PANED(paned), 200);
+    
+    GtkWidget *sidebar = ui_gtk_vbox_new(0);
+    gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE);
+    
+    UiObject *left = uic_object_new(obj, sidebar);
+    UiContainer *ct1 = ui_box_container(obj, sidebar);
+    left->container = ct1;
+    
+    UiObject *right = uic_object_new(obj, sidebar);
+    UiContainer *ct2 = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct2->widget = paned;
+    ct2->add = ui_split_container_add2;
+    right->container = ct2;
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    uic_obj_add(obj, right);
+    uic_obj_add(obj, left);
+    
+    return sidebar;
+}
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: remove
+    gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Document Tabview -------------------- */
+static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) {
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    UiObject *tab = g_object_get_qdata(G_OBJECT(page), q);
+    if(!tab) {
+        return;
+    }
+    
+    //printf("page_change: %d\n", page_num);
+    UiContext *ctx = tab->ctx;
+    uic_context_detach_all(ctx->parent); // TODO: fix?
+    ctx->parent->attach_document(ctx->parent, ctx->document);
+}
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    
+    g_signal_connect(
+                tabview,
+                "switch-page",
+                G_CALLBACK(page_change),
+                NULL);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane));
+    tabbedpane->ctx = uic_current_obj(obj)->ctx;
+    tabbedpane->widget = tabview;
+    tabbedpane->document = NULL;
+    
+    return tabbedpane;
+}
+
+UiObject* ui_document_tab(UiTabbedPane *view) {
+    GtkWidget *frame = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+    // TODO: label
+    gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL);
+    
+    UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject));
+    tab->widget = NULL; // initialization for uic_context()
+    tab->ctx = uic_context(tab, view->ctx->allocator);
+    tab->ctx->parent = view->ctx;
+    tab->ctx->attach_document = uic_context_attach_document;
+    tab->ctx->detach_document2 = uic_context_detach_document2;
+    tab->widget = frame;
+    tab->window = view->ctx->obj->window;
+    tab->container = ui_frame_container(tab, frame);
+    tab->next = NULL;
+    
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    g_object_set_qdata(G_OBJECT(frame), q, tab);
+    
+    return tab;
+}
+
+void ui_tab_set_document(UiContext *ctx, void *document) {
+    // TODO: remove?
+    if(ctx->parent->document) {
+        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+    }
+    //uic_context_set_document(ctx, document);
+    //uic_context_set_document(ctx->parent, document);
+    //ctx->parent->document = document;
+}
+
+void ui_tab_detach_document(UiContext *ctx) {
+    // TODO: remove?
+    //uic_context_detach_document(ctx->parent);
+}
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+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_width(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.width = width;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.newline = TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define	CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <string.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)
+    
+typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool);
+
+typedef struct UiDocumentView UiDocumentView;
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiLayoutBool fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    int          width;
+    int          gridwidth;
+};
+
+struct UiContainer {
+    GtkWidget *widget;
+    GtkMenu *menu;
+    GtkWidget *current;
+    
+    void (*add)(UiContainer*, GtkWidget*, UiBool);
+    UiLayout layout;
+    
+    int close;
+};
+
+typedef struct UiBoxContainer {
+    UiContainer container;
+    UiBool has_fill;
+} UiBoxContainer;
+
+typedef struct UiGridContainer {
+    UiContainer container;
+    int x;
+    int y;
+#ifdef UI_GTK2
+    int width;
+    int height;
+#endif
+} UiGridContainer;
+
+typedef struct UiPanedContainer {
+    UiContainer container;
+    GtkWidget *current_pane;
+    int orientation;
+    int max;
+    int cur;
+} UiPanedContainer;
+
+typedef struct UiTabViewContainer {
+    UiContainer container;
+} UiTabViewContainer;
+
+GtkWidget* ui_gtk_vbox_new(int spacing);
+GtkWidget* ui_gtk_hbox_new(int spacing);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow);
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview);
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill);
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+
+UiObject* ui_add_document_tab(UiDocumentView *view);
+void ui_tab_set_document(UiContext *ctx, void *document);
+void ui_tab_detach_document(UiContext *ctx);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* CONTAINER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/display.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,127 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "display.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+    gtk_label_set_xalign(GTK_LABEL(widget), xalign);
+    gtk_label_set_yalign(GTK_LABEL(widget), yalign);
+#else
+    gtk_misc_set_alignment(GTK_MISC(widget), xalign, yalign);
+#endif
+}
+
+UIWIDGET ui_label(UiObject *obj, char *label) { 
+    GtkWidget *widget = gtk_label_new(label);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, FALSE);
+    
+    return widget;
+}
+
+UIWIDGET ui_llabel(UiObject *obj, char *label) {
+    UIWIDGET widget = ui_label(obj, label);
+    set_alignment(widget, 0, .5);
+    return widget;
+}
+
+UIWIDGET ui_rlabel(UiObject *obj, char *label) {
+    UIWIDGET widget = ui_label(obj, label);
+    //gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_RIGHT);
+    
+    set_alignment(widget, 1, .5);
+    return widget;
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+    GtkWidget *widget = gtk_label_new("");
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, TRUE);
+    
+    return widget;
+}
+
+UIWIDGET ui_separator(UiObject *obj) {
+#if UI_GTK3
+    GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+    GtkWidget *widget = gtk_hseparator_new();
+#endif
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, FALSE);
+    
+    return widget;
+}
+
+/* ------------------------- progress bar ------------------------- */
+
+UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return ui_progressbar_var(obj, var);
+}
+
+UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
+    return ui_progressbar_var(obj, var);
+}
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var) {
+    GtkWidget *progressbar = gtk_progress_bar_new();
+    if(var && var->value) {
+        UiDouble *value = var->value;
+        value->get = ui_progressbar_get;
+        value->set = ui_progressbar_set;
+        value->obj = progressbar;
+        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 0.5);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, progressbar, FALSE);
+    
+    return progressbar;
+}
+
+double ui_progressbar_get(UiDouble *d) {
+    d->value = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+    return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value);
+    d->value = value;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/display.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define	LABEL_H
+
+#include "../ui/toolkit.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* LABEL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/dnd.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,101 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dnd.h"
+#include <cx/buffer.h>
+
+#ifdef UI_GTK2LEGACY
+static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
+    CxBuffer *buf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    char *uri;
+    int i = 0;
+    while((uri = uris[i]) != NULL) {
+        cxBufferPutString(buf, uri);
+        cxBufferPutString(buf, "\r\n");
+    }
+    GdkAtom type = gdk_atom_intern("text/uri-list", FALSE);
+    gtk_selection_data_set(selection_data, type, 8, (guchar*)buf->space, buf->pos);
+    cxBufferFree(buf);
+    return TRUE;
+}
+static char** selection_data_get_uris(GtkSelectionData *selection_data) {
+    // TODO: implement
+    return NULL;
+}
+#define gtk_selection_data_set_uris selection_data_set_uris
+#define gtk_selection_data_get_uris selection_data_get_uris
+#endif
+
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+    // TODO: handle error?
+    gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+    char **uriarray = calloc(nelm+1, sizeof(char*));
+    for(int i=0;i<nelm;i++) {
+        uriarray[i] = uris[i];
+    }
+    uriarray[nelm] = NULL;
+    gtk_selection_data_set_uris(sel->data, uriarray);
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+    guchar *text = gtk_selection_data_get_text(sel->data);
+    if(text) {
+        char *textcp = strdup((char*)text);
+        g_free(text);
+        return textcp;
+    }
+    return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+    gchar **uris = gtk_selection_data_get_uris(sel->data);
+    if(uris) {
+        size_t al = 32;
+        char **array = malloc(al * sizeof(char*));
+        size_t i = 0;
+        while(uris[i] != NULL) {
+            if(i >= al) {
+                al *= 2;
+                array = realloc(array, al * sizeof(char*));
+            }
+            array[i] = strdup((char*)uris[i]);
+            i++;
+        }
+        *nelm = i;
+        g_strfreev(uris);
+        return array;
+    }
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/dnd.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/draw_cairo.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,132 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_cairo.h"
+
+#ifdef UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+    UiCairoGraphics g;
+    g.g.width = gtk_widget_get_allocated_width(w);
+    g.g.height = gtk_widget_get_allocated_height(w);
+    g.widget = w;
+    g.cr = cr;
+    
+    UiDrawEvent *event = data;
+    UiEvent ev;
+    ev.obj = event->obj;
+    ev.window = event->obj->window;
+    ev.document = event->obj->ctx->document;
+    
+    event->callback(&ev, &g.g, event->userdata);
+    
+    return FALSE;
+}
+#else
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+    UiCairoGraphics g;
+    g.g.width = w->allocation.width;
+    g.g.height = w->allocation.height;
+    g.widget = w;
+    g.cr = gdk_cairo_create(w->window);
+    
+    UiDrawEvent *event = data;
+    UiEvent ev;
+    ev.obj = event->obj;
+    ev.window = event->obj->window;
+    ev.document = event->obj->ctx->document;
+    
+    event->callback(&ev, &g.g, event->userdata);
+    
+    return FALSE;
+}
+#endif
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+#ifdef UI_GTK3
+    g_signal_connect(G_OBJECT(widget),
+            "draw",
+            G_CALLBACK(ui_drawingarea_expose),
+            event);
+#else
+    g_signal_connect(G_OBJECT(widget),
+            "expose_event",
+            G_CALLBACK(ui_canvas_expose),
+            event);
+#endif
+}
+
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    //return gtk_widget_get_pango_context(gr->widget);
+    return pango_cairo_create_context(gr->cr);
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    double dred = (double)red / (double)255;
+    double dgreen = (double)green / (double)255;
+    double dblue = (double)blue / (double)255;
+    cairo_set_source_rgb(gr->cr, dred, dgreen, dblue);
+}
+
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    cairo_set_line_width(gr->cr, 1);
+    cairo_move_to(gr->cr, (double)x1 + 0.5, (double)y1 + 0.5);
+    cairo_line_to(gr->cr, (double)x2 + 0.5, (double)y2 + 0.5);
+    cairo_stroke(gr->cr);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    cairo_set_line_width(gr->cr, 1);
+    cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h);
+    if(fill) {
+        cairo_fill(gr->cr);
+    } else {
+        cairo_stroke(gr->cr);
+    }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g; 
+    cairo_move_to(gr->cr, x, y);
+    pango_cairo_show_layout(gr->cr, text->layout);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/draw_cairo.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_CAIRO_H
+#define	DRAW_CAIRO_H
+
+#include "graphics.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiCairoGraphics {
+    UiGraphics g;
+    GtkWidget  *widget;
+    cairo_t    *cr;
+} UiCairoGraphics;
+
+// ui_canvas_expose
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DRAW_CAIRO_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/draw_gdk.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,93 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_gdk.h"
+
+
+gboolean ui_drawingarea_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+    UiGdkGraphics g;
+    g.g.width = w->allocation.width;
+    g.g.height = w->allocation.height;
+    g.widget = w;
+    g.gc = gdk_gc_new(w->window);
+    
+    UiDrawEvent *event = data;
+    UiEvent ev;
+    ev.obj = event->obj;
+    ev.window = event->obj->window;
+    ev.document = event->obj->ctx->document;
+    
+    event->callback(&ev, &g.g, event->userdata);
+    
+    return FALSE;
+}
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+    g_signal_connect(G_OBJECT(widget),
+            "expose_event",
+            G_CALLBACK(ui_drawingarea_expose),
+            event);
+}
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+    UiGdkGraphics *gr = (UiGdkGraphics*)g;
+    return gtk_widget_get_pango_context(gr->widget);
+}
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+    UiGdkGraphics *gr = (UiGdkGraphics*)g;
+    GdkColor color;
+    color.red = red * 257;
+    color.green = green * 257;
+    color.blue = blue * 257;
+    gdk_gc_set_rgb_fg_color(gr->gc, &color);
+    //gdk_gc_set_rgb_bg_color(g->gc, &color);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+    UiGdkGraphics *gr = (UiGdkGraphics*)g; 
+    gdk_draw_line(gr->widget->window, gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    UiGdkGraphics *gr = (UiGdkGraphics*)g; 
+    gdk_draw_rectangle(gr->widget->window, gr->gc, fill, x, y, w, h);
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+    UiGdkGraphics *gr = (UiGdkGraphics*)g; 
+    gdk_draw_layout(gr->widget->window, gr->gc, x, y, text->layout);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/draw_gdk.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_GDK_H
+#define	DRAW_GDK_H
+
+#include "graphics.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGdkGraphics {
+    UiGraphics g;
+    GtkWidget  *widget;
+    GdkGC      *gc;
+} UiGdkGraphics;
+
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DRAW_GDK_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/entry.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,213 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+#include "entry.h"
+
+
+UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = i;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
+}
+
+UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = d;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
+}
+
+UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = r;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
+}
+
+UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
+}
+
+UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
+    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
+}
+
+UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_RANGE);
+    UiRange *r = var->value;
+    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
+}
+
+UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type) {
+    double min = 0;
+    double max = 1000;
+    if(type == UI_VAR_RANGE) {
+        UiRange *r = var->value;
+        min = r->min;
+        max = r->max;
+    }
+    if(step == 0) {
+        step = 1;
+    }
+#ifdef UI_GTK2LEGACY
+    if(min == max) {
+        max = min + 1;
+    }
+#endif
+    GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step);
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
+    if(var) {
+        double value = 0;
+        UiObserver **obs = NULL;
+        switch(type) {
+            default: break;
+            case UI_VAR_INTEGER: {
+                UiInteger *i = var->value;
+                i->get = ui_spinbutton_getint;
+                i->set = ui_spinbutton_setint;
+                i->obj = spin;
+                value = (double)i->value;
+                obs = &i->observers;
+                break;
+            }
+            case UI_VAR_DOUBLE: {
+                UiDouble *d = var->value;
+                d->get = ui_spinbutton_getdouble;
+                d->set = ui_spinbutton_setdouble;
+                d->obj = spin;
+                value = d->value;
+                obs = &d->observers;
+                break;
+            }
+            case UI_VAR_RANGE: {
+                UiRange *r = var->value;
+                r->get = ui_spinbutton_getrangeval;
+                r->set = ui_spinbutton_setrangeval;
+                r->setrange = ui_spinbutton_setrange;
+                r->setextent = ui_spinbutton_setextent;
+                r->obj = spin;
+                value = r->value;
+                obs = &r->observers;
+                break;
+            }
+        }
+        gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = obs;
+        
+        g_signal_connect(
+                spin,
+                "value-changed",
+                G_CALLBACK(ui_spinner_changed),
+                event);
+        g_signal_connect(
+                spin,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, spin, FALSE);
+    
+    return spin;
+}
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max) {
+    gtk_spin_button_set_range(GTK_SPIN_BUTTON(spinner), min, max);
+}
+
+void ui_spinner_setdigits(UIWIDGET spinner, int digits) {
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), digits);
+}
+
+
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = event->var->value;
+    e.intval = 0;
+    
+    UiObserver *observer = *event->observers;
+    ui_notify_evt(observer, &e);
+}
+
+
+int64_t ui_spinbutton_getint(UiInteger *i) {
+    i->value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(i->obj));
+    return i->value;
+}
+
+void ui_spinbutton_setint(UiInteger *i, int64_t val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->obj), (double)val);
+    i->value = val;
+}
+
+double ui_spinbutton_getdouble(UiDouble *d) {
+    d->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->obj));
+    return d->value;
+}
+
+void ui_spinbutton_setdouble(UiDouble *d, double val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->obj), val);
+    d->value = val;
+}
+
+double ui_spinbutton_getrangeval(UiRange *r) {
+    r->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(r->obj));
+    return r->value;
+}
+
+void ui_spinbutton_setrangeval(UiRange *r, double val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->obj), val);
+    r->value = val;
+}
+void ui_spinbutton_setrange(UiRange *r, double min, double max) {
+    gtk_spin_button_set_range(GTK_SPIN_BUTTON(r->obj), min, max);
+    r->min = min;
+    r->max = max;
+}
+
+void ui_spinbutton_setextent(UiRange *r, double extent) {
+    gtk_spin_button_set_increments(GTK_SPIN_BUTTON(r->obj), extent, extent*10);
+    r->extent = extent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/entry.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,45 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/* 
+ * File:   entry.h
+ * Author: olaf
+ *
+ * Created on 11. November 2017, 13:38
+ */
+
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include "toolkit.h"
+#include "../ui/entry.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type);
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event);
+
+int64_t ui_spinbutton_getint(UiInteger *i);
+void ui_spinbutton_setint(UiInteger *i, int64_t val);
+
+double ui_spinbutton_getdouble(UiDouble *d);
+void ui_spinbutton_setdouble(UiDouble *d, double val);
+
+double ui_spinbutton_getrangeval(UiRange *r);
+void ui_spinbutton_setrangeval(UiRange *r, double val);
+void ui_spinbutton_setrange(UiRange *r, double min, double max);
+void ui_spinbutton_setextent(UiRange *r, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENTRY_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/graphics.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,157 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "graphics.h"
+#include "container.h"
+#include "../common/object.h"
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+    GtkWidget *widget = gtk_drawing_area_new();
+    
+    if(f) {
+        UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = userdata;
+        ui_connect_draw_handler(widget, event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, TRUE);
+    
+    return widget;
+}
+
+
+static gboolean widget_button_pressed(
+        GtkWidget *widget,
+        GdkEvent *event,
+        gpointer userdata)
+{
+    UiEventData *eventdata = userdata;
+    
+    UiMouseEvent me;
+    me.x = (int)event->button.x;
+    me.y = (int)event->button.y;
+    
+    int exec = 0;
+    if(event->button.type == GDK_BUTTON_PRESS) {
+        exec = 1;
+        me.type = UI_PRESS;
+    } else if(event->button.type == GDK_2BUTTON_PRESS) {
+        exec = 1;
+        me.type = UI_PRESS2;
+    }
+    
+    if(exec) {
+        UiEvent e;
+        e.obj = eventdata->obj;
+        e.window = eventdata->obj->window;
+        e.document = eventdata->obj->ctx->document;
+        e.eventdata = &me;
+        e.intval = 0;
+        eventdata->callback(&e, eventdata->userdata);
+    }
+    return TRUE;
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+#ifdef UI_GTK3
+        *width = gtk_widget_get_allocated_width(drawingarea);
+        *height = gtk_widget_get_allocated_height(drawingarea);
+#else
+        *width = drawingarea->allocation.width;
+        *height = drawingarea->allocation.height;
+#endif
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+    gtk_widget_queue_draw(drawingarea);
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+    gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK);
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = u;
+        
+        g_signal_connect(G_OBJECT(widget),
+                "button-press-event",
+                G_CALLBACK(widget_button_pressed),
+                event);
+    } else {
+         // TODO: warning
+    }
+}
+
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g) {
+    UiTextLayout *layout = malloc(sizeof(UiTextLayout));
+    PangoContext *pc = ui_get_pango_context(g);
+    layout->layout = pango_layout_new(pc);
+    return layout;
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+    pango_layout_set_text(layout->layout, str, -1);
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+    pango_layout_set_text(layout->layout, str, len);
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+    PangoFontDescription *fontDesc;
+    fontDesc = pango_font_description_from_string(font);
+    pango_font_description_set_size(fontDesc, size * PANGO_SCALE);
+    pango_layout_set_font_description(layout->layout, fontDesc);
+    pango_font_description_free(fontDesc);
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+    pango_layout_get_size(layout->layout, width, height);
+    *width = *width / PANGO_SCALE;
+    *height = *height / PANGO_SCALE;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+    pango_layout_set_width(layout->layout, width * PANGO_SCALE);
+    pango_layout_set_ellipsize(layout->layout, PANGO_ELLIPSIZE_END);
+    //pango_layout_set_wrap(layout->layout, PANGO_WRAP_WORD_CHAR);
+}
+
+void ui_text_free(UiTextLayout *text) {
+    g_object_unref(text->layout);
+    free(text);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,58 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAWINGAREA_H
+#define	DRAWINGAREA_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiDrawEvent {
+    ui_drawfunc callback;
+    UiObject    *obj;
+    void        *userdata;
+} UiDrawEvent;
+
+struct UiTextLayout {
+    PangoLayout *layout;
+};
+
+// implemented in draw_*.h
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event);
+PangoContext *ui_get_pango_context(UiGraphics *g);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* DRAWINGAREA_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/image.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,136 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cx/map.h>
+
+#include "toolkit.h"
+#include "image.h"
+#include "../common/properties.h"
+
+static CxMap *image_map;
+
+static GtkIconTheme *icon_theme;
+
+void ui_image_init(void) {
+    image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    
+    icon_theme = gtk_icon_theme_get_default();
+}
+
+// **** deprecated functions ****
+
+GdkPixbuf* ui_get_image(const char *name) {
+    UiImage *img = cxMapGet(image_map, name);
+    if(img) {
+        return img->pixbuf;
+    } else {
+        //ui_add_image(name, name);
+        //return ucx_map_cstr_get(image_map, name);
+        // TODO
+        return NULL;
+    }
+}
+
+// **** new functions ****
+
+static UiIcon* get_icon(const char *name, int size, int scale) {
+#ifdef UI_SUPPORTS_SCALE
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
+#else
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+#endif
+    if(info) {
+        UiIcon *icon = malloc(sizeof(UiIcon));
+        icon->info = info;
+        return icon;
+    }
+    return NULL;
+}
+
+UiIcon* ui_icon(const char *name, int size) {
+    return get_icon(name, size, ui_get_scalefactor());
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+    return get_icon(name, size, 1);
+}
+
+void ui_free_icon(UiIcon *icon) {
+    g_object_unref(icon->info);
+    free(icon);
+}
+
+UiImage* ui_icon_image(UiIcon *icon) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    if(pixbuf) {
+        UiImage *img = malloc(sizeof(UiImage));
+        img->pixbuf = pixbuf;
+        return img;
+    }
+    return NULL;
+}
+
+UiImage* ui_image(const char *filename) {
+    return ui_named_image(filename, NULL);
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+    char *path =  uic_get_image_path(filename);
+    if(!path) {
+        fprintf(stderr, "UiError: pixmaps directory not set\n");
+        return NULL;
+    }
+    UiImage *img = ui_load_image_from_path(path, name);
+    free(path);
+    return img;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", path);
+        return NULL;
+    }
+    
+    UiImage *img = malloc(sizeof(UiImage));
+    img->pixbuf = pixbuf;
+    if(name) {
+        cxMapPut(image_map, name, img);
+    }
+    return img;
+}
+
+void ui_free_image(UiImage *img) {
+    g_object_unref(img->pixbuf);
+    free(img);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/image.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef IMAGE_H
+#define	IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+    
+struct UiIcon {
+    GtkIconInfo *info;
+};
+
+struct UiImage {
+    GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* IMAGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/menu.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,474 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include "menu.h"
+#include "toolkit.h"
+#include "../common/context.h"
+#include "../common/menu.h"
+#include "../ui/properties.h"
+#include "../ui/window.h"
+#include "container.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_SUBMENU         */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_STOCK_ITEM      */ add_menuitem_st_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_CHECK_ITEM_NV   */ add_checkitemnv_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_ITEM_LIST_NV    */ NULL, // TODO
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+// private menu functions
+GtkWidget *ui_create_menubar(UiObject *obj) {
+    UiMenu *menus_begin = uic_get_menu_list();
+    if(menus_begin == NULL) {
+        return NULL;
+    }
+    
+    GtkWidget *mb = gtk_menu_bar_new();
+    
+    UiMenu *ls = menus_begin;
+    while(ls) {
+        UiMenu *menu = ls;
+        add_menu_widget(mb, 0, &menu->item, obj);
+        
+        ls = (UiMenu*)ls->item.next;
+    }
+    
+    return mb;
+}
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *menu = (UiMenu*)item;
+    
+    GtkWidget *menu_widget = gtk_menu_new();
+    GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
+    
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](menu_widget, index, it, obj);
+        
+        it = it->next;
+        index++;
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item);
+}
+
+void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *i = (UiMenuItem*)item;
+    
+    //GtkWidget *widget = gtk_menu_item_new_with_label(i->title);
+    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(i->label);
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+    
+    if(i->groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
+    }
+}
+
+void add_menuitem_st_widget(
+        GtkWidget *parent,
+        int index,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiStMenuItem *i = (UiStMenuItem*)item;
+    
+    GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group);
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+    
+    if(i->groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
+    }
+}
+
+void add_menuseparator_widget(
+        GtkWidget *parent,
+        int index,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    gtk_menu_shell_append(
+            GTK_MENU_SHELL(parent),
+            gtk_separator_menu_item_new());
+}
+
+void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiCheckItem *ci = (UiCheckItem*)item;
+    GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+    gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+    
+    if(ci->callback) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = ci->userdata;
+        event->callback = ci->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(ui_menu_event_toggled),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+}
+
+void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiCheckItemNV *ci = (UiCheckItemNV*)item;
+    GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+    gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+    
+    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = widget;
+        value->get = ui_checkitem_get;
+        value->set = ui_checkitem_set;
+        value = 0;
+    } else {
+        // TODO: error
+    }
+}
+
+void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item;
+    const CxAllocator *a = obj->ctx->allocator;
+    
+    UiActiveMenuItemList *ls = cxMalloc(
+            a,
+            sizeof(UiActiveMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = GTK_MENU_SHELL(p);
+    ls->index = index;
+    ls->oldcount = 0;
+    ls->list = il->list;
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    ls->list->observers = ui_add_observer(
+            ls->list->observers,
+            (ui_callback)ui_update_menuitem_list,
+            ls);
+    
+    ui_update_menuitem_list(NULL, ls);
+}
+
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {    
+    // remove old items
+    if(list->oldcount > 0) {
+        int i = 0;
+        GList *mi = gtk_container_get_children(GTK_CONTAINER(list->menu));
+        while(mi) {
+            if(i >= list->index && i < list->index + list->oldcount) {
+                //gtk_container_remove(GTK_CONTAINER(list->menu), mi->data);
+                gtk_widget_destroy(mi->data);
+            }
+            mi = mi->next;
+            i++;
+        }
+    }
+    
+    char *str = ui_list_first(list->list);
+    if(str) {
+        GtkWidget *widget = gtk_separator_menu_item_new();
+        gtk_menu_shell_insert(list->menu, widget, list->index);
+        gtk_widget_show(widget);
+    }
+    int i = 1;
+    while(str) {
+        GtkWidget *widget = gtk_menu_item_new_with_label(str);
+        gtk_menu_shell_insert(list->menu, widget, list->index + i);
+        gtk_widget_show(widget);
+        
+        if(list->callback) {
+            // TODO: use mempool
+            UiEventData *event = malloc(sizeof(UiEventData));
+            event->obj = list->object;
+            event->userdata = list->userdata;
+            event->callback = list->callback;
+            event->value = i - 1;
+
+            g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+            g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+        }
+        
+        str = ui_list_next(list->list);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
+
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) {
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = NULL;
+    evt.intval = event->value;
+    event->callback(&evt, event->userdata);    
+}
+
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) {
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = NULL;
+    evt.intval = gtk_check_menu_item_get_active(ci);
+    event->callback(&evt, event->userdata);    
+}
+
+int64_t ui_checkitem_get(UiInteger *i) {
+    int state = gtk_check_menu_item_get_active(i->obj);
+    i->value = state;
+    return state;
+}
+
+void ui_checkitem_set(UiInteger *i, int64_t value) {
+    i->value = value;
+    gtk_check_menu_item_set_active(i->obj, value);
+}
+
+
+/*
+ * widget menu functions
+ */
+
+static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
+    if(event->type == GDK_BUTTON_PRESS) {
+        GdkEventButton *e = (GdkEventButton*)event;
+        if(e->button == 3) {
+            gtk_widget_show_all(GTK_WIDGET(menu));
+            ui_contextmenu_popup(menu);
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+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);
+    
+    GtkMenu *menu = GTK_MENU(gtk_menu_new());
+    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
+    
+    ct->menu = menu;
+    return menu;
+}
+
+void ui_contextmenu_popup(UIMENU menu) {
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+    gtk_menu_popup_at_pointer(menu, NULL);
+#else
+    gtk_menu_popup(menu, NULL, NULL, 0, 0, 0, gtk_get_current_event_time());
+#endif
+}
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
+    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+}
+
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    CxList *groups = NULL;
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
+    }
+    va_end(ap);
+    
+    // create menuitem
+    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label);
+    gtk_widget_show(widget);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+        cxListDestroy(groups);
+    }
+}
+
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
+    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+}
+
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    CxList *groups = NULL;
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
+    }
+    va_end(ap);
+    
+    // create menuitem
+    GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group);
+    gtk_widget_show(widget);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+        cxListDestroy(groups);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,77 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MENU_H
+#define	MENU_H
+
+#include "../ui/menu.h"
+#include "../common/menu.h"
+#include <cx/list.h>
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+    
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef void(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
+
+struct UiActiveMenuItemList {
+    UiObject     *object;
+    GtkMenuShell *menu;
+    int          index;
+    int          oldcount;
+    UiList       *list;
+    ui_callback  callback;
+    void         *userdata;
+};
+
+GtkWidget *ui_create_menubar(UiObject *obj);
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event);
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event);
+int64_t ui_checkitem_get(UiInteger *i);
+void ui_checkitem_set(UiInteger *i, int64_t value);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/model.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,539 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "model.h"
+#include "image.h"
+#include "toolkit.h"
+
+#define IS_UI_LIST_MODEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type))
+#define UI_LIST_MODEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_CAST((obj), list_model_type, UiListModel))
+
+static void list_model_class_init(GObjectClass *cl, gpointer data);
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data);
+static void list_model_init(UiListModel *instance, GObjectClass *cl);
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data);
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data);
+
+static GObjectClass list_model_class;
+static const GTypeInfo list_model_info = {
+    sizeof(GObjectClass),
+    NULL,
+    NULL,
+    (GClassInitFunc)list_model_class_init,
+    NULL,
+    NULL,
+    sizeof(UiListModel),
+    0,
+    (GInstanceInitFunc)list_model_init
+};
+static const GInterfaceInfo list_model_interface_info = {
+    (GInterfaceInitFunc)list_model_interface_init,
+    NULL,
+    NULL
+};
+static GType list_model_type;
+
+static const GInterfaceInfo list_model_dnd_dest_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_dest_interface_init,
+    NULL,
+    NULL
+};
+static const GInterfaceInfo list_model_dnd_src_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_src_interface_init,
+    NULL,
+    NULL
+};
+
+void ui_list_init() {
+    list_model_type = g_type_register_static(
+            G_TYPE_OBJECT,
+            "UiListModel",
+            &list_model_info,
+            (GTypeFlags)0);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_MODEL,
+            &list_model_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_DEST,
+            &list_model_dnd_dest_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_SOURCE,
+            &list_model_dnd_src_interface_info);
+}
+
+static void list_model_class_init(GObjectClass *cl, gpointer data) {
+    cl->dispose = ui_list_model_dispose;
+    cl->finalize = ui_list_model_finalize;
+    
+}
+
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) {
+    i->get_flags       = ui_list_model_get_flags;
+    i->get_n_columns   = ui_list_model_get_n_columns;
+    i->get_column_type = ui_list_model_get_column_type;
+    i->get_iter        = ui_list_model_get_iter;
+    i->get_path        = ui_list_model_get_path;
+    i->get_value       = ui_list_model_get_value;
+    i->iter_next       = ui_list_model_iter_next;
+    i->iter_children   = ui_list_model_iter_children;
+    i->iter_has_child  = ui_list_model_iter_has_child;
+    i->iter_n_children = ui_list_model_iter_n_children;
+    i->iter_nth_child  = ui_list_model_iter_nth_child;
+    i->iter_parent     = ui_list_model_iter_parent;
+}
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) {
+    i->drag_data_received = ui_list_model_drag_data_received;
+    i->row_drop_possible = ui_list_model_row_drop_possible;
+}
+
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) {
+    i->drag_data_delete = ui_list_model_drag_data_delete;
+    i->drag_data_get = ui_list_model_drag_data_get;
+    i->row_draggable = ui_list_model_row_draggable;
+}
+
+static void list_model_init(UiListModel *instance, GObjectClass *cl) {
+    instance->columntypes = NULL;
+    instance->var = NULL;
+    instance->numcolumns = 0;
+    instance->stamp = g_random_int();
+}
+
+static GType ui_gtk_type(UiModelType type) {
+    switch(type) {
+        default: break;
+        case UI_STRING: return G_TYPE_STRING;
+        case UI_INTEGER: return G_TYPE_INT;
+    }
+    return G_TYPE_INVALID;
+}
+
+static void ui_model_set_value(GType type, void *data, GValue *value) {
+    switch(type) {
+        default: break;
+        case G_TYPE_OBJECT: {
+            value->g_type = G_TYPE_OBJECT;
+            g_value_set_object(value, data);
+            return;
+        }
+        case G_TYPE_STRING: {
+            value->g_type = G_TYPE_STRING;
+            g_value_set_string(value, data);
+            return;
+        }
+        case G_TYPE_INT: {
+            value->g_type = G_TYPE_INT;
+            int *i = data;
+            g_value_set_int(value, *i);
+            return;
+        }
+    }
+    value->g_type = G_TYPE_INVALID; 
+}
+
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) {
+    UiListModel *model = g_object_new(list_model_type, NULL);
+    model->obj = obj;
+    model->model = info;
+    model->var = var;
+    model->columntypes = calloc(sizeof(GType), 2 * info->columns);
+    int ncol = 0;
+    for(int i=0;i<info->columns;i++) {
+        UiModelType type = info->types[i];
+        if(type == UI_ICON_TEXT) {
+            model->columntypes[ncol] = G_TYPE_OBJECT;
+            ncol++;
+            model->columntypes[ncol] = G_TYPE_STRING;
+        } else {
+            model->columntypes[ncol] = ui_gtk_type(info->types[i]);
+        }
+        ncol++;
+    }
+    model->numcolumns = ncol;
+    return model;
+}
+
+void ui_list_model_dispose(GObject *obj) {
+    
+}
+
+void ui_list_model_finalize(GObject *obj) {
+    
+}
+
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) {
+    return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
+}
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) {
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0);
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    return model->numcolumns;
+}
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) {
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID);
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID);
+    return model->columntypes[index];
+}
+
+gboolean ui_list_model_get_iter(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreePath *path)
+{
+    g_assert(IS_UI_LIST_MODEL(tree_model));
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    // check the depth of the path
+    // a list must have a depth of 1
+    gint depth = gtk_tree_path_get_depth(path);
+    g_assert(depth == 1);
+    
+    // get row
+    gint *indices = gtk_tree_path_get_indices(path);
+    gint row = indices[0];
+    
+    // check row
+    if(row == 0) {
+        // we don't need to count if the first element is requested
+        if(list->first(list) == NULL) {
+            return FALSE;
+        }
+    } else if(row >= list->count(list)) {
+        return FALSE;
+    }
+    
+    // the UiList has an integrated iterator
+    // we only get a value to adjust it
+    void *val = NULL;
+    if(row == 0) {
+        val = list->first(list);
+    } else {
+        val = list->get(list, row);
+    }
+    
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)(intptr_t)row; // list->index
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+GtkTreePath* ui_list_model_get_path(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL);
+    g_return_val_if_fail(iter != NULL, NULL);
+    g_return_val_if_fail(iter->user_data != NULL, NULL);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    
+    GtkTreePath *path = gtk_tree_path_new();
+    gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index
+    
+    return path;
+}
+
+void ui_list_model_get_value(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        gint column,
+        GValue *value)
+{
+    g_return_if_fail(IS_UI_LIST_MODEL(tree_model));
+    g_return_if_fail(iter != NULL);
+    g_return_if_fail(iter->user_data != NULL);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    g_return_if_fail(column < model->numcolumns);
+    
+    // TODO: return correct value from column
+    
+    //value->g_type = G_TYPE_STRING;
+    list->iter = iter->user_data;
+    //list->index = (int)(intptr_t)iter->user_data2;
+    //list->current = iter->user_data3;
+    if(model->model->getvalue) {
+        void *data = model->model->getvalue(iter->user_data3, column);
+        if(model->columntypes[column] == G_TYPE_OBJECT) {
+            UiImage *img = data;
+            ui_model_set_value(model->columntypes[column], img->pixbuf, value);
+        } else {
+            ui_model_set_value(model->columntypes[column], data, value);
+        }
+    } else {
+        value->g_type = G_TYPE_INVALID;
+    }
+}
+
+gboolean ui_list_model_iter_next(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    g_return_val_if_fail(iter != NULL, FALSE);
+    //g_return_val_if_fail(iter->user_data != NULL, FALSE);
+    
+    if(!iter->user_data) {
+        return FALSE;
+    }
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    list->iter = iter->user_data;
+    //list->index = (int)(intptr_t)iter->user_data2;
+    void *val = list->next(list);
+    iter->user_data = list->iter;
+    intptr_t index = (intptr_t)iter->user_data2;
+    index++;
+    //iter->user_data2 = (gpointer)(intptr_t)list->index;
+    iter->user_data2 = (gpointer)index;
+    iter->user_data3 = val;
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    if(parent) {
+        return FALSE;
+    }
+    
+    /*
+     * a list element has no children
+     * we set the iter to the first element
+     */
+    void *val = list->first(list);
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)0;
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_has_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    return FALSE;
+}
+
+gint ui_list_model_iter_n_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_assert(IS_UI_LIST_MODEL(tree_model));
+    
+    if(!iter) {
+        // return number of rows
+        UiListModel *model = UI_LIST_MODEL(tree_model);
+        UiList *list = model->var->value;
+        return list->count(list);
+    }
+    
+    return 0;
+}
+
+gboolean ui_list_model_iter_nth_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent,
+        gint n)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    
+    if(parent) {
+        return FALSE;
+    }
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    // check n
+    if(n == 0) {
+        // we don't need to count if the first element is requested
+        if(list->first(list) == NULL) {
+            return FALSE;
+        }
+    } else if(n >= list->count(list)) {
+        return FALSE;
+    }
+    
+    void *val = list->get(list, n);
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)(intptr_t)n; // list->index
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_parent(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *child)
+{
+    return FALSE;
+}
+
+// ****** dnd ******
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+        GtkSelectionData  *selection_data)
+{
+    //printf("drag received\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->drop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->drop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+	GtkSelectionData  *selection_data)
+{
+    //printf("row_drop_possible\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->candrop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        return model->model->candrop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path)
+{
+    //printf("row_draggable\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->candrag) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        return model->model->candrag(&e, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data)
+{
+    //printf("drag_data_get\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->data_get(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path)
+{
+    //printf("drag_data_delete\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        model->model->data_delete(&e, model->var->value, row);
+    }
+    return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/model.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,149 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MODEL_H
+#define	MODEL_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../ui/tree.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif    
+
+typedef struct UiListModel        UiListModel;
+
+/*
+ * UiList to GtkTreeModel wrapper
+ */
+struct UiListModel {
+    GObject  object;
+    UiObject *obj;
+    UiModel  *model;
+    UiVar   *var;
+    GType    *columntypes;
+    int      numcolumns;
+    int      stamp;
+};
+
+/*
+ * initialize the class and register the type
+ */
+void ui_list_init();
+
+/*
+ * Creates a UiListModel for a given UiList
+ */
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info);
+
+void ui_list_model_dispose(GObject *obj);
+void ui_list_model_finalize(GObject *obj);
+
+
+// interface functions
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model);
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model);
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index);
+
+gboolean ui_list_model_get_iter(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreePath *path);
+
+GtkTreePath* ui_list_model_get_path(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+void ui_list_model_get_value(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        gint column,
+        GValue *value);
+
+gboolean ui_list_model_iter_next(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent);
+
+gboolean ui_list_model_iter_has_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gint ui_list_model_iter_n_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_nth_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent,
+        gint n);
+
+gboolean ui_list_model_iter_parent(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *child);
+
+/* dnd */
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest,
+        GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+	GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path);
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data);
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* MODEL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+GTK_SRC_DIR = ui/gtk/
+GTK_OBJPRE = $(OBJ_DIR)$(GTK_SRC_DIR)
+
+# some objects are defined in config.mk
+GTKOBJ += toolkit.o
+GTKOBJ += window.o
+GTKOBJ += container.o
+GTKOBJ += menu.o
+GTKOBJ += toolbar.o
+GTKOBJ += button.o
+GTKOBJ += display.o
+GTKOBJ += text.o
+GTKOBJ += model.o
+GTKOBJ += tree.o
+GTKOBJ += image.o
+GTKOBJ += graphics.o
+GTKOBJ += range.o
+GTKOBJ += entry.o
+GTKOBJ += dnd.o
+
+TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
+TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/range.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,130 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "range.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
+#ifdef UI_GTK3
+    GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL);
+#else
+    GtkWidget *scrollbar;
+    if(orientation == UI_HORIZONTAL) {
+        scrollbar = gtk_hscrollbar_new(NULL);
+    } else {
+        scrollbar = gtk_hscrollbar_new(NULL);
+    }
+#endif
+    
+    if(range) {
+        range->get = ui_scrollbar_get;
+        range->set = ui_scrollbar_set;
+        range->setrange = ui_scrollbar_setrange;
+        range->setextent = ui_scrollbar_setextent;
+        range->obj = scrollbar;
+    }
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+        
+        g_signal_connect(
+                G_OBJECT(scrollbar),
+                "value-changed",
+                G_CALLBACK(ui_scrollbar_value_changed),
+                event);
+        g_signal_connect(
+                scrollbar,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scrollbar, FALSE);
+    
+    return scrollbar;
+}
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
+}
+
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
+}
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = event->value;
+    event->callback(&e, event->userdata); 
+    return TRUE;
+}
+
+double ui_scrollbar_get(UiRange *range) {
+    double value = gtk_range_get_value(GTK_RANGE(range->obj));
+    range->value = value;
+    return value;
+}
+
+void   ui_scrollbar_set(UiRange *range, double value) {
+    gtk_range_set_value(GTK_RANGE(range->obj), value);
+    range->value = value;
+}
+
+void   ui_scrollbar_setrange(UiRange *range, double min, double max) {
+    gtk_range_set_range(GTK_RANGE(range->obj), min, max);
+    range->min = min;
+    range->max = max;
+}
+
+void   ui_scrollbar_setextent(UiRange *range, double extent) {
+    GtkAdjustment *a = gtk_range_get_adjustment(GTK_RANGE(range->obj));
+#ifdef UI_GTK2LEGACY
+    a->page_size = extent;
+#else
+    gtk_adjustment_set_page_size(a, extent);
+#endif
+#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18)
+    gtk_adjustment_changed(a);
+#endif
+    range->extent = extent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/range.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event);
+    
+double ui_scrollbar_get(UiRange *range);
+void   ui_scrollbar_set(UiRange *range, double value);
+void   ui_scrollbar_setrange(UiRange *range, double min, double max);
+void   ui_scrollbar_setextent(UiRange *range, double extent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/text.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,691 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "text.h"
+#include "container.h"
+
+
+#include "../common/types.h"
+
+static void selection_handler(
+        GtkTextBuffer *buf,
+        GtkTextIter *location,
+        GtkTextMark *mark,
+        UiTextArea *textview)
+{
+    const char *mname = gtk_text_mark_get_name(mark);
+    if(mname) {
+        GtkTextIter begin;
+        GtkTextIter end;
+        int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
+        if(sel != textview->last_selection_state) {
+            if(sel) {
+                ui_set_group(textview->ctx, UI_GROUP_SELECTION);
+            } else {
+                ui_unset_group(textview->ctx, UI_GROUP_SELECTION);
+            }
+        }
+        textview->last_selection_state = sel;
+    }
+}
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+    GtkWidget *text_area = gtk_text_view_new();
+    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
+    g_signal_connect(
+            text_area,
+            "realize",
+            G_CALLBACK(ui_textarea_realize_event),
+            NULL);
+    
+    UiTextArea *uitext = malloc(sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    uitext->last_selection_state = 0;
+    
+    g_signal_connect(
+                text_area,
+                "destroy",
+                G_CALLBACK(ui_textarea_destroy),
+                uitext);
+    
+    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    
+    // font and padding
+    PangoFontDescription *font;
+    font = pango_font_description_from_string("Monospace");
+    gtk_widget_modify_font(text_area, font);
+    pango_font_description_free(font);
+    
+    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
+    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
+    
+    // add
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // bind value
+    UiText *value = var->value;
+    if(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);
+        }
+        
+        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->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->remove = ui_textarea_remove;
+        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);
+    }
+    
+    return scroll_area;
+}
+
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
+    ui_destroy_boundvar(textarea->ctx, textarea->var);
+    free(textarea);
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    var->from = NULL;
+    var->from_ctx = NULL;
+    return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+    if(var) {
+        return ui_textarea_var(obj, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
+    return gtk_bin_get_child(GTK_BIN(textarea));
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    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);
+    char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+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);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+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);
+}
+
+int ui_textarea_position(UiText *text) {
+    GtkTextIter begin;
+    GtkTextIter end;
+    gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
+    text->pos = gtk_text_iter_get_offset(&begin);
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    GtkTextIter b;
+    GtkTextIter e;
+    gtk_text_buffer_get_selection_bounds(text->obj, &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;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    return gtk_text_iter_get_offset(&end);
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter ib;
+    GtkTextIter ie;
+    gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
+    gtk_text_buffer_delete(buf, &ib, &ie);
+}
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
+    gtk_widget_grab_focus(widget);
+}
+
+
+
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
+    UiText *value = textarea->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textarea->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textarea->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+// undo manager functions
+
+void ui_textbuf_insert(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *location,
+        char *text,
+        int length,
+        void *data)
+{
+    UiVar *var = data;
+    UiText *value = var->value;
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    if(mgr->cur) {
+        UiTextBufOp *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
+            while(elm) {
+                elm->prev = NULL;   
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
+                elm = next;
+            }
+        }
+        
+        UiTextBufOp *last_op = mgr->cur;
+        if(
+            last_op->type == UI_TEXTBUF_INSERT &&
+            ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+        {
+            // append text to last op       
+            int ln = last_op->len;
+            char *newtext = malloc(ln + length + 1);
+            memcpy(newtext, last_op->text, ln);
+            memcpy(newtext+ln, text, length);
+            newtext[ln+length] = '\0';
+            
+            last_op->text = newtext;
+            last_op->len = ln + length;
+            last_op->end += length;
+            
+            return;
+        }
+    }
+    
+    char *dpstr = malloc(length + 1);
+    memcpy(dpstr, text, length);
+    dpstr[length] = 0;
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
+    op->type = UI_TEXTBUF_INSERT;
+    op->start = gtk_text_iter_get_offset(location);
+    op->end = op->start+length;
+    op->len = length;
+    op->text = dpstr;
+    
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
+}
+
+void ui_textbuf_delete(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *start,
+        GtkTextIter *end,
+        void *data)
+{
+    UiVar *var = data;
+    UiText *value = var->value;
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    if(mgr->cur) {
+        UiTextBufOp *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
+            while(elm) {
+                elm->prev = NULL;   
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
+                elm = next;
+            }
+        }
+    }
+    
+    char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
+    op->type = UI_TEXTBUF_DELETE;
+    op->start = gtk_text_iter_get_offset(start);
+    op->end = gtk_text_iter_get_offset(end);
+    op->len = op->end - op->start;
+    
+    char *dpstr = malloc(op->len + 1);
+    memcpy(dpstr, text, op->len);
+    dpstr[op->len] = 0;
+    op->text = dpstr;
+    
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
+}
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->end = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+    UiTextBufOp *op = mgr->begin;
+    while(op) {
+        UiTextBufOp *nextOp = op->next;
+        if(op->text) {
+            free(op->text);
+        }
+        free(op);
+        op = nextOp;
+    }
+    free(mgr);
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+    if(op->text) {
+        free(op->text);
+    }
+    free(op);
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+    // return 1 if oldstr + newstr are one word
+    
+    int has_space = 0;
+    for(int i=0;i<oldlen;i++) {
+        if(oldstr[i] < 33) {
+            has_space = 1;
+            break;
+        }
+    }
+    
+    for(int i=0;i<newlen;i++) {
+        if(has_space && newstr[i] > 32) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+void ui_text_undo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    if(mgr->cur) {
+        UiTextBufOp *op = mgr->cur;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_delete(value->obj, &begin, &end);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = mgr->cur->prev;
+    }
+}
+
+void ui_text_redo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    UiTextBufOp *elm = NULL;
+    if(mgr->cur) {
+        if(mgr->cur->next) {
+            elm = mgr->cur->next;
+        }
+    } else if(mgr->begin) {
+        elm = mgr->begin;
+    }
+    
+    if(elm) {
+        UiTextBufOp *op = elm;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_delete(value->obj, &begin, &end);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
+    GtkWidget *textfield = gtk_entry_new();
+    
+    UiTextField *uitext = malloc(sizeof(UiTextField));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    
+    g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(ui_textfield_destroy),
+                uitext);
+    
+    if(width > 0) {
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
+    }
+    if(frameless) {
+        // TODO: gtk2legacy workaroud
+        gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
+    }
+    if(password) {
+        gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, textfield, FALSE);
+    
+    if(var) {
+        UiString *value = var->value;
+        if(value->value.ptr) {
+            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            value->value.free(value->value.ptr);
+            value->value.ptr = NULL;
+            value->value.free = NULL;
+        }
+        
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+        value->value.ptr = NULL;
+        value->value.free = NULL;
+        value->obj = GTK_ENTRY(textfield);
+        
+        g_signal_connect(
+                textfield,
+                "changed",
+                G_CALLBACK(ui_textfield_changed),
+                uitext);
+    }
+    
+    return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+    if(var) {
+        return create_textfield_var(obj, width, frameless, password, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiVar *var = NULL;
+    if(value) {
+        var = malloc(sizeof(UiVar));
+        var->value = value;
+        var->type = UI_VAR_SPECIAL;
+        var->from = NULL;
+        var->from_ctx = NULL;
+    }
+    return create_textfield_var(obj, width, frameless, password, var);
+}
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
+    if(textfield->var) {
+        UiText *text = textfield->var->value;
+        if(text->undomgr) {
+            ui_destroy_undomgr(text->undomgr);
+        }
+        ui_destroy_boundvar(textfield->ctx, textfield->var);
+    }
+    free(textfield);
+}
+
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+    UiString *value = textfield->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textfield->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textfield->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    gtk_entry_set_text(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,118 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_H
+#define	TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+#include <cx/linked_list.h>
+#include "../common/context.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+    
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+    UiTextBufOp *prev;
+    UiTextBufOp *next;
+    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+    int  start;
+    int  end;
+    int  len;
+    char *text;
+};
+
+typedef struct UiUndoMgr {
+    UiTextBufOp  *begin;
+    UiTextBufOp  *end;
+    UiTextBufOp  *cur;
+    int          length;
+    int          event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+    UiContext *ctx;
+    UiVar    *var;
+    int       last_selection_state;
+} UiTextArea;
+
+typedef struct UiTextField {
+    UiContext *ctx;
+    UiVar    *var;
+    // TODO: validatefunc
+} UiTextField;
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
+
+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);
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data);
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea);
+
+void ui_textbuf_insert(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *location,
+        char *text,
+        int len,
+        void *data);
+void ui_textbuf_delete(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *start,
+        GtkTextIter *end,
+        void *data);
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_free_textbuf_op(UiTextBufOp *op);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield);
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, char *value);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/toolbar.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,430 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "toolbar.h"
+#include "button.h"
+#include "image.h"
+#include "tree.h"
+#include <cx/basic_mempool.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include "../common/context.h"
+
+static CxMap *toolbar_items;
+static CxList *defaults;
+
+void ui_toolbar_init() {
+    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    defaults = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *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_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
+    UiToolItem *item = malloc(sizeof(UiToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+    item->label = label;
+    item->image = img;
+    item->callback = f;
+    item->userdata = udata;
+    item->isimportant = 0;
+    item->groups = NULL;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_vstgr(
+        char *name,
+        char *stockid,
+        int isimportant,
+        ui_callback f,
+        void *userdata,
+        va_list ap)
+{
+    UiStToolItem *item = malloc(sizeof(UiStToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
+    item->stockid = stockid;
+    item->callback = f;
+    item->userdata = userdata;
+    item->groups = NULL;
+    item->isimportant = isimportant;
+    
+    // add groups
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!item->groups) {
+            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+        }
+        cxListAdd(item->groups, &group);
+    }
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = label;
+    item->image = img;
+    item->stockid = NULL;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = i;
+    item->var = NULL;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = NULL;
+    item->image = NULL;
+    item->stockid = stockid;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = i;
+    item->var = NULL;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = label;
+    item->image = img;
+    item->stockid = NULL;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = NULL;
+    item->var = intvar;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = NULL;
+    item->image = NULL;
+    item->stockid = stockid;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = NULL;
+    item->var = intvar;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+
+void ui_toolbar_combobox(
+        char *name,
+        UiList *list,
+        ui_getvaluefunc getvalue,
+        ui_callback f,
+        void *udata)
+{
+    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
+    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    var->from = NULL;
+    var->from_ctx = NULL;
+    cb->var = var;
+    cb->getvalue = getvalue;
+    cb->callback = f;
+    cb->userdata = udata;
+    
+    cxMapPut(toolbar_items, name, cb);
+}
+
+void ui_toolbar_combobox_str(
+        char *name,
+        UiList *list,
+        ui_callback f,
+        void *udata)
+{
+    ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
+}
+
+void ui_toolbar_combobox_nv(
+        char *name,
+        char *listname,
+        ui_getvaluefunc getvalue,
+        ui_callback f,
+        void *udata)
+{
+    UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
+    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;  
+    cb->listname = listname;
+    cb->getvalue = getvalue;
+    cb->callback = f;
+    cb->userdata = udata;
+    
+    cxMapPut(toolbar_items, name, cb);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+    char *s = strdup(name);
+    cxListAdd(defaults, s);
+}
+
+GtkWidget* ui_create_toolbar(UiObject *obj) {
+    if(!defaults) {
+        return NULL;
+    }
+    
+    GtkWidget *toolbar = gtk_toolbar_new();
+#ifdef UI_GTK3
+    gtk_style_context_add_class(
+            gtk_widget_get_style_context(toolbar),
+            GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
+#endif
+    
+    GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char *, def, i) {
+        UiToolItemI *item = cxMapGet(toolbar_items, def);
+        if(item) {
+            item->add_to(tb, item, obj);
+        } else if(!strcmp(def, "@separator")) {
+            gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1);
+        } else {
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
+        }
+    }
+    
+    return toolbar;
+}
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj) {
+    GtkToolItem *button = gtk_tool_button_new(NULL, item->label);
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->image) {
+        GdkPixbuf *pixbuf = ui_get_image(item->image);
+        GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+        gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+    } else {
+        gtk_tool_item_set_is_important(button, TRUE);
+    }
+    
+    if(item->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+    }
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) {
+    GtkToolItem *button = gtk_tool_button_new_from_stock(item->stockid);
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->isimportant) {
+        gtk_tool_item_set_is_important(button, TRUE);
+    }
+    
+    if(item->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+    }
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->stockid) {
+        button = gtk_toggle_tool_button_new_from_stock(item->stockid);
+    } else {
+        button = gtk_toggle_tool_button_new();
+        gtk_tool_item_set_homogeneous(button, FALSE);
+        if(item->label) {
+            gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->label);
+        }
+        if(item->image) {
+            GdkPixbuf *pixbuf = ui_get_image(item->image);
+            GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+            gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+        }    
+    }
+    
+    UiVar *var;
+    if(item->value) {
+        var = malloc(sizeof(UiVar));
+        var->value = item->value;
+        var->type = UI_VAR_SPECIAL;
+        var->from = NULL;
+        var->from_ctx = NULL;
+    } else {
+        var = uic_create_var(obj->ctx, item->var, UI_VAR_INTEGER);
+    }
+    
+    if(var->value) {
+        UiInteger *i = var->value;
+        i->get = ui_tool_toggle_button_get;
+        i->set = ui_tool_toggle_button_set;
+        i->obj = button;
+        
+        if(i->value != 0) {
+            gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE);
+        }
+    }
+    
+    // register event
+    // the event func will call the UiInteger observer callbacks
+    UiEventData *event = cxMalloc(
+            obj->ctx->allocator,
+            sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = var;
+    event->callback = NULL;
+
+    g_signal_connect(
+            button,
+            "toggled",
+            G_CALLBACK(ui_tool_button_toggled),
+            event);
+    
+    // add item to toolbar
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_toggle_tool_button_get_active(widget);
+    
+    UiVar *var = event->userdata;
+    UiInteger *i = var->value;
+    
+    ui_notify_evt(i->observers, &e);
+}
+
+int64_t ui_tool_toggle_button_get(UiInteger *integer) {
+    integer->value = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj));
+    return integer->value;
+}
+
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value) {
+    gboolean s = value != 0 ? TRUE : FALSE;
+    gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj), s);
+    integer->value = s;
+}
+
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) {
+    UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+    modelinfo->getvalue = cb->getvalue;
+    UiListModel *model = ui_list_model_new(obj, cb->var, modelinfo);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+    GtkToolItem *item = gtk_tool_item_new();
+    gtk_container_add(GTK_CONTAINER(item), combobox);
+    gtk_toolbar_insert(tb, item, -1);
+}
+
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj) {
+    UiVar *var = uic_create_var(obj->ctx, cb->listname, UI_VAR_LIST);
+    if(var) {
+        UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+        modelinfo->getvalue = cb->getvalue;
+        UiListModel *model = ui_list_model_new(obj, var, modelinfo);
+        
+        GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+        GtkToolItem *item = gtk_tool_item_new();
+        gtk_container_add(GTK_CONTAINER(item), combobox);
+        gtk_toolbar_insert(tb, item, -1);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,135 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLBAR_H
+#define	TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include <cx/map.h>
+#include <cx/list.h>
+
+#include "model.h"
+#include "tree.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolItemI      UiToolItemI;
+typedef struct UiToolItem       UiToolItem;
+typedef struct UiStToolItem     UiStToolItem;
+typedef struct UiToggleToolItem UiToggleToolItem;
+
+typedef struct UiToolbarComboBox   UiToolbarComboBox;
+typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
+
+typedef void(*ui_toolbar_add_f)(GtkToolbar*, UiToolItemI*, UiObject*);
+
+struct UiToolItemI {
+    ui_toolbar_add_f  add_to;
+};
+
+struct UiToolItem {
+    UiToolItemI    item;
+    const char     *label;
+    const char     *image;
+    ui_callback    callback;
+    void           *userdata;
+    const char     *varname;
+    CxList         *groups;
+    int            isimportant;
+};
+
+struct UiStToolItem {
+    UiToolItemI    item;
+    const char     *stockid;
+    ui_callback    callback;
+    void           *userdata;
+    const char     *varname;
+    CxList         *groups;
+    int            isimportant;
+};
+
+struct UiToggleToolItem {
+    UiToolItemI    item;
+    const char     *label;
+    const char     *image;
+    const char     *stockid;
+    UiInteger      *value;
+    const char     *var;
+    CxList         *groups;
+    int            isimportant;
+};
+
+struct UiToolbarComboBox {
+    UiToolItemI         item;
+    UiVar               *var;
+    ui_getvaluefunc getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+struct UiToolbarComboBoxNV {
+    UiToolItemI         item;
+    char                *listname;
+    ui_getvaluefunc     getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+void ui_toolbar_init();
+
+void ui_toolitem_vstgr(
+        char *name,
+        char *stockid,
+        int isimportant,
+        ui_callback f,
+        void *userdata,
+        va_list ap);
+
+GtkWidget* ui_create_toolbar(UiObject *obj);
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj);
+
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj);
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_update(UiEvent *event, void *combobox);
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event);
+int64_t ui_tool_toggle_button_get(UiInteger *integer);
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/toolkit.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,270 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "toolkit.h"
+#include "toolbar.h"
+#include "model.h"
+#include "image.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+
+#include <cx/utils.h>
+#include <cx/string.h>
+#include <cx/printf.h>
+
+#include <pthread.h>
+
+#ifndef UI_GTK2
+static GtkApplication *app;
+#endif
+
+static 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;
+
+static ui_callback   appclose_fnc;
+static void          *appclose_udata;
+
+static UiObject      *active_window;
+
+static int scale_factor = 1;
+
+void ui_init(char *appname, int argc, char **argv) {
+    uic_init_global_context();
+    
+    gtk_init(&argc, &argv);
+    application_name = appname;
+    
+    uic_docmgr_init();
+    ui_toolbar_init();
+    
+    // init custom types
+    ui_list_init();
+    
+    ui_image_init();
+    
+    uic_load_app_properties();
+    
+#ifdef UI_SUPPORTS_SCALE
+    scale_factor = gdk_monitor_get_scale_factor(
+            gdk_display_get_primary_monitor(gdk_display_get_default()));
+#endif
+}
+
+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;
+}
+
+
+#ifndef UI_GTK2
+static void app_startup(GtkApplication* app, gpointer userdata) {
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
+}
+
+static void app_activate(GtkApplication* app, gpointer userdata) {
+    printf("activate\n");
+}
+#endif
+
+void ui_main() {
+#ifndef UI_GTK2
+    cxmutstr appid = cx_asprintf(
+            "ui.%s",
+            application_name ? application_name : "application1");
+    
+    app = gtk_application_new(
+            appid.ptr,
+            G_APPLICATION_FLAGS_NONE);
+    g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
+    g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
+    g_application_run(G_APPLICATION (app), 0, NULL);
+    g_object_unref (app);
+    
+    free(appid.ptr);
+#else
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
+    gtk_main();
+#endif
+    if(exit_func) {
+        exit_func(NULL, exit_data);
+    }
+    uic_store_app_properties();
+}
+
+#ifndef UI_GTK2
+void ui_app_quit() {
+    g_application_quit(G_APPLICATION(app));
+}
+
+GtkApplication* ui_get_application() {
+    return app;
+}
+#endif
+
+void ui_show(UiObject *obj) {
+    uic_check_group_widgets(obj->ctx);
+    gtk_widget_show_all(obj->widget);
+}
+
+void ui_close(UiObject *obj) {
+    gtk_widget_destroy(obj->widget);
+}
+
+
+static gboolean ui_job_finished(void *data) {
+    UiJob *job = data;
+    
+    UiEvent event;
+    event.obj = job->obj;
+    event.window = job->obj->window;
+    event.document = job->obj->ctx->document;
+    event.intval = 0;
+    event.eventdata = NULL;
+
+    job->finish_callback(&event, job->finish_data);
+    free(job);
+    return FALSE;
+}
+
+static void* ui_jobthread(void *data) {
+    UiJob *job = data;
+    int result = job->job_func(job->job_data);
+    if(!result) {
+        g_idle_add(ui_job_finished, job);
+    }
+    return NULL;
+}
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+    UiJob *job = malloc(sizeof(UiJob));
+    job->obj = obj;
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = f;
+    job->finish_data = fd;
+    pthread_t pid;
+    pthread_create(&pid, NULL, ui_jobthread, job);
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    gtk_widget_set_sensitive(widget, enabled);
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    gtk_widget_set_no_show_all(widget, !value);
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    if(visible) {
+        gtk_widget_set_no_show_all(widget, FALSE);
+        gtk_widget_show_all(widget);
+    } else {
+        gtk_widget_hide(widget);
+    }
+}
+
+void ui_clipboard_set(char *str) {
+    GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    gtk_clipboard_set_text(cb, str, strlen(str));
+}
+
+char* ui_clipboard_get() {
+    GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    char *str = gtk_clipboard_wait_for_text(cb);
+    if(str) {
+        char *copy = strdup(str);
+        g_free(str);
+        return copy;
+    } else {
+        return NULL;
+    }
+}
+
+int ui_get_scalefactor() {
+    return scale_factor;
+}
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata) {
+    free(userdata);
+}
+
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) {
+    ui_destroy_boundvar(data->obj->ctx, data->var);
+    free(data);
+}
+
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+    uic_unbind_var(var);
+    
+    if(var->type == UI_VAR_SPECIAL) {
+        free(var);
+    } else {
+        ui_free(var->from_ctx, var);
+        // TODO: free or unbound
+        //uic_remove_bound_var(ctx, var);
+    }
+}
+
+void ui_set_active_window(UiObject *obj) {
+    active_window = obj;
+}
+
+UiObject *ui_get_active_window() {
+    return active_window;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,90 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define	TOOLKIT_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+    
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+typedef struct UiEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+} UiEventData;
+
+typedef struct UiVarEventData {
+    UiObject   *obj;
+    UiVar     *var;
+    UiObserver **observers;
+} UiVarEventData;
+
+
+typedef struct UiJob {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+} UiJob;
+
+struct UiSelection {
+    GtkSelectionData *data;
+};
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+#ifndef UI_GTK2
+void ui_app_quit();
+GtkApplication* ui_get_application();
+#endif
+
+int ui_get_scalefactor();
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata);
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
+
+void ui_set_active_window(UiObject *obj);
+UiObject *ui_get_active_window();
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLKIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/tree.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,562 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+
+#include "tree.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
+}
+
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+    
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+#if GTK_MINOR_VERSION >= 8
+    gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    // TODO: implement for older gtk3
+#endif
+#else
+    // TODO: implement for gtk2
+#endif
+    
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = getvalue;
+    UiList *list = var->value;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    
+    UiListView *listview = malloc(sizeof(UiListView));
+    listview->obj = obj;
+    listview->widget = view;
+    listview->var = var;
+    listview->model = model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->obj = listview;
+    
+    // add callback
+    if(f) {
+        UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+        event->obj = obj;
+        event->userdata = udata;
+        event->activate = f;
+        event->selection = NULL;
+
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    ct->current = view;
+    
+    return scroll_area;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_listview_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_listview_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+    printf("drag begin\n");
+    
+}
+
+static void drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer udata)
+{
+    printf("drag end\n");
+    
+}
+
+static GtkTargetEntry targetentries[] =
+    {
+      { "STRING",        0, 0 },
+      { "text/plain",    0, 1 },
+      { "text/uri-list", 0, 2 },
+    };
+
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    
+    int addi = 0;
+    for(int i=0;i<model->columns;i++) {
+        GtkTreeViewColumn *column = NULL;
+        if(model->types[i] == UI_ICON_TEXT) {
+            column = gtk_tree_view_column_new();
+            gtk_tree_view_column_set_title(column, model->titles[i]);
+            
+            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+            GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
+            
+            gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
+            gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
+            
+            
+            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
+            gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+            
+            addi++;
+        } else {
+            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                renderer,
+                "text",
+                i + addi,
+                NULL);
+        }
+        gtk_tree_view_column_set_resizable(column, TRUE);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+    }
+    
+    //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+    //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    
+#endif
+    
+    UiList *list = var->value;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    
+    //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+    //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+       
+    // add TreeView as observer to the UiList to update the TreeView if the
+    // data changes
+    UiListView *tableview = malloc(sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->widget = view;
+    tableview->var = var;
+    tableview->model = model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                tableview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->obj = tableview;
+    
+    // add callback
+    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = cb.activate;
+    event->selection = cb.selection;
+    event->userdata = cb.userdata;
+    if(cb.activate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(cb.selection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    // TODO: destroy callback
+
+    
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    ct->current = view;
+    
+    return scroll_area;
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_table_var(obj, var, model, cb);
+}
+
+UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_table_var(obj, var, model, cb);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+    GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
+    if(c) {
+        return c->data;
+    }
+    return NULL;
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+    int al = 16;
+    char **targets = calloc(16, sizeof(char*));
+    targets[0] = target0;
+    
+    int i = 1;
+    char *target;
+    while((target = va_arg(ap, char*)) != NULL) {
+        if(i >= al) {
+            al *= 2;
+            targets = realloc(targets, al*sizeof(char*));
+        }
+        targets[i] = target;
+        i++;
+    }
+    
+    *nelm = i;
+    return targets;
+}
+
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+    GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+    for(int i=0;i<nelm;i++) {
+        targets[i].target = str[i];
+    }
+    return targets;
+}
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            GDK_BUTTON1_MASK,
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+void ui_listview_update(UiList *list, int i) {
+    UiListView *view = list->obj;
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model));
+    g_object_unref(G_OBJECT(model));
+    // TODO: free old model
+}
+
+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);
+    // TODO: destroy model?
+    free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+    gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL);
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    // TODO: destroy model?
+    free(v);
+}
+
+
+void ui_listview_activate_event(
+        GtkTreeView *treeview,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event)
+{
+    UiListSelection *selection = ui_listview_selection(
+            gtk_tree_view_get_selection(treeview),
+            event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = selection;
+    e.intval = selection->count > 0 ? selection->rows[0] : -1;
+    event->activate(&e, event->userdata);
+    
+    if(selection->count > 0) {
+        free(selection->rows);
+    }
+    free(selection);
+}
+
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event)
+{
+    UiListSelection *selection = ui_listview_selection(treeselection, event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = selection;
+    e.intval = selection->count > 0 ? selection->rows[0] : -1;
+    event->selection(&e, event->userdata);
+    
+    if(selection->count > 0) {
+        free(selection->rows);
+    }
+    free(selection);
+}
+
+UiListSelection* ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event)
+{
+    GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+    
+    UiListSelection *ls = malloc(sizeof(UiListSelection));
+    ls->count = g_list_length(rows);
+    ls->rows = calloc(ls->count, sizeof(int));
+    GList *r = rows;
+    int i = 0;
+    while(r) {
+        GtkTreePath *path = r->data;
+        ls->rows[i] = ui_tree_path_list_index(path);
+        r = r->next;
+        i++;
+    }
+    return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+    int depth = gtk_tree_path_get_depth(path);
+    if(depth == 0) {
+        fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+        return -1;
+    }
+    int *indices = gtk_tree_path_get_indices(path);
+    return indices[depth - 1];
+}
+
+
+/* --------------------------- ComboBox ---------------------------  */
+
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_combobox_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_combobox_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = getvalue;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata);
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, combobox, FALSE);
+    return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) {
+    GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
+    
+    UiListView *uicbox = malloc(sizeof(UiListView));
+    uicbox->obj = obj;
+    uicbox->widget = combobox;
+    uicbox->var = model->var;
+    uicbox->model = model->model;
+    
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_combobox_destroy),
+                uicbox);
+    
+    // bind var
+    UiList *list = model->var->value;
+    list->update = ui_combobox_modelupdate;
+    list->obj = uicbox;
+    
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+    gtk_cell_layout_set_attributes(
+            GTK_CELL_LAYOUT(combobox),
+            renderer,
+            "text",
+            0,
+            NULL);
+    gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+    
+    // add callback
+    if(f) {
+        UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = udata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                combobox,
+                "changed",
+                G_CALLBACK(ui_combobox_change_event),
+                event);
+    }
+    
+    return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+    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);
+    e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+    UiListView *view = list->obj;
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+    gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/tree.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,88 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define	TREE_H
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+#include "model.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    UiObject    *obj;
+    GtkWidget   *widget;
+    UiVar       *var;
+    UiModel     *model;
+} UiListView;
+
+typedef struct UiTreeEventData {
+    UiObject    *obj;
+    ui_callback activate;
+    ui_callback selection;
+    void        *userdata;
+} UiTreeEventData;
+    
+void* ui_strmodel_getvalue(void *elm, int column);
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
+void ui_listview_update(UiList *list, int i);
+void ui_combobox_destroy(GtkWidget *w, UiListView *v);
+void ui_listview_destroy(GtkWidget *w, UiListView *v);
+
+void ui_listview_activate_event(
+        GtkTreeView *tree_view,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event);
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event);
+UiListSelection* ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event);
+int ui_tree_path_list_index(GtkTreePath *path);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_modelupdate(UiList *list, int i);
+        
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TREE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/window.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,189 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../ui/window.h"
+#include "../ui/properties.h"
+#include "../common/context.h"
+
+#include <cx/basic_mempool.h>
+
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+
+static int nwindows = 0;
+
+static int window_default_width = 650;
+static int window_default_height = 550;
+
+void ui_exit_event(GtkWidget *widget, gpointer data) {
+    UiObject *obj = data;
+    UiEvent ev;
+    ev.window = obj->window;
+    ev.document = obj->ctx->document;
+    ev.obj = obj;
+    ev.eventdata = NULL;
+    ev.intval = 0;
+    
+    if(obj->ctx->close_callback) {
+        obj->ctx->close_callback(&ev, obj->ctx->close_data);
+    }
+    // TODO: free UiObject
+    
+    nwindows--;
+#ifdef UI_GTK2
+    if(nwindows == 0) {
+        gtk_main_quit();
+    }
+#endif
+}
+
+static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
+    
+#ifndef UI_GTK2
+    obj->widget = gtk_application_window_new(ui_get_application());
+#else
+    obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+    
+    
+    obj->ctx = uic_context(obj, mp->allocator);
+    obj->window = window_data;
+    
+    if(title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(obj->widget), title);
+    }
+    
+    char *width = ui_get_property("ui.window.width");
+    char *height = ui_get_property("ui.window.height");
+    if(width && height) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                atoi(width),
+                atoi(height));
+    } else {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                window_default_width,
+                window_default_height);
+    }
+    
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(0);
+    gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
+    
+    if(!simple) {
+        // menu
+        GtkWidget *mb = ui_create_menubar(obj);
+        if(mb) {
+            gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+        }
+
+        // toolbar
+        GtkWidget *tb = ui_create_toolbar(obj);
+        if(tb) {
+            gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+        }
+    }
+    
+    // window content
+    // the content has a (TODO: not yet) configurable frame
+    GtkWidget *frame = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+    gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+    
+    // content vbox
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    gtk_container_add(GTK_CONTAINER(frame), content_box);
+    obj->container = ui_box_container(obj, content_box);
+    
+    nwindows++;
+    return obj;
+}
+
+
+UiObject* ui_window(char *title, void *window_data) {
+    return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+    return create_window(title, window_data, TRUE);
+}
+
+static char* ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action) {
+    char *button;
+    char *title;
+    
+    if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        button = GTK_STOCK_OPEN;
+        title = "Datei öffnen...";
+    } else {
+        button = GTK_STOCK_SAVE;
+        title = "Datei speichern...";
+    }
+    
+    GtkWidget *dialog = gtk_file_chooser_dialog_new(
+                                title,
+                                GTK_WINDOW(obj->widget),
+                                action,
+                                GTK_STOCK_CANCEL,
+                                GTK_RESPONSE_CANCEL,
+                                button,
+                                GTK_RESPONSE_ACCEPT,
+                                NULL);
+    if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+        char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+        gtk_widget_destroy(dialog);
+        char *copy = strdup(file);
+        g_free(file);
+        return copy;
+    } else {
+        gtk_widget_destroy(dialog);
+        return NULL;
+    }
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN);
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -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.
+#
+
+$(MOTIF_OBJPRE)%.o: motif/%.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/motif/button.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,214 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "button.h"
+#include "container.h"
+#include "../common/context.h"
+#include <cx/mempool.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include <cx/compare.h>
+
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+    UiContainer *ct = uic_get_current_container(obj);
+    XmString str = XmStringCreateLocalized(label);
+    
+    int n = 0;
+    Arg args[16];
+    
+    XtSetArg(args[n], XmNlabelString, str);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget button = XmCreatePushButton(parent, "button", args, n);
+    ct->add(ct, button);
+    
+    if(f) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = data;
+        event->callback = f;
+        event->value = 0;
+        XtAddCallback(
+                button,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+    }
+    
+    XtManageChild(button);
+    
+    return button;
+}
+
+// wrapper
+int64_t ui_toggle_button_get(UiInteger *i) {
+    int state = 0;
+    XtVaGetValues(i->obj, XmNset, &state, NULL);
+    i->value = state;
+    return state;
+}
+
+void ui_toggle_button_set(UiInteger *i, int64_t value) {
+    Arg arg;
+    XtSetArg(arg, XmNset, value);
+    XtSetValues(i->obj, &arg, 1);
+    i->value = value;
+}
+
+void ui_toggle_button_callback(
+        Widget widget,
+        UiEventData *event,
+        XmToggleButtonCallbackStruct *tb)
+{
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    // TODO: e.document
+    e.intval = tb->set;
+    event->callback(&e, event->userdata); 
+}
+
+void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.intval = event->value;
+    event->callback(&e, event->userdata);
+}
+
+
+static void radio_callback(
+        Widget widget,
+        RadioEventData *event,
+        XmToggleButtonCallbackStruct *tb)
+{
+    if(tb->set) {
+        RadioButtonGroup *group = event->group;
+        if(group->current) {
+            Arg arg;
+            XtSetArg(arg, XmNset, FALSE);
+            XtSetValues(group->current, &arg, 1);
+        }
+        group->current = widget;
+    }
+}
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+    UiContainer *ct = uic_get_current_container(obj);
+    XmString str = XmStringCreateLocalized(label);
+    
+    int n = 0;
+    Arg args[16];
+    
+    XtSetArg(args[n], XmNlabelString, str);
+    n++;
+    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget button = XmCreateToggleButton(parent, "radiobutton", args, n);
+    ct->add(ct, button);
+    
+    if(rgroup) {
+        RadioButtonGroup *group;
+        if(rgroup->obj) {
+            group = rgroup->obj;
+            if(!group->buttons) {
+                group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
+            }
+            cxListAdd(group->buttons, button);
+            group->ref++;
+        } else {
+            group = malloc(sizeof(RadioButtonGroup));
+            group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
+            cxListAdd(group->buttons, button);
+            group->current = button;
+            // this is the first button in the radiobutton group
+            // so we should enable it
+            Arg arg;
+            XtSetArg(arg, XmNset, TRUE);
+            XtSetValues(button, &arg, 1);
+            rgroup->obj = group;
+            
+            group->current = button;
+        }
+        
+        RadioEventData *event = malloc(sizeof(RadioEventData));
+        event->obj = obj;
+        event->callback = NULL;
+        event->userdata = NULL;
+        event->group = group;
+        XtAddCallback(
+            button,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)radio_callback,
+            event);
+        
+        rgroup->get = ui_radiobutton_get;
+        rgroup->set = ui_radiobutton_set;
+    }
+    
+    XtManageChild(button); 
+    return button;
+}
+
+int64_t ui_radiobutton_get(UiInteger *value) {
+    RadioButtonGroup *group = value->obj;
+    
+    int i = cxListFind(group->buttons, group->current);
+    if (i >= 0) {
+        value->value = i;
+        return i;
+    } else {
+        return 0;
+    }
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+    RadioButtonGroup *group = value->obj;
+    Arg arg;
+    
+    XtSetArg(arg, XmNset, FALSE);
+    XtSetValues(group->current, &arg, 1);
+    
+    Widget button = cxListAt(group->buttons, i);
+    if(button) {
+        XtSetArg(arg, XmNset, TRUE);
+        XtSetValues(button, &arg, 1);
+        group->current = button;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,69 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 BUTTON_H
+#define	BUTTON_H
+
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    CxList  *buttons;
+    Widget  current;
+    int     ref;
+} RadioButtonGroup;
+
+typedef struct {
+    UiObject         *obj;
+    ui_callback      callback;
+    void             *userdata;
+    RadioButtonGroup *group;
+} RadioEventData;
+
+// wrapper
+int64_t ui_toggle_button_get(UiInteger *i);
+void ui_toggle_button_set(UiInteger *i, int64_t value);
+void ui_toggle_button_callback(
+        Widget widget,
+        UiEventData *data,
+        XmToggleButtonCallbackStruct *e);
+void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* BUTTON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/container.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,814 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <cx/array_list.h>
+#include <cx/linked_list.h>
+#include <cx/compare.h>
+
+#define UI_GRID_MAX_COLUMNS 512
+
+static UiBool ui_lb2bool(UiLayoutBool b) {
+    return b == UI_LAYOUT_TRUE ? TRUE : FALSE;
+}
+
+static UiLayoutBool ui_bool2lb(UiBool b) {
+    return b ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE;
+}
+
+
+UiContainer* ui_frame_container(UiObject *obj, Widget frame) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = frame;
+    ct->prepare = ui_frame_container_prepare;
+    ct->add = ui_frame_container_add;
+    return ct;
+}
+
+Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+    return ct->widget;
+}
+
+void ui_frame_container_add(UiContainer *ct, Widget widget) {
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation) {
+    UiBoxContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiBoxContainer));
+    ct->container.widget = box;
+    ct->container.prepare = ui_box_container_prepare;
+    ct->container.add = ui_box_container_add;
+    ct->orientation = orientation;
+    ct->margin = margin;
+    ct->spacing = spacing;
+    return (UiContainer*)ct;
+}
+
+Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+    UiBoxContainer *bc = (UiBoxContainer*)ct;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
+    
+    if(bc->has_fill && fill) {
+        fprintf(stderr, "UiError: container has 2 filled widgets");
+        fill = FALSE;
+    }
+    if(fill) {
+        bc->has_fill = TRUE;
+    }
+    
+    int a = *n;
+    // determine fixed and dynamic attachments
+    void *f1;
+    void *f2;
+    void *d1;
+    void *d2;
+    void *w1;
+    void *w2;
+    if(bc->orientation == UI_BOX_VERTICAL) {
+        f1 = XmNleftAttachment;
+        f2 = XmNrightAttachment;
+        d1 = XmNtopAttachment;
+        d2 = XmNbottomAttachment;
+        w1 = XmNtopWidget;
+        w2 = XmNbottomWidget;
+        
+        // margin/spacing
+        XtSetArg(args[a], XmNleftOffset, bc->margin); a++;
+        XtSetArg(args[a], XmNrightOffset, bc->margin); a++;
+        
+        XtSetArg(args[a], XmNtopOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+    } else {
+        f1 = XmNtopAttachment;
+        f2 = XmNbottomAttachment;
+        d1 = XmNleftAttachment;
+        d2 = XmNrightAttachment;
+        w1 = XmNleftWidget;
+        w2 = XmNrightWidget;
+        
+        // margin/spacing
+        XtSetArg(args[a], XmNtopOffset, bc->margin); a++;
+        XtSetArg(args[a], XmNbottomOffset, bc->margin); a++;
+        
+        XtSetArg(args[a], XmNleftOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+    }
+    XtSetArg(args[a], f1, XmATTACH_FORM); a++;
+    XtSetArg(args[a], f2, XmATTACH_FORM); a++;
+
+    if(fill) {
+        XtSetArg(args[a], d2, XmATTACH_FORM); a++;
+    }
+    if(bc->prev_widget) {
+        XtSetArg(args[a], d1, XmATTACH_WIDGET); a++;
+        XtSetArg(args[a], w1, bc->prev_widget); a++;
+    } else {
+        XtSetArg(args[a], d1, XmATTACH_FORM); a++;
+    }
+    
+    *n = a;
+    return ct->widget;
+}
+
+void ui_box_container_add(UiContainer *ct, Widget widget) {
+    UiBoxContainer *bc = (UiBoxContainer*)ct;
+    // determine dynamic attachments
+    void *d1;
+    void *d2;
+    void *w1;
+    void *w2;
+    if(bc->orientation == UI_BOX_VERTICAL) {
+        d1 = XmNtopAttachment;
+        d2 = XmNbottomAttachment;
+        w1 = XmNtopWidget;
+        w2 = XmNbottomWidget;
+        
+    } else {
+        d1 = XmNleftAttachment;
+        d2 = XmNrightAttachment;
+        w1 = XmNleftWidget;
+        w2 = XmNrightWidget;
+    }
+    
+    if(bc->prev_widget) {
+        int v = 0;
+        XtVaGetValues(bc->prev_widget, d2, &v, NULL);
+        if(v == XmATTACH_FORM) {
+            XtVaSetValues(
+                    bc->prev_widget,
+                    d2,
+                    XmATTACH_WIDGET,
+                    w2,
+                    widget,
+                    NULL);
+            XtVaSetValues(
+                    widget,
+                    d1,
+                    XmATTACH_NONE,
+                    d2,
+                    XmATTACH_FORM,
+                    NULL);
+        }
+    }
+    bc->prev_widget = widget;
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing) {
+    UiGridContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiGridContainer));
+    ct->container.widget = form;
+    ct->container.prepare = ui_grid_container_prepare;
+    ct->container.add = ui_grid_container_add;
+    ct->columnspacing = columnspacing;
+    ct->rowspacing = rowspacing;
+    ct->lines = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    return (UiContainer*)ct;
+}
+
+void ui_grid_newline(UiGridContainer *grid) {
+    if(grid->current) {
+        grid->current = NULL;
+    }
+    grid->container.layout.newline = FALSE;
+}
+
+Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    if(ct->layout.newline) {
+        ui_grid_newline(grid);
+    }
+    return ct->widget;
+}
+
+void ui_grid_container_add(UiContainer *ct, Widget widget) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(grid->current) {
+        cxListAdd(grid->current, widget);
+    } else {
+        grid->current = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        cxListAdd(grid->current, widget);
+        cxListAdd(grid->lines, grid->current);
+    }
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+static void ui_grid_resize(Widget widget, XtPointer udata, XtPointer cdata) {
+    UiGridContainer *grid = udata;
+    
+    CxList *rowdim = cxArrayListCreateSimple(sizeof(int), grid->lines->size);
+    int coldim[UI_GRID_MAX_COLUMNS];
+    memset(coldim, 0, UI_GRID_MAX_COLUMNS*sizeof(int));
+    int numcol = 0;
+    
+    // get the minimum size of the columns and rows
+    int sumw = 0;
+    int sumh = 0;
+    CxIterator lineIterator = cxListIterator(grid->lines);
+    cx_foreach(CxList *, row, lineIterator) {
+        int rheight = 0;
+        int i=0;
+        int sum_width = 0;
+        CxIterator colIterator = cxListIterator(row);
+        cx_foreach(Widget, w, colIterator) {
+            int widget_width = 0;
+            int widget_height = 0;
+            XtVaGetValues(
+                    w,
+                    XmNwidth,
+                    &widget_width,
+                    XmNheight,
+                    &widget_height, 
+                    NULL);
+            
+            // get the maximum height in this row
+            if(widget_height > rheight) {
+                rheight = widget_height;
+            }
+            
+            // get the maximum width in this column
+            if(widget_width > coldim[i]) {
+                coldim[i] = widget_width;
+            }
+            sum_width += widget_width;
+            if(sum_width > sumw) {
+                sumw = sum_width;
+            }
+            
+            i++;
+            if(i > numcol) {
+                numcol = i;
+            }
+        }
+        cxListAdd(rowdim, &rheight);
+        sumh += rheight;
+    }
+    
+    // check container size
+    int gwidth = 0;
+    int gheight = 0;
+    XtVaGetValues(widget, XmNwidth, &gwidth, XmNheight, &gheight, NULL);
+    if(gwidth < sumw || gheight < sumh) {
+        XtVaSetValues(widget, XmNwidth, sumw, XmNheight, sumh, NULL);
+        cxListDestroy(rowdim);
+        return;
+    }
+    
+    
+    // adjust the positions of all children
+    int y = 0;
+    lineIterator = cxListIterator(grid->lines);
+    cx_foreach(CxList *, row, lineIterator) {
+        int x = 0;       
+        int i=0;
+        int *rowheight = cxListAt(rowdim, lineIterator.index);
+        CxIterator colIterator = cxListIterator(row);
+        cx_foreach(Widget, w, colIterator) {
+            XtVaSetValues(
+                    w,
+                    XmNx, x,
+                    XmNy, y,
+                    XmNwidth, coldim[i],
+                    XmNheight, *rowheight,
+                    NULL);
+            
+            x += coldim[i];
+            i++;
+        }
+        y += *rowheight;
+    }
+    
+    cxListDestroy(rowdim);
+}
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = scrolledwindow;
+    ct->prepare = ui_scrolledwindow_container_prepare;
+    ct->add = ui_scrolledwindow_container_add;
+    return ct;
+}
+
+Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+    return ct->widget;
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget) {
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+UiContainer* ui_tabview_container(UiObject *obj, Widget frame) {
+    UiTabViewContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiTabViewContainer));
+    ct->context = obj->ctx;
+    ct->container.widget = frame;
+    ct->container.prepare = ui_tabview_container_prepare;
+    ct->container.add = ui_tabview_container_add;
+    ct->tabs = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
+    return (UiContainer*)ct;
+}
+
+Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+    int a = *n;
+    XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
+    XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
+    XtSetArg(args[a], XmNtopAttachment, XmATTACH_FORM); a++;
+    XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
+    *n = a;
+    return ct->widget;
+}
+
+void ui_tabview_container_add(UiContainer *ct, Widget widget) {
+    UiTabViewContainer *tabview = (UiTabViewContainer*)ct; 
+    
+    if(tabview->current) {
+        XtUnmanageChild(tabview->current);
+    }
+
+    tabview->current = widget;
+    cxListAdd(tabview->tabs, widget);
+    
+    ui_select_tab(ct->widget, 0);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UIWIDGET ui_box(UiObject *obj, int margin, int spacing, UiBoxOrientation orientation) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    Arg args[16];
+    int n = 0;
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget form = XmCreateForm(parent, "vbox", args, n);
+    ct->add(ct, form);
+    XtManageChild(form);
+    
+    UiObject *newobj = uic_object_new(obj, form);
+    newobj->container = ui_box_container(obj, form, margin, spacing, orientation);
+    uic_obj_add(obj, newobj);
+    
+    return form;
+}
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_box(obj, 0, 0, UI_BOX_VERTICAL);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_box(obj, 0, 0, UI_BOX_HORIZONTAL);
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+    return ui_box(obj, margin, spacing, UI_BOX_VERTICAL);
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+    return ui_box(obj, margin, spacing, UI_BOX_HORIZONTAL);
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    Arg args[16];
+    int n = 0;
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget grid = XmCreateDrawingArea(parent, "grid", args, n);
+    ct->add(ct, grid);
+    XtManageChild(grid);
+    
+    UiObject *newobj = uic_object_new(obj, grid);
+    newobj->container = ui_grid_container(obj, grid, columnspacing, rowspacing);
+    uic_obj_add(obj, newobj);
+    
+    XtAddCallback (grid, XmNresizeCallback , ui_grid_resize, newobj->container);
+    
+    return grid;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    Arg args[16];
+    int n = 0;
+    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); // TODO: dosn't work, use XmAPPLICATION_DEFINED
+    n++;
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", args, n);
+    ct->add(ct, scrolledwindow);
+    XtManageChild(scrolledwindow);
+    
+    UiObject *newobj = uic_object_new(obj, scrolledwindow);
+    newobj->container = ui_scrolledwindow_container(obj, scrolledwindow);
+    uic_obj_add(obj, newobj);
+    
+    return scrolledwindow;
+}
+
+UIWIDGET ui_sidebar(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    Arg args[16];
+    int n = 0;
+    
+    XtSetArg(args[n], XmNorientation, XmHORIZONTAL);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget pane = XmCreatePanedWindow(parent, "pane", args, n);
+    ct->add(ct, pane);
+    XtManageChild(pane);
+    
+    // add sidebar widget
+    Widget sidebar = XmCreateForm(pane, "sidebar", args, 0);
+    XtManageChild(sidebar);
+    
+    UiObject *left = uic_object_new(obj, sidebar);
+    left->container = ui_box_container(left, sidebar, 0, 0, UI_BOX_VERTICAL);
+    
+    // add content widget
+    XtSetArg (args[0], XmNpaneMaximum, 8000);
+    Widget content = XmCreateForm(pane, "content_area", args, 1);
+    XtManageChild(content);
+    
+    UiObject *right = uic_object_new(obj, content);
+    right->container = ui_box_container(right, content, 0, 0, UI_BOX_VERTICAL);
+    
+    uic_obj_add(obj, right);
+    uic_obj_add(obj, left);
+    
+    return sidebar;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    // create a simple frame as container widget
+    // when tabs are selected, the current child will be replaced by the
+    // the new tab widget
+    Arg args[16];
+    int n = 0;
+    XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_OUT);
+    n++;
+    XtSetArg(args[n], XmNshadowThickness, 0);
+    n++;
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget form = XmCreateForm(parent, "tabview", args, n);
+    ct->add(ct, form);
+    XtManageChild(form);
+    
+    UiObject *tabviewobj = uic_object_new(obj, form);
+    tabviewobj->container = ui_tabview_container(obj, form);
+    uic_obj_add(obj, tabviewobj);
+    
+    XtVaSetValues(form, XmNuserData, tabviewobj->container, NULL);
+    
+    return form;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.label = title;
+    
+    ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+    UiTabViewContainer *ct = NULL;
+    XtVaGetValues(tabview, XmNuserData, &ct, NULL);
+    if(ct) {
+        XtUnmanageChild(ct->current);
+        Widget w = cxListAt(ct->tabs, tab);
+        if(w) {
+            XtManageChild(w);
+            ct->current = w;
+        } else {
+            fprintf(stderr, "UiError: front tab index: %d\n", tab);
+        }
+    } else {
+        fprintf(stderr, "UiError: widget is not a tabview\n");
+    }
+}
+
+
+/* document tabview */
+
+static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
+    MotifTabbedPane *v = (MotifTabbedPane*)udata;
+    
+    int width = 0;
+    int height = 0;
+    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
+    int button_width = width / 4;
+    int x = 0;
+    CxIterator tabIterator = cxListIterator(v->tabs);
+    cx_foreach(UiTab*, tab, tabIterator) {
+        XtVaSetValues(
+                tab->tab_button,
+                XmNx, x,
+                XmNy, 0,
+                XmNwidth,
+                button_width,
+                
+                NULL);
+        x += button_width;
+    }
+    
+    if(height <= v->height) {
+        XtVaSetValues(widget, XmNheight, v->height + 4, NULL);
+    }
+}
+
+static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
+    MotifTabbedPane *v = (MotifTabbedPane*)udata;
+    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
+    XEvent *event = cbs->event;
+    Display *dpy = XtDisplay(widget); 
+    
+    XGCValues gcvals;
+    GC gc;
+    Pixel fgpix;
+    
+    int tab_x;
+    int tab_width;
+    XtVaGetValues(v->current->tab_button, XmNx, &tab_x, XmNwidth, &tab_width, XmNhighlightColor, &fgpix, NULL);
+    
+    gcvals.foreground = v->bg1;
+    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
+      
+    int width = 0;
+    int height = 0;
+    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
+    XFillRectangle(dpy, XtWindow(widget), gc, 0, 0, width, height);
+    
+    gcvals.foreground = fgpix;
+    gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
+    
+    XFillRectangle(dpy, XtWindow(widget), gc, tab_x, 0, tab_width, height);
+    
+}
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+    int n = 0;
+    Arg args[16];
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    
+    Widget tabview = XmCreateForm(parent, "tabview_form", args, n);
+    XtManageChild(tabview);
+    
+    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
+    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
+    XtSetArg(args[2], XmNspacing, 1);
+    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
+    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
+    XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
+    XtSetArg(args[6], XmNmarginWidth, 0);
+    XtSetArg(args[7], XmNmarginHeight, 0);
+    Widget tabbar = XmCreateDrawingArea(tabview, "tabbar", args, 8);
+    XtManageChild(tabbar);
+    
+    XtSetArg(args[0], XmNleftAttachment, XmATTACH_FORM);
+    XtSetArg(args[1], XmNrightAttachment, XmATTACH_FORM);
+    XtSetArg(args[2], XmNtopAttachment, XmATTACH_WIDGET);
+    XtSetArg(args[3], XmNtopWidget, tabbar);
+    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+    XtSetArg(args[5], XmNshadowThickness, 0);
+    Widget tabct = XmCreateForm(tabview, "tabview", args, 6);
+    XtManageChild(tabct);
+    
+    MotifTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(MotifTabbedPane));
+    tabbedpane->view.ctx = uic_current_obj(obj)->ctx;
+    tabbedpane->view.widget = tabct;
+    tabbedpane->view.document = NULL;
+    tabbedpane->tabbar = tabbar;
+    tabbedpane->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
+    tabbedpane->current = NULL;
+    tabbedpane->height = 0;
+    
+    XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabbedpane);
+    XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabbedpane);
+    
+    return &tabbedpane->view;
+}
+
+UiObject* ui_document_tab(UiTabbedPane *view) {
+    MotifTabbedPane *v = (MotifTabbedPane*)view;
+    int n = 0;
+    Arg args[16];
+    
+    // hide the current tab content
+    if(v->current) {
+        XtUnmanageChild(v->current->content->widget);
+    }
+    
+    UiTab *tab = ui_malloc(view->ctx, sizeof(UiTab));
+    
+    // create the new tab content
+    XtSetArg(args[0], XmNshadowThickness, 0);
+    XtSetArg(args[1], XmNleftAttachment, XmATTACH_FORM);
+    XtSetArg(args[2], XmNrightAttachment, XmATTACH_FORM);
+    XtSetArg(args[3], XmNtopAttachment, XmATTACH_FORM);
+    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+    XtSetArg(args[5], XmNuserData, tab);
+    Widget frame = XmCreateFrame(view->widget, "tab", args, 6);
+    XtManageChild(frame);
+    
+    UiObject *content = ui_malloc(view->ctx, sizeof(UiObject));
+    content->widget = NULL; // initialization for uic_context()
+    content->ctx = uic_context(content, view->ctx->allocator);
+    content->ctx->parent = view->ctx;
+    content->ctx->attach_document = uic_context_attach_document;
+    content->ctx->detach_document2 = uic_context_detach_document2;
+    content->widget = frame;
+    content->window = view->ctx->obj->window;
+    content->container = ui_frame_container(content, frame);
+    content->next = NULL;
+    
+    // add tab button
+    cxListAdd(v->tabs, tab);
+    
+    XmString label = XmStringCreateLocalized("tab");
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNshadowThickness, 0);
+    XtSetArg(args[2], XmNhighlightThickness, 0);
+    
+    Widget button = XmCreatePushButton(v->tabbar, "tab_button", args, 3);
+    tab->tabbedpane = v;
+    tab->content = content;
+    tab->tab_button = button; 
+    XtManageChild(button);
+    XtAddCallback(
+        button,
+        XmNactivateCallback,
+        (XtCallbackProc)ui_tab_button_callback,
+        tab);
+    
+    if(v->height == 0) {
+        XtVaGetValues(
+                button,
+                XmNarmColor,
+                &v->bg1,
+                XmNbackground,
+                &v->bg2,
+                XmNheight,
+                &v->height,
+                NULL);
+        v->height += 2; // border
+    }
+    
+    ui_change_tab(v, tab);
+    ui_tabbar_resize(v->tabbar, v, NULL);
+    
+    return content;
+}
+
+void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) {  
+    MotifTabbedPane *t = tab->tabbedpane;
+    if(t->current) {
+        XtUnmanageChild(t->current->content->widget);
+        XtVaSetValues(t->current->tab_button, XmNset, 0, NULL);
+    }
+    XtManageChild(tab->content->widget);
+    
+    ui_change_tab(t, tab);
+    
+}
+
+void ui_change_tab(MotifTabbedPane *pane, UiTab *tab) {
+    UiContext *ctx = tab->content->ctx;
+    ctx->parent->detach_document2(ctx->parent, pane->current->content->ctx->document);
+    ctx->parent->attach_document(ctx->parent, ctx->document);
+    
+    if(pane->current) {
+        XtVaSetValues(pane->current->tab_button, XmNshadowThickness, 0, XmNbackground, pane->bg1, NULL);
+    }
+    XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, pane->bg2, NULL);
+    
+    pane->current = tab;
+    pane->index = cxListFind(pane->tabs, tab);
+    printf("index: %d\n", pane->index);
+    
+    // redraw tabbar
+    Display *dpy = XtDisplay(pane->tabbar);
+    Window window = XtWindow(pane->tabbar);
+    if(dpy && window) {
+        XClearArea(dpy, XtWindow(pane->tabbar), 0, 0, 0, 0, TRUE);
+        XFlush(dpy);
+    }
+}
+
+void ui_tab_set_document(UiContext *ctx, void *document) {
+    if(ctx->parent->document) {
+        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+    }
+    uic_context_attach_document(ctx, document);
+    //uic_context_set_document(ctx->parent, document);
+    //ctx->parent->document = document;
+    
+    UiTab *tab = NULL;
+    XtVaGetValues(
+            ctx->obj->widget,
+            XmNuserData,
+            &tab,
+            NULL);
+    if(tab) {
+        if(tab->tabbedpane->current == tab) {
+            ctx->parent->attach_document(ctx->parent, ctx->document);
+        }
+    } else {
+        fprintf(stderr, "UiError: ui_bar_set_document: Cannot set document");
+    }
+}
+
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.vexpand = expand;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.newline = TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,160 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 CONTAINER_H
+#define	CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <cx/list.h>
+#include <string.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+    
+typedef struct MotifTabbedPane    MotifTabbedPane;
+typedef struct UiTab              UiTab;
+typedef struct UiBoxContainer     UiBoxContainer;
+typedef struct UiGridContainer    UiGridContainer;
+typedef struct UiTabViewContainer UiTabViewContainer;
+typedef struct UiLayout           UiLayout;
+
+typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool);
+
+typedef enum UiLayoutBool     UiLayoutBool;
+typedef enum UiBoxOrientation UiBoxOrientation;
+
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+enum UiBoxOrientation {
+    UI_BOX_VERTICAL = 0,
+    UI_BOX_HORIZONTAL
+};
+
+struct UiLayout {
+    UiLayoutBool fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    int          gridwidth;
+};
+
+struct UiContainer {
+    Widget   widget;
+    Widget   (*prepare)(UiContainer*, Arg *, int*, UiBool);
+    void     (*add)(UiContainer*, Widget);
+    UiLayout layout;
+    Widget   current;
+    Widget   menu;
+};
+
+struct UiBoxContainer {
+    UiContainer container;
+    Widget      prev_widget;
+    UiBool      has_fill;
+    UiBoxOrientation orientation;
+    int         margin;
+    int         spacing;
+};
+
+struct UiGridContainer {
+    UiContainer container;
+    CxList      *lines;
+    CxList      *current;
+    int         columnspacing;
+    int         rowspacing;
+};
+
+struct UiTabViewContainer {
+    UiContainer container;
+    UiContext   *context;
+    Widget      widget;
+    CxList      *tabs;
+    Widget      current;
+};
+
+struct MotifTabbedPane {
+    UiTabbedPane view;
+    Widget       tabbar;
+    CxList       *tabs;
+    UiTab        *current;
+    int          index;
+    Pixel        bg1;
+    Pixel        bg2;
+    int          height;
+};
+
+struct UiTab {
+    MotifTabbedPane *tabbedpane;
+    UiObject        *content;
+    Widget          tab_button;
+};
+    
+
+UiContainer* ui_frame_container(UiObject *obj, Widget frame);
+Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_frame_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation);
+Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_box_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing);
+Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_grid_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow);
+Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_tabview_container(UiObject *obj, Widget rowcolumn);
+Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_tabview_container_add(UiContainer *ct, Widget widget);
+
+void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d);
+void ui_change_tab(MotifTabbedPane *pane, UiTab *tab);
+
+void ui_tab_set_document(UiContext *ctx, void *document);
+void ui_tab_detach_document(UiContext *ctx);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* CONTAINER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/dnd.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,45 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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 "dnd.h"
+
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+    
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+    
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+    return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/dnd.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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 DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/graphics.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,283 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+
+#include "graphics.h"
+
+#include "container.h"
+
+static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) {
+    UiDrawEvent *drawevent = u;
+    //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c;
+    //XEvent *event = cbs->event;
+    Display *dpy = XtDisplay(widget);
+    
+    UiEvent ev;
+    ev.obj = drawevent->obj;
+    ev.window = drawevent->obj->window;
+    ev.document = drawevent->obj->ctx->document;
+    ev.eventdata = NULL;
+    ev.intval = 0;
+    
+    XtVaGetValues(
+            widget,
+            XmNwidth,
+            &drawevent->gr.g.width,
+            XmNheight,
+            &drawevent->gr.g.height,
+            NULL);
+    
+    XGCValues gcvals;
+    gcvals.foreground = BlackPixelOfScreen(XtScreen(widget));
+    drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals);
+    
+    drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata);
+}
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    int n = 0;
+    Arg args[16];
+    
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n);
+    
+    if(f) {
+        UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = userdata;
+        
+        event->gr.display = XtDisplay(drawingarea);
+        event->gr.widget = drawingarea;
+        
+        Colormap colormap;
+        XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL);    
+        event->gr.colormap = colormap;
+        
+        XtAddCallback(
+                drawingarea,
+                XmNexposeCallback,
+                ui_drawingarea_expose,
+                event);
+        
+        XtVaSetValues(drawingarea, XmNuserData, event, NULL);
+    }
+    
+    XtManageChild(drawingarea);
+    return drawingarea;
+}
+
+static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) {
+    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
+    XEvent *xevent = cbs->event;
+    UiMouseEventData *event = u;
+    
+    if (cbs->reason == XmCR_INPUT) {
+        if (xevent->xany.type == ButtonPress) {
+            UiMouseEvent me;
+            me.x = xevent->xbutton.x;
+            me.y = xevent->xbutton.y;
+            // TODO: configurable double click time
+            me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2;
+            
+            UiEvent e;
+            e.obj = event->obj;
+            e.window = event->obj->window;
+            e.document = event->obj->ctx->document;
+            e.eventdata = &me;
+            e.intval = 0;
+            event->callback(&e, event->userdata);
+            
+            
+            event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time;
+        }
+    }
+    
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+    if(f) {
+        UiMouseEventData *event = malloc(sizeof(UiMouseEventData));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = u;
+        event->last_event = 0;
+        
+        XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event);
+    }
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+    XtVaGetValues(
+            drawingarea,
+            XmNwidth,
+            width,
+            XmNheight,
+            height,
+            NULL);
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+    //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True);
+    UiDrawEvent *event;
+    XtVaGetValues(drawingarea, XmNuserData, &event, NULL);
+    ui_drawingarea_expose(drawingarea, event, NULL);
+}
+
+
+/* -------------------- text layout functions -------------------- */
+UiTextLayout* ui_text(UiGraphics *g) {
+    UiTextLayout *text = malloc(sizeof(UiTextLayout));
+    memset(text, 0, sizeof(UiTextLayout));
+    text->text = NULL;
+    text->length = 0;
+    text->widget = ((UiXlibGraphics*)g)->widget;
+    text->fontset = NULL;
+    return text;
+}
+
+static void create_default_fontset(UiTextLayout *layout) {
+    char **missing = NULL;
+    int num_missing = 0;
+    char *def = NULL;
+    Display *dpy = XtDisplay(layout->widget);
+    XFontSet fs = XCreateFontSet(
+        dpy,
+        "-dt-interface system-medium-r-normal-s*utf*:,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u,"
+                "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni,"
+                "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208",
+        &missing, &num_missing, &def);
+    layout->fontset = fs;
+}
+
+void ui_text_free(UiTextLayout *text) {
+    // TODO
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+    ui_text_setstringl(layout, str, strlen(str));
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+    layout->text = str;
+    layout->length = len;
+    layout->changed = 1;
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+    create_default_fontset(layout);//TODO
+    layout->changed = 1;
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+    if(layout->changed) {
+        XRectangle ext, lext;
+        XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext);
+        layout->width = ext.width;
+        layout->height = ext.height;
+        layout->changed = 0;
+    }
+    *width = layout->width;
+    *height = layout->height;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+    layout->maxwidth = width;
+}
+
+
+/* -------------------- drawing functions -------------------- */
+
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+    UiXlibGraphics *gr = (UiXlibGraphics*)g;
+    XColor color;
+    color.flags= DoRed | DoGreen | DoBlue; 
+    color.red = red * 257;
+    color.green = green * 257;
+    color.blue = blue * 257;
+    XAllocColor(gr->display, gr->colormap, &color);
+    XSetForeground(gr->display, gr->gc, color.pixel);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+    UiXlibGraphics *gr = (UiXlibGraphics*)g;
+    XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    UiXlibGraphics *gr = (UiXlibGraphics*)g;
+    if(fill) {
+        XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+    } else {
+        XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+    }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+    UiXlibGraphics *gr = (UiXlibGraphics*)g;
+    int width, height;
+    ui_text_getsize(text, &width, &height);
+    if(text->maxwidth > 0) {
+        XRectangle clip;
+        clip.x = x;
+        clip.y = y;
+        clip.width = text->maxwidth;
+        clip.height = height;
+        XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted);
+    }
+    
+    XmbDrawString(
+            gr->display,
+            XtWindow(gr->widget),
+            text->fontset,
+            gr->gc,
+            x,
+            y + height,
+            text->text,
+            text->length);
+    
+    XSetClipMask(gr->display, gr->gc, None);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifndef GRAPHICS_H
+#define	GRAPHICS_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiXlibGraphics {
+    UiGraphics g;
+    Display    *display;
+    Widget     widget;
+    Colormap   colormap;
+    GC         gc;
+} UiXlibGraphics;
+
+typedef struct UiDrawEvent {
+    ui_drawfunc    callback;
+    UiObject       *obj;
+    void           *userdata;
+    UiXlibGraphics gr;
+} UiDrawEvent;
+
+typedef struct UiMouseEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    Time        last_event;
+} UiMouseEventData;
+
+struct UiTextLayout {
+    char     *text;
+    size_t   length;
+    Widget   widget;
+    XFontSet fontset;
+    int      maxwidth;
+    int      width;
+    int      height;
+    int      changed;
+};
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* GRAPHICS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/image.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,40 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+#include "image.h"
+
+UiIcon* ui_icon(const char *name, int size) {
+    return NULL;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+    return NULL;
+}
+
+void ui_free_icon(UiIcon *icon) {
+    
+}
+
+UiImage* ui_icon_image(UiIcon *icon) {
+    return NULL;
+}
+
+UiImage* ui_image(const char *filename) {
+    return NULL;
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+    return NULL;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+    return NULL;
+}
+
+void ui_free_image(UiImage *img) {
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/image.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,31 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/* 
+ * File:   image.h
+ * Author: olaf
+ *
+ * Created on 1. Juli 2018, 19:01
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/label.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,69 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "label.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_label(UiObject *obj, char *label) {
+    UiContainer *ct = uic_get_current_container(obj);
+    XmString str = XmStringCreateLocalized(label);
+    
+    int n = 0;
+    Arg args[16]; 
+    XtSetArg(args[n], XmNlabelString, str);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget widget = XmCreateLabel(parent, "label", args, n);
+    ct->add(ct, widget);  
+    XtManageChild(widget);
+    
+    return widget;
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    XmString str = XmStringCreateLocalized("");
+    
+    int n = 0;
+    Arg args[16]; 
+    XtSetArg(args[n], XmNlabelString, str);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget widget = XmCreateLabel(parent, "space_label", args, n);
+    ct->add(ct, widget);  
+    XtManageChild(widget);
+    
+    return widget;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/label.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 LABEL_H
+#define	LABEL_H
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* LABEL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/list.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,208 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "container.h"
+
+#include "list.h"
+#include "../common/object.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
+}
+
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    int count;
+    XmStringTable items = ui_create_stringlist(var->value, getvalue, &count);
+    
+    Arg args[8];
+    int n = 0;
+    XtSetArg(args[n], XmNitemCount, count);
+    n++;
+    XtSetArg(args[n], XmNitems, count == 0 ? NULL : items);
+    n++;
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget widget = XmCreateScrolledList(parent, "listview", args, n);
+    ct->add(ct, XtParent(widget));
+    XtManageChild(widget);
+    
+    UiListView *listview = cxMalloc(obj->ctx->allocator, sizeof(UiListView));
+    listview->widget = widget;
+    listview->list = var;
+    listview->getvalue = getvalue;
+    
+    for (int i=0;i<count;i++) {
+        XmStringFree(items[i]);
+    }
+    XtFree((char *)items);
+    
+    if(f) {
+        UiListViewEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiListViewEventData));
+        event->event.obj = obj;
+        event->event.userdata = udata;
+        event->event.callback = f;
+        event->event.value = 0;
+        event->var = var;
+        XtAddCallback(
+                widget,
+                XmNdefaultActionCallback,
+                (XtCallbackProc)ui_list_selection_callback,
+                event);
+    }
+    
+    return widget;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_listview_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        UiListVar *value = var->value;
+        return ui_listview_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+
+XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) { 
+    int num = list->count(list);
+    XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString));
+    void *data = list->first(list);
+    for(int i=0;i<num;i++) {
+        items[i] = XmStringCreateLocalized(getvalue(data, 0));
+        data = list->next(list);
+    }
+    
+    *count = num;
+    return items;
+}
+
+
+void ui_listview_update(UiEvent *event, UiListView *view) {
+    int count;
+    XmStringTable items = ui_create_stringlist(
+            view->list->value,
+            view->getvalue,
+            &count);
+    
+    XtVaSetValues(
+            view->widget,
+            XmNitems, count == 0 ? NULL : items,
+            XmNitemCount,
+            count,
+            NULL);
+    
+    for (int i=0;i<count;i++) {
+        XmStringFree(items[i]);
+    }
+    XtFree((char *)items);
+}
+
+void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data) {
+    XmListCallbackStruct *cbs = (XmListCallbackStruct *)data;
+    
+    UiEvent e;
+    e.obj = event->event.obj;
+    e.window = event->event.obj->window;
+    e.document = event->event.obj->ctx->document;
+    UiList *list = event->var->value;
+    e.eventdata = list->get(list, cbs->item_position - 1);
+    e.intval = cbs->item_position - 1;
+    event->event.callback(&e, event->event.userdata);
+}
+
+
+/* --------------------------- ComboBox ---------------------------  */
+
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_combobox_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        UiListVar *value = var->value;
+        return ui_combobox_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiListView *listview = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiListView));
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    Arg args[16];
+    int n = 0;
+    XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_NONE);
+    n++;
+    XtSetArg(args[n], XmNtraversalOn, FALSE);
+    n++;
+    XtSetArg(args[n], XmNwidth, 160);
+    n++;
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget combobox = XmCreateDropDownList(parent, "combobox", args, n);
+    XtManageChild(combobox);
+    listview->widget = combobox;
+    listview->list = var;
+    listview->getvalue = getvalue;
+    
+    ui_listview_update(NULL, listview);
+    
+    return parent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/list.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 LIST_H
+#define	LIST_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    Widget          widget;
+    UiVar           *list;
+    ui_getvaluefunc getvalue;
+} UiListView;
+
+typedef struct UiListViewEventData {
+    UiEventData event;
+    UiVar *var;
+} UiListViewEventData;
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count);
+void ui_listview_update(UiEvent *event, UiListView *view);
+void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* LIST_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/menu.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,484 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <stdarg.h>
+
+#include "menu.h"
+#include "button.h"
+#include "toolkit.h"
+#include "stock.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../ui/window.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_SUBMENU         */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_STOCK_ITEM      */ add_menuitem_st_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_CHECK_ITEM_NV   */ add_checkitemnv_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_ITEM_LIST_NV    */ NULL, // TODO
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+// private menu functions
+void ui_create_menubar(UiObject *obj) {
+    UiMenu *menus = uic_get_menu_list();
+    if(!menus) {
+        return;
+    }
+    
+    Widget menubar = XmCreateMenuBar(obj->widget, "main_list", NULL, 0);
+    XtManageChild(menubar);
+    
+    UiMenu *menu = menus;
+    int menu_index = 0;
+    while(menu) {
+        menu_index += add_menu_widget(menubar, menu_index, &menu->item, obj);
+        
+        menu = (UiMenu*)menu->item.next;
+    }
+}
+
+int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *menu = (UiMenu*)item;
+    
+    Widget menuItem = XtVaCreateManagedWidget(
+            menu->label,
+            xmCascadeButtonWidgetClass,
+            parent,
+            NULL);
+    Widget m = XmVaCreateSimplePulldownMenu(parent, menu->label, i, NULL, NULL);
+    
+    UiMenuItemI *mi = menu->items_begin;
+    int menu_index = 0;
+    while(mi) {
+        menu_index += createMenuItem[mi->type](m, menu_index, mi, obj);
+        mi = mi->next;
+    }
+    
+    return 1;
+}
+
+int add_menuitem_widget(
+        Widget parent,
+        int i,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiMenuItem *mi = (UiMenuItem*)item;
+    
+    Arg args[1];
+    XmString label = XmStringCreateLocalized(mi->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    
+    Widget mitem = XtCreateManagedWidget(
+            "menubutton",
+            xmPushButtonWidgetClass,
+            parent,
+            args,
+            1);
+    XmStringFree(label);
+    
+    if(mi->callback != NULL) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = mi->userdata;
+        event->callback = mi->callback;
+        event->value = 0;
+        XtAddCallback(
+                mitem,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+    }
+    
+    if(mi->groups) {
+        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
+    }
+    
+    return 1;
+}
+
+int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiStMenuItem *mi = (UiStMenuItem*)item;
+    
+    UiStockItem *si = ui_get_stock_item(mi->stockid);
+    if(!si) {
+        fprintf(stderr, "UI Error: unknown stock id: %s\n", mi->stockid);
+        return 0;
+    }
+    
+    int n = 0;
+    Arg args[4];
+    XmString label = XmStringCreateLocalized(si->label);
+    XmString at = NULL;
+    
+    XtSetArg(args[n], XmNlabelString, label);
+    n++;
+    if(si->accelerator) {
+        XtSetArg(args[n], XmNaccelerator, si->accelerator);
+        n++;
+    }
+    if(si->accelerator_label) {
+        at = XmStringCreateLocalized(si->accelerator_label);
+        XtSetArg(args[n], XmNacceleratorText, at);
+        n++;
+    }
+    
+    Widget mitem = XtCreateManagedWidget(
+            "menubutton",
+            xmPushButtonWidgetClass,
+            parent,
+            args,
+            n);
+    XmStringFree(label);
+    if(at) {
+        XmStringFree(at);
+    }
+    
+    if(mi->callback != NULL) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = mi->userdata;
+        event->callback = mi->callback;
+        event->value = 0;
+        XtAddCallback(
+                mitem,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+    }
+    
+    if(mi->groups) {
+        uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
+    }
+    
+    return 1;
+}
+
+int add_menuseparator_widget(
+        Widget parent,
+        int i,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    Widget s = XmCreateSeparatorGadget (parent, "menu_separator", NULL, 0);
+    XtManageChild(s);
+    return 1;
+}
+
+int add_checkitem_widget(
+        Widget parent,
+        int i,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiCheckItem *ci = (UiCheckItem*)item;
+    
+    Arg args[3];
+    XmString label = XmStringCreateLocalized(ci->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNvisibleWhenOff, 1);
+    Widget checkbox = XtCreateManagedWidget(
+            "menutogglebutton",
+            xmToggleButtonWidgetClass,
+            parent,
+            args,
+            2);
+    XmStringFree(label);
+    
+    if(ci->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = ci->userdata;
+        event->callback = ci->callback;
+        XtAddCallback(
+            checkbox,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)ui_toggle_button_callback,
+            event);
+    }
+    
+    return 1;
+}
+
+int add_checkitemnv_widget(
+        Widget parent,
+        int i,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiCheckItemNV *ci = (UiCheckItemNV*)item;
+    
+    Arg args[3];
+    XmString label = XmStringCreateLocalized(ci->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNvisibleWhenOff, 1);
+    Widget checkbox = XtCreateManagedWidget(
+            "menutogglebutton",
+            xmToggleButtonWidgetClass,
+            parent,
+            args,
+            2);
+    XmStringFree(label);
+    
+    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = checkbox;
+        value->get = ui_toggle_button_get;
+        value->set = ui_toggle_button_set;
+        value = 0;
+    } else {
+        // TODO: error
+    }
+    
+    return 1;
+}
+
+int add_menuitem_list_widget(
+        Widget parent,
+        int i,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiMenuItemList *il = (UiMenuItemList*)item;
+    
+    UiActiveMenuItemList *ls = cxMalloc(
+            obj->ctx->allocator,
+            sizeof(UiActiveMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = parent;
+    ls->index = i;
+    ls->oldcount = 0;
+    ls->list = il->list;
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    ls->list->observers = ui_add_observer(
+            ls->list->observers,
+            (ui_callback)ui_update_menuitem_list,
+            ls);
+    
+    ui_update_menuitem_list(NULL, ls);
+    
+    return 0;
+}
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
+    Arg args[4];
+    
+    // remove old items
+    if(list->oldcount > 0) {
+        Widget *children;
+        int nc;
+        
+        XtVaGetValues(
+                list->menu,
+                XmNchildren,
+                &children,
+                XmNnumChildren,
+                &nc,
+                NULL);
+        
+        for(int i=0;i<list->oldcount;i++) {
+            XtDestroyWidget(children[list->index + i]);
+        }
+    }
+    
+    char *str = ui_list_first(list->list);
+    if(str) {
+        // add separator
+        XtSetArg(args[0], XmNpositionIndex, list->index);
+        Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1);
+        XtManageChild(s);
+    }
+    int i = 1;
+    while(str) {
+        XmString label = XmStringCreateLocalized(str);
+        XtSetArg(args[0], XmNlabelString, label);
+        XtSetArg(args[1], XmNpositionIndex, list->index + i);
+
+        Widget mitem = XtCreateManagedWidget(
+                "menubutton",
+                xmPushButtonWidgetClass,
+                list->menu,
+                args,
+                2);
+        XmStringFree(label);
+        
+        if(list->callback) {
+            // TODO: use mempool
+            UiEventData *event = malloc(sizeof(UiEventData));
+            event->obj = list->object;
+            event->userdata = list->userdata;
+            event->callback = list->callback;
+            event->value = i - 1;
+
+            XtAddCallback(
+                mitem,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+        }
+        
+        str = ui_list_next(list->list);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
+
+void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) {
+    UiEventData *event = udata;
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.intval = 0;
+    event->callback(&e, event->userdata);    
+}
+
+
+/*
+ * widget menu functions
+ */
+
+static void ui_popup_handler(Widget widget, XtPointer data,  XEvent *event, Boolean *c) {
+    Widget menu = data;
+    XmMenuPosition(menu, (XButtonPressedEvent *)event);
+    XtManageChild(menu);
+    
+    *c = FALSE;
+}
+
+UIMENU ui_contextmenu(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(ct->current) {
+        return ui_contextmenu_w(obj, ct->current);
+    } else {
+        return NULL; // TODO: warn
+    }
+}
+
+UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0);
+    ct->menu = menu;
+    
+    XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu);
+    
+    return menu;
+}
+
+void ui_contextmenu_popup(UIMENU menu) {
+    
+}
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
+    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+}
+
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    CxList *groups = NULL;
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
+    }
+    va_end(ap);
+    
+    // create menuitem
+    Arg args[4];
+    XmString labelstr = XmStringCreateLocalized(label);
+    XtSetArg(args[0], XmNlabelString, labelstr);
+    
+    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
+    XtManageChild(item);
+    XmStringFree(labelstr);
+}
+
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
+    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+}
+
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    CxList *groups = NULL;
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
+    }
+    va_end(ap);
+    
+    // create menuitem
+    UiStockItem *stockItem = ui_get_stock_item(stockid);
+    Arg args[4];
+    XmString labelstr = XmStringCreateLocalized(stockItem->label);
+    XtSetArg(args[0], XmNlabelString, labelstr);
+    
+    Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
+    XtManageChild(item);
+    XmStringFree(labelstr);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,73 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 MENU_H
+#define	MENU_H
+
+#include "../ui/menu.h"
+#include "../common/menu.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
+
+struct UiActiveMenuItemList {
+    UiObject     *object;
+    Widget       menu;
+    int          index;
+    int          oldcount;
+    UiList       *list;
+    ui_callback  callback;
+    void         *userdata;
+};
+
+void ui_create_menubar(UiObject *obj);
+
+int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuseparator_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_checkitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_checkitemnv_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_list_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,49 @@
+#
+# 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.
+#
+
+MOTIF_SRC_DIR = ui/motif/
+MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR)
+
+MOTIFOBJ = toolkit.o
+MOTIFOBJ += stock.o
+MOTIFOBJ += window.o
+MOTIFOBJ += container.o
+MOTIFOBJ += menu.o
+MOTIFOBJ += toolbar.o
+MOTIFOBJ += button.o
+MOTIFOBJ += label.o
+MOTIFOBJ += text.o
+MOTIFOBJ += list.o
+MOTIFOBJ += tree.o
+MOTIFOBJ += graphics.o
+MOTIFOBJ += range.o
+MOTIFOBJ += dnd.o
+MOTIFOBJ += image.o
+
+TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
+TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/range.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,137 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 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 "range.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    int n = 0;
+    Arg args[16];
+    XtSetArg(args[n], XmNorientation, orientation == UI_HORIZONTAL ? XmHORIZONTAL : XmVERTICAL);
+    n++;
+    XtSetArg (args[n], XmNmaximum, 10);
+    n++;
+    XtSetArg (args[n], XmNsliderSize, 1);
+    n++;
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget scrollbar = XmCreateScrollBar(parent, "scrollbar", args, n);
+    XtManageChild(scrollbar);
+    ct->add(ct, scrollbar);
+    
+    if(range) {
+        range->get = ui_scrollbar_get;
+        range->set = ui_scrollbar_set;
+        range->setrange = ui_scrollbar_setrange;
+        range->setextent = ui_scrollbar_setextent;
+        range->obj = scrollbar;
+    }
+    
+    if(f) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+        XtAddCallback(
+                scrollbar,
+                XmNvalueChangedCallback,
+                (XtCallbackProc)ui_scrollbar_callback,
+                event);
+    }
+    
+    return scrollbar;
+}
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
+}
+
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
+}
+
+void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.intval = event->value;
+    event->callback(&e, event->userdata);
+}
+
+double ui_scrollbar_get(UiRange *range) {
+    int intval;
+    XtVaGetValues(
+            range->obj,
+            XmNvalue,
+            &intval,
+            NULL);
+    double value = (double)intval / 10;
+    range->value = value;
+    return value;
+}
+
+void   ui_scrollbar_set(UiRange *range, double value) {
+    XtVaSetValues(
+            range->obj,
+            XmNvalue,
+            (int)(value * 10),
+            NULL);
+    range->value = value;
+}
+
+void   ui_scrollbar_setrange(UiRange *range, double min, double max) {
+    XtVaSetValues(
+            range->obj,
+            XmNminimum,
+            (int)(min * 10),
+            XmNmaximum,
+            (int)(max * 10),
+            NULL);
+    range->min = min;
+    range->max = max;
+}
+
+void   ui_scrollbar_setextent(UiRange *range, double extent) {
+    XtVaSetValues(
+            range->obj,
+            XmNsliderSize,
+            (int)(extent * 10),
+            NULL);
+    range->extent = extent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/range.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 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 RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata);
+double ui_scrollbar_get(UiRange *range);
+void   ui_scrollbar_set(UiRange *range, double value);
+void   ui_scrollbar_setrange(UiRange *range, double min, double max);
+void   ui_scrollbar_setextent(UiRange *range, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/stock.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,76 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "stock.h"
+#include "../ui/properties.h"
+#include <cx/hash_map.h>
+
+static CxMap *stock_items;
+
+void ui_stock_init() {
+    stock_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64);
+    
+    ui_add_stock_item(UI_STOCK_NEW, "New", "Ctrl<Key>N", "Ctrl+N", NULL);
+    ui_add_stock_item(UI_STOCK_OPEN, "Open", "Ctrl<Key>O", "Ctrl+O", NULL);
+    ui_add_stock_item(UI_STOCK_SAVE, "Save", "Ctrl<Key>S", "Ctrl+S", NULL);
+    ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", NULL, NULL, NULL);
+    ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", NULL, NULL, NULL);
+    ui_add_stock_item(UI_STOCK_CLOSE, "Close", "Ctrl<Key>W", "Ctrl+W", NULL);
+    ui_add_stock_item(UI_STOCK_UNDO, "Undo", "Ctrl<Key>Z", "Ctrl+Z", NULL);
+    ui_add_stock_item(UI_STOCK_REDO, "Redo", NULL, NULL, NULL);
+    ui_add_stock_item(UI_STOCK_GO_BACK, "Back", NULL, NULL, NULL);
+    ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", NULL, NULL, NULL);
+    ui_add_stock_item(UI_STOCK_CUT, "Cut", "Ctrl<Key>X", "Ctrl+X", NULL);
+    ui_add_stock_item(UI_STOCK_COPY, "Copy", "Ctrl<Key>C", "Ctrl+C", NULL);
+    ui_add_stock_item(UI_STOCK_PASTE, "Paste", "Ctrl<Key>V", "Ctrl+V", NULL);
+    ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL);
+}
+
+void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) {
+    UiStockItem *i = malloc(sizeof(UiStockItem));
+    i->label = label;
+    i->accelerator = accelerator;
+    i->accelerator_label = accelerator_label;
+    // TODO: icon
+    
+    cxMapPut(stock_items, id, i);
+}
+
+UiStockItem* ui_get_stock_item(char *id) {
+    UiStockItem *item = cxMapGet(stock_items, id);
+    if(item) {
+        char *label = uistr_n(id);
+        if(label) {
+            item->label = label;
+        }
+    }
+    return item;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/stock.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,56 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 STOCK_H
+#define	STOCK_H
+
+#include "../ui/stock.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiStockItem {
+    char *label;
+    char *accelerator;
+    char *accelerator_label;
+    // TODO: icon
+} UiStockItem;
+    
+void ui_stock_init();
+
+void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon);
+
+UiStockItem* ui_get_stock_item(char *id);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* STOCK_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/text.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,507 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "text.h"
+#include "container.h"
+
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+    UiContainer *ct = uic_get_current_container(obj);
+    int n = 0;
+    Arg args[16];
+    
+    //XtSetArg(args[n], XmNeditable, TRUE);
+    //n++;
+    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
+    ct->add(ct, XtParent(text_area));
+    XtManageChild(text_area);
+    
+    UiTextArea *uitext = cxMalloc(
+            obj->ctx->allocator,
+            sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->last_selection_state = 0;
+    XtAddCallback(
+                text_area,
+                XmNmotionVerifyCallback,
+                (XtCallbackProc)ui_text_selection_callback,
+                uitext);
+    
+    // bind value
+    if(var->value) {
+        UiText *value = var->value;
+        if(value->value.ptr) {
+            XmTextSetString(text_area, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textarea_set;
+        value->get = ui_textarea_get;
+        value->getsubstr = ui_textarea_getsubstr;
+        value->insert = ui_textarea_insert;
+        value->setposition = ui_textarea_setposition;
+        value->position = ui_textarea_position;
+        value->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->value.ptr = NULL;
+        value->obj = text_area;
+        
+        if(!value->undomgr) {
+            value->undomgr = ui_create_undomgr();
+        }
+        
+        XtAddCallback(
+                text_area,
+                XmNmodifyVerifyCallback,
+                (XtCallbackProc)ui_text_modify_callback,
+                var);
+    }
+    
+    return text_area;
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+    if(var) {
+        return ui_textarea_var(obj, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    char *str = XmTextGetString(text->obj);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    XmTextSetString(text->obj, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    int length = end - begin;
+    char *str = XtMalloc(length + 1);
+    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    text->value.ptr = NULL;
+    XmTextInsert(text->obj, pos, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+    long begin;
+    long end;
+    XmTextGetSelectionPosition(text->obj, &begin, &end);
+    text->pos = begin;
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+    return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+void ui_text_set(UiText *text, char *str) {
+    if(text->set) {
+        text->set(text, str);
+    } else {
+        if(text->value.ptr) {
+            text->value.free(text->value.ptr);
+        }
+        text->value.ptr = XtNewString(str);
+        text->value.free = (ui_freefunc)XtFree;
+    }
+}
+
+char* ui_text_get(UiText *text) {
+    if(text->get) {
+        return text->get(text);
+    } else {
+        return text->value.ptr;
+    }
+}
+
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->end = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+    UiTextBufOp *op = mgr->begin;
+    while(op) {
+        UiTextBufOp *nextOp = op->next;
+        if(op->text) {
+            free(op->text);
+        }
+        free(op);
+        op = nextOp;
+    }
+    free(mgr);
+}
+
+void ui_text_selection_callback(
+        Widget widget,
+        UiTextArea *textarea,
+        XtPointer data)
+{
+    long left = 0;
+    long right = 0;
+    XmTextGetSelectionPosition(widget, &left, &right);
+    int sel = left < right ? 1 : 0;
+    if(sel != textarea->last_selection_state) {
+        if(sel) {
+            ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
+        } else {
+            ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
+        }
+    }
+    textarea->last_selection_state = sel;
+}
+
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
+    UiText *value = var->value;
+    if(!value->obj) {
+        // TODO: bug, fix
+        return;
+    }
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    
+    XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
+    int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    char *text = txv->text->ptr;
+    int length = txv->text->length;
+    
+    if(mgr->cur) {
+        UiTextBufOp *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
+            while(elm) {
+                elm->prev = NULL;   
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
+                elm = next;
+            }
+        }
+        
+        UiTextBufOp *last_op = mgr->cur;
+        if(
+            last_op->type == UI_TEXTBUF_INSERT &&
+            ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+        {
+            // append text to last op       
+            int ln = last_op->len;
+            char *newtext = malloc(ln + length + 1);
+            memcpy(newtext, last_op->text, ln);
+            memcpy(newtext+ln, text, length);
+            newtext[ln+length] = '\0';
+            
+            last_op->text = newtext;
+            last_op->len = ln + length;
+            last_op->end += length;
+            
+            return;
+        }
+    }
+    
+    char *str;
+    if(type == UI_TEXTBUF_INSERT) {
+        str = malloc(length + 1);
+        memcpy(str, text, length);
+        str[length] = 0;
+    } else {
+        length = txv->endPos - txv->startPos;
+        str = malloc(length + 1);
+        XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
+    }
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
+    op->type = type;
+    op->start = txv->startPos;
+    op->end = txv->endPos + 1;
+    op->len = length;
+    op->text = str;
+    
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+    // return 1 if oldstr + newstr are one word
+    
+    int has_space = 0;
+    for(int i=0;i<oldlen;i++) {
+        if(oldstr[i] < 33) {
+            has_space = 1;
+            break;
+        }
+    }
+    
+    for(int i=0;i<newlen;i++) {
+        if(has_space && newstr[i] > 32) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+    if(op->text) {
+        free(op->text);
+    }
+    free(op);
+}
+
+
+void ui_text_undo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    if(mgr->cur) {
+        UiTextBufOp *op = mgr->cur;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextInsert(value->obj, op->start, op->text);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = mgr->cur->prev;
+    }
+}
+
+void ui_text_redo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    UiTextBufOp *elm = NULL;
+    if(mgr->cur) {
+        if(mgr->cur->next) {
+            elm = mgr->cur->next;
+        }
+    } else if(mgr->begin) {
+        elm = mgr->begin;
+    }
+    
+    if(elm) {
+        UiTextBufOp *op = elm;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                XmTextInsert(value->obj, op->start, op->text);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+/* ------------------------- textfield ------------------------- */
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiContainer *ct = uic_get_current_container(obj);
+    int n = 0;
+    Arg args[16];
+    XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
+    n++;
+    if(width > 0) {
+        XtSetArg(args[n], XmNcolumns, width / 2 + 1);
+        n++;
+    }
+    if(frameless) {
+        XtSetArg(args[n], XmNshadowThickness, 0);
+        n++;
+    }
+    if(password) {
+        // TODO
+    }
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget textfield = XmCreateText(parent, "text_field", args, n);
+    ct->add(ct, textfield);
+    XtManageChild(textfield);
+    
+    // bind value
+    if(value) {
+        if(value->value.ptr) {
+            XmTextSetString(textfield, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textfield_set;
+        value->get = ui_textfield_get;
+        value->value.ptr = NULL;
+        value->obj = textfield;
+    }
+    
+    return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+    if(var) {
+        UiString *value = var->value;
+        return ui_textfield(obj, value);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    char *value = XmTextGetString(str->obj);
+    str->value.ptr = value;
+    str->value.free = (ui_freefunc)XtFree;
+    return value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    XmTextSetString(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,93 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TEXT_H
+#define	TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+#include <cx/list.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+    UiTextBufOp *prev;
+    UiTextBufOp *next;
+    int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+    int  start;
+    int  end;
+    int  len;
+    char *text;
+};
+    
+typedef struct UiUndoMgr {
+    UiTextBufOp *begin;
+    UiTextBufOp *end;
+    UiTextBufOp *cur;
+    int         length;
+    int         event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+    UiContext *ctx;
+    int last_selection_state;
+} UiTextArea;
+    
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_text_selection_callback(
+        Widget widget,
+        UiTextArea *textarea,
+        XtPointer data);
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+void ui_free_textbuf_op(UiTextBufOp *op);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, char *value);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/toolbar.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,381 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "toolbar.h"
+#include "button.h"
+#include "stock.h"
+#include "list.h"
+
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#include "../common/context.h"
+
+static CxMap *toolbar_items;
+static CxList *defaults;
+
+void ui_toolbar_init() {
+    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    defaults = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *userdata) {
+    UiToolItem *item = malloc(sizeof(UiToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+    item->label = label;
+    item->image = NULL;
+    item->callback = f;
+    item->userdata = userdata;
+    item->groups = NULL;
+    item->isimportant = FALSE;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
+    ui_toolitem_stgr(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+    UiStToolItem *item = malloc(sizeof(UiStToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
+    item->stockid = stockid;
+    item->callback = f;
+    item->userdata = userdata;
+    item->groups = NULL;
+    item->isimportant = FALSE;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!item->groups) {
+            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+        }
+        cxListAdd(item->groups, &group);
+    }
+    va_end(ap);
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
+    // TODO
+    
+    UiToolItem *item = malloc(sizeof(UiToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+    item->label = label;
+    item->image = img;
+    item->callback = f;
+    item->userdata = udata;
+    item->groups = NULL;
+    item->isimportant = FALSE;
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+
+void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+    // TODO
+    
+    UiStToolItem *item = malloc(sizeof(UiStToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_toggle_widget;
+    item->stockid = stockid;
+    item->callback = f;
+    item->userdata = udata;
+    item->groups = NULL;
+    item->isimportant = FALSE;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, udata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!item->groups) {
+            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+        }
+        cxListAdd(item->groups, &group);
+    }
+    va_end(ap);
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_imggr(char *name, char *label, char *img, ui_callback f, void *udata, ...) {
+    // TODO
+    
+    UiToolItem *item = malloc(sizeof(UiToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = label;
+    item->image = img;
+    item->callback = f;
+    item->userdata = udata;
+    item->groups = NULL;
+    item->isimportant = FALSE;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, udata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        if(!item->groups) {
+            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+        }
+        cxListAdd(item->groups, &group);
+    }
+    va_end(ap);
+    
+    cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolbar_combobox(
+        char *name,
+        UiList *list,
+        ui_getvaluefunc getvalue,
+        ui_callback f,
+        void *udata)
+{
+    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
+    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;  
+    cb->list = list;
+    cb->getvalue = getvalue;
+    cb->callback = f;
+    cb->userdata = udata;
+    
+    cxMapPut(toolbar_items, name, cb);
+}
+
+void ui_toolbar_combobox_str(
+        char *name,
+        UiList *list,
+        ui_callback f,
+        void *udata)
+{
+    ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
+}
+
+void ui_toolbar_combobox_nv(
+        char *name,
+        char *listname,
+        ui_getvaluefunc getvalue,
+        ui_callback f,
+        void *udata)
+{
+    UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
+    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;  
+    cb->listname = listname;
+    cb->getvalue = getvalue;
+    cb->callback = f;
+    cb->userdata = udata;
+    
+    cxMapPut(toolbar_items, name, cb);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+    char *s = strdup(name);
+    cxListAdd(defaults, s);
+}
+
+Widget ui_create_toolbar(UiObject *obj, Widget parent) {
+    if(!defaults) {
+        return NULL;
+    }
+    
+    Arg args[8];
+    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
+    XtSetArg(args[1], XmNshadowThickness, 1);
+    XtSetArg(args[2], XmNtopAttachment, XmATTACH_FORM);
+    XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
+    XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
+    Widget frame = XmCreateFrame(parent, "toolbar_frame", args, 5);
+    
+    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
+    XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
+    XtSetArg(args[2], XmNspacing, 1);
+    Widget toolbar = XmCreateRowColumn (frame, "toolbar", args, 3);
+    
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char *, def, i) {
+        UiToolItemI *item = cxMapGet(toolbar_items, def);
+        if(item) {
+            item->add_to(toolbar, item, obj);
+        } else if(!strcmp(def, "@separator")) {
+            // TODO
+        } else {
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
+        }
+    }
+    
+    XtManageChild(toolbar);
+    XtManageChild(frame);
+    
+    return frame;
+}
+
+void add_toolitem_widget(Widget parent, UiToolItem *item, UiObject *obj) {
+    Arg args[4];
+    
+    XmString label = XmStringCreateLocalized(item->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNshadowThickness, 1);
+    XtSetArg(args[2], XmNtraversalOn, FALSE);
+    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
+    
+    XmStringFree(label);
+    
+    if(item->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        XtAddCallback(
+                button,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+    }
+    
+    XtManageChild(button);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+    }
+}
+
+void add_toolitem_st_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
+    Arg args[8];
+    
+    UiStockItem *stock_item = ui_get_stock_item(item->stockid);
+     
+    XmString label = XmStringCreateLocalized(stock_item->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNshadowThickness, 1);
+    XtSetArg(args[2], XmNtraversalOn, FALSE);
+    Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
+ 
+    XmStringFree(label);
+    
+    if(item->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        XtAddCallback(
+                button,
+                XmNactivateCallback,
+                (XtCallbackProc)ui_push_button_callback,
+                event);
+    }
+    
+    XtManageChild(button);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+    }
+}
+
+void add_toolitem_toggle_widget(Widget parent, UiToolItem *item, UiObject *obj) {
+    Arg args[8];
+    
+    XmString label = XmStringCreateLocalized(item->label);
+    XtSetArg(args[0], XmNlabelString, label);
+    XtSetArg(args[1], XmNshadowThickness, 1);
+    XtSetArg(args[2], XmNtraversalOn, FALSE);
+    XtSetArg(args[3], XmNindicatorOn, XmINDICATOR_NONE);
+    Widget button = XmCreateToggleButton(parent, "toolbar_toggle_button", args, 4);
+    
+    XmStringFree(label);
+    
+    if(item->callback) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        XtAddCallback(
+                button,
+                XmNvalueChangedCallback,
+                (XtCallbackProc)ui_toggle_button_callback,
+                event);
+    }
+    
+    XtManageChild(button);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+    }
+}
+
+void add_toolitem_st_toggle_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
+    
+}
+
+void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj) {
+    UiListView *listview = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiListView));
+    
+    UiVar *var = cxMalloc(obj->ctx->allocator, sizeof(UiVar));
+    var->value = item->list;
+    var->type = UI_VAR_SPECIAL;
+    
+    Arg args[8];
+    XtSetArg(args[0], XmNshadowThickness, 1);
+    XtSetArg(args[1], XmNindicatorOn, XmINDICATOR_NONE);
+    XtSetArg(args[2], XmNtraversalOn, FALSE);
+    XtSetArg(args[3], XmNwidth, 120);
+    Widget combobox = XmCreateDropDownList(tb, "toolbar_combobox", args, 4);
+    XtManageChild(combobox);
+    listview->widget = combobox;
+    listview->list = var;
+    listview->getvalue = item->getvalue;
+    
+    ui_listview_update(NULL, listview);
+    
+    if(item->callback) {
+        // TODO:
+        
+    }
+}
+
+void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj) {
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,105 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TOOLBAR_H
+#define	TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolItemI    UiToolItemI;
+typedef struct UiToolItem     UiToolItem;
+typedef struct UiStToolItem   UiStToolItem;
+
+typedef struct UiToolbarComboBox   UiToolbarComboBox;
+typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
+
+typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*);
+
+struct UiToolItemI {
+    ui_toolbar_add_f  add_to;
+};
+
+struct UiToolItem {
+    UiToolItemI item;
+    char           *label;
+    void           *image;
+    ui_callback    callback;
+    void           *userdata;
+    CxList         *groups;
+    Boolean        isimportant;
+};
+
+struct UiStToolItem {
+    UiToolItemI    item;
+    char           *stockid;
+    ui_callback    callback;
+    void           *userdata;
+    CxList         *groups;
+    Boolean        isimportant;
+};
+
+struct UiToolbarComboBox {
+    UiToolItemI         item;
+    UiList              *list;
+    ui_getvaluefunc     getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+struct UiToolbarComboBoxNV {
+    UiToolItemI         item;
+    char                *listname;
+    ui_getvaluefunc     getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+void ui_toolbar_init();
+
+Widget ui_create_toolbar(UiObject *obj, Widget parent);
+
+void add_toolitem_widget(Widget tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_widget(Widget tb, UiStToolItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(Widget tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_toggle_widget(Widget tb, UiStToolItem *item, UiObject *obj);
+
+void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj);
+void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/toolkit.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,302 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <unistd.h>
+#include <pthread.h>
+
+#include "toolkit.h"
+#include "toolbar.h"
+#include "stock.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include <cx/buffer.h>
+
+static XtAppContext app;
+static Display *display;
+static Widget active_window;
+static 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;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+static int is_toplevel_realized = 0;
+
+int event_pipe[2];
+
+
+static String fallback[] = {
+    	//"*fontList: -dt-interface system-medium-r-normal-s*utf*:",    
+        "*text_area*renderTable: f1",
+        "*f1*fontType: FONT_IS_XFT",
+        "*f1*fontName: Monospace",
+        "*f1*fontSize: 11",
+        "*renderTable: rt",
+        "*rt*fontType: FONT_IS_XFT",
+        "*rt*fontName: Sans",
+        "*rt*fontSize: 11",
+	NULL
+};
+
+void input_proc(XtPointer data, int *source, XtInputId *iid) {
+    void *ptr;
+    read(event_pipe[0], &ptr, sizeof(void*));
+}
+
+void ui_init(char *appname, int argc, char **argv) { 
+    application_name = appname;
+    
+    XtToolkitInitialize();
+    XtSetLanguageProc(NULL, NULL, NULL);
+    app = XtCreateApplicationContext();
+    XtAppSetFallbackResources(app, fallback);
+    
+    display =  XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv);
+    char **missing = NULL;
+    int nm = 0;
+    char *def = NULL;
+    XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def);
+    
+    uic_docmgr_init();
+    ui_toolbar_init();
+    ui_stock_init();
+    
+    uic_load_app_properties();
+    
+    if(pipe(event_pipe)) {
+        fprintf(stderr, "UiError: Cannot create event pipe\n");
+        exit(-1);
+    }
+    XtAppAddInput(
+            app,
+            event_pipe[0],
+            (XtPointer)XtInputReadMask,
+            input_proc,
+            NULL);
+}
+
+char* ui_appname() {
+    return application_name;
+}
+
+Display* ui_get_display() {
+    return display;
+}
+
+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);
+    }
+    XtAppMainLoop(app);
+    if(exit_func) {
+        exit_func(NULL, exit_data);
+    }
+    uic_store_app_properties();
+}
+
+void ui_exit_mainloop() {
+    XtAppSetExitFlag(app);
+}
+
+void ui_secondary_event_loop(int *loop) {
+    while(*loop && !XtAppGetExitFlag(app)) {
+        XEvent event;
+        XtAppNextEvent(app, &event);
+        XtDispatchEvent(&event);
+    }
+}
+
+void ui_show(UiObject *obj) {
+    uic_check_group_widgets(obj->ctx);
+    XtRealizeWidget(obj->widget);
+    ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if
+}
+
+// implemented in window.c
+//void ui_close(UiObject *obj)
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    XtSetSensitive(widget, enabled);
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    if(!value) {
+        XtUnmanageChild(widget);
+    }
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    if(visible) {
+        XtManageChild(widget);
+    } else {
+        XtUnmanageChild(widget);
+    }
+}
+
+static Boolean ui_job_finished(void *data) {
+    printf("WorkProc\n");
+    UiJob *job = data;
+    
+    UiEvent event;
+    event.obj = job->obj;
+    event.window = job->obj->window;
+    event.document = job->obj->ctx->document;
+    event.intval = 0;
+    event.eventdata = NULL;
+
+    job->finish_callback(&event, job->finish_data);
+    free(job);
+    return TRUE;
+}
+
+static void* ui_jobthread(void *data) {
+    UiJob *job = data;
+    int result = job->job_func(job->job_data);
+    if(!result) {
+        printf("XtAppAddWorkProc\n");
+        write(event_pipe[1], &job, sizeof(void*)); // hack
+        XtAppAddWorkProc(app, ui_job_finished, job);
+        
+    }
+    return NULL;
+}
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+    UiJob *job = malloc(sizeof(UiJob));
+    job->obj = obj;
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = f;
+    job->finish_data = fd;
+    pthread_t pid;
+    pthread_create(&pid, NULL, ui_jobthread, job);
+}
+
+void ui_clipboard_set(char *str) {
+    printf("copy: {%s}\n", str);
+    int length = strlen(str) + 1;
+    
+    Display *dp = XtDisplayOfObject(active_window);
+    Window window = XtWindowOfObject(active_window);
+    
+    XmString label = XmStringCreateLocalized("toolkit_clipboard");
+    long id = 0;
+    
+    while(XmClipboardStartCopy(
+            dp,
+            window,
+            label,
+            CurrentTime,
+            NULL,
+            NULL,
+            &id) == ClipboardLocked);
+    XmStringFree(label);
+    
+    while(XmClipboardCopy(
+            dp,
+            window,
+            id,
+            "STRING",
+            str, 
+            length,
+            1,
+            NULL) == ClipboardLocked);
+    
+    while(XmClipboardEndCopy(dp, window, id) == ClipboardLocked);
+}
+
+char* ui_clipboard_get() {
+    Display *dp = XtDisplayOfObject(active_window);
+    Window window = XtWindowOfObject(active_window);
+    
+    long id;
+    size_t size = 128;
+    char *buf = malloc(size);
+    
+    int r;
+    for(;;) {
+        r = XmClipboardRetrieve(dp, window, "STRING", buf, size, NULL, &id);
+        if(r == ClipboardSuccess) {
+            break;
+        } else if(r == ClipboardTruncate) {
+            size *= 2;
+            buf = realloc(buf, size);
+        } else if(r == ClipboardNoData) {
+            free(buf);
+            buf = NULL;
+            break;
+        }
+    }
+    
+    return buf;
+}
+
+void ui_set_active_window(Widget w) {
+    active_window = w;
+}
+
+Widget ui_get_active_window() {
+    return active_window;
+}
+
+void ui_window_dark_theme(Display *dp, Window window) {
+    Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False);
+    Atom type = XInternAtom(dp, "UTF8_STRING", False);
+    XChangeProperty(
+            dp, 
+            window, 
+            atom,
+            type,
+            8,
+            PropModeReplace,
+            (const unsigned char*)"dark",
+            4);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,74 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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
+
+Display* ui_get_display();
+
+typedef struct UiEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+} UiEventData;
+
+typedef struct UiJob {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+} UiJob;
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+void ui_exit_mainloop();
+
+void ui_set_active_window(Widget w);
+Widget ui_get_active_window();
+
+void ui_secondary_event_loop(int *loop);
+void ui_window_dark_theme(Display *dp, Window window);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLKIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/tree.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,328 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "tree.h"
+
+#include "container.h"
+#include "../common/object.h"
+#include "../common/context.h"
+#include <cx/utils.h>
+#include <cx/compare.h>
+#include <cx/printf.h>
+
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
+    // TODO: check if modelinfo is complete
+    
+    Arg args[32];
+    int n = 0;
+    
+    // create scrolled window
+    UiContainer *ct = uic_get_current_container(obj);
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    
+    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC);
+    n++;
+    XtSetArg(args[n], XmNshadowThickness, 0);
+    n++;
+    Widget scrollw = XmCreateScrolledWindow(parent, "scroll_win", args, n);
+    ct->add(ct, scrollw);
+    XtManageChild(scrollw);
+    
+    // create table headers
+    XmStringTable header = (XmStringTable)XtMalloc(
+            model->columns * sizeof(XmString));
+    for(int i=0;i<model->columns;i++) {
+        header[i] = XmStringCreateLocalized(model->titles[i]);
+    }
+    n = 0;
+    XtSetArg(args[n], XmNdetailColumnHeading, header);
+    n++;
+    XtSetArg(args[n], XmNdetailColumnHeadingCount, model->columns);
+    n++;
+    
+    // set res
+    XtSetArg(args[n], XmNlayoutType, XmDETAIL);
+    n++;
+    XtSetArg(args[n], XmNentryViewType, XmSMALL_ICON);
+    n++;
+    XtSetArg(args[n], XmNselectionPolicy, XmSINGLE_SELECT);
+    n++;
+    XtSetArg(args[n], XmNwidth, 600);
+    n++;
+    
+    // create widget
+    //UiContainer *ct = uic_get_current_container(obj);
+    //Widget parent = ct->add(ct, args, &n);
+    
+    Widget container = XmCreateContainer(scrollw, "table", args, n);
+    XtManageChild(container);
+    
+    // add callbacks
+    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = cb.activate;
+    event->selection = cb.selection;
+    event->userdata = cb.userdata;
+    event->last_selection = NULL;
+    if(cb.selection) {
+        XtAddCallback(
+                container,
+                XmNselectionCallback,
+                (XtCallbackProc)ui_table_select_callback,
+                event);
+    }
+    if(cb.activate) {
+        XtAddCallback(
+                container,
+                XmNdefaultActionCallback,
+                (XtCallbackProc)ui_table_action_callback,
+                event);
+    }
+    
+    // add initial data
+    UiList *list = var->value;
+    void *data = list->first(list);
+    int width = 0;
+    while(data) {
+        int w = ui_add_icon_gadget(container, model, data);
+        if(w > width) {
+            width = w;
+        }
+        data = list->next(list);
+    }
+    
+    UiTableView *tableview = cxMalloc(obj->ctx->allocator, sizeof(UiTableView));
+    tableview->widget = container;
+    tableview->var = var;
+    tableview->model = model;
+    
+    // set new XmContainer width
+    XtVaSetValues(container, XmNwidth, width, NULL);
+    
+    // cleanup
+    for(int i=0;i<model->columns;i++) {
+        XmStringFree(header[i]);
+    }
+    XtFree((char*)header);
+    
+    return scrollw;
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = data;
+    var->type = UI_VAR_SPECIAL;
+    return ui_table_var(obj, var, model, cb);
+}
+
+void ui_table_update(UiEvent *event, UiTableView *view) {
+    // clear container
+    Widget *children;
+    int nc;
+
+    XtVaGetValues(
+            view->widget,
+            XmNchildren,
+            &children,
+            XmNnumChildren,
+            &nc,
+            NULL);
+
+    for(int i=0;i<nc;i++) {
+        XtDestroyWidget(children[i]);
+    }
+    
+    UiList *list = view->var->value;
+    
+    void *data = list->first(list);
+    int width = 0;
+    while(data) {
+        int w = ui_add_icon_gadget(view->widget, view->model, data);
+        if(w > width) {
+            width = w;
+        }
+        data = list->next(list);
+    }
+    
+}
+
+#define UI_COL_CHAR_WIDTH 12
+
+int ui_add_icon_gadget(Widget container, UiModel *model, void *data) {
+    int width = 50;
+    
+    if(model->columns == 0) {
+        return width;
+    }
+    
+    XmString label = NULL;
+    Arg args[8];
+    Boolean f;
+    // first column
+    if(model->types[0] != 12345678) { // TODO: icon/label type
+        char *str = ui_type_to_string(
+                model->types[0],
+                model->getvalue(data, 0),
+                &f);
+        
+        // column width
+        width += strlen(str) * UI_COL_CHAR_WIDTH;
+        
+        
+        XmString label = XmStringCreateLocalized(str);
+        XtSetArg(args[0], XmNlabelString, label);
+        if(f) {
+            free(str);
+        }
+    } else {
+        // TODO
+    }
+            
+    // remaining columns are the icon gadget details
+    XmStringTable details = (XmStringTable)XtMalloc(
+            (model->columns - 1) * sizeof(XmString));
+    for(int i=1;i<model->columns;i++) {
+        char *str = ui_type_to_string(
+                model->types[i],
+                model->getvalue(data, i),
+                &f);
+        
+        // column width
+        width += strlen(str) * UI_COL_CHAR_WIDTH;
+        
+        details[i - 1] = XmStringCreateLocalized(str);
+        if(f) {
+            free(str);
+        }
+    }
+    XtSetArg(args[1], XmNdetail, details);
+    XtSetArg(args[2], XmNdetailCount, model->columns - 1);
+    XtSetArg(args[3], XmNshadowThickness, 0); 
+    // create widget
+    Widget item = XmCreateIconGadget(container, "table_item", args, 4);
+    XtManageChild(item);
+    
+    // cleanup
+    XmStringFree(label);
+    for(int i=0;i<model->columns-1;i++) {
+        XmStringFree(details[i]);
+    }
+    XtFree((char*)details);
+    
+    return width;
+}
+
+char* ui_type_to_string(UiModelType type, void *data, Boolean *free) {
+    switch(type) {
+        case UI_STRING: *free = FALSE; return data;
+        case UI_INTEGER: {
+            *free = TRUE;
+            int *val = data;
+            cxmutstr str = cx_asprintf("%d", *val);
+            return str.ptr;
+        }
+        case UI_ICON: break; // TODO
+        case UI_ICON_TEXT: break; // TODO
+    }
+    *free = FALSE;
+    return NULL;
+}
+
+void ui_table_action_callback(
+        Widget widget,
+        UiTreeEventData *event,
+        XmContainerSelectCallbackStruct *sel)
+{ 
+    UiListSelection *selection = ui_list_selection(sel);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = selection;
+    e.intval = selection->count > 0 ? selection->rows[0] : -1;
+    event->activate(&e, event->userdata);
+    
+    free(event->last_selection->rows);
+    free(event->last_selection);
+    event->last_selection = selection;
+}
+
+void ui_table_select_callback(
+        Widget widget,
+        UiTreeEventData *event,
+        XmContainerSelectCallbackStruct *sel)
+{ 
+    UiListSelection *selection = ui_list_selection(sel);
+    if(!ui_compare_list_selection(selection, event->last_selection)) {
+        UiEvent e;
+        e.obj = event->obj;
+        e.window = event->obj->window;
+        e.document = event->obj->ctx->document;
+        e.eventdata = selection;
+        e.intval = selection->count > 0 ? selection->rows[0] : -1;
+        event->selection(&e, event->userdata);
+    }
+    if(event->last_selection) {
+        free(event->last_selection->rows);
+        free(event->last_selection);
+    }
+    event->last_selection = selection;
+}
+
+UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs) {
+    UiListSelection *selection = malloc(sizeof(UiListSelection));
+    selection->count = xs->selected_item_count;
+    selection->rows = calloc(selection->count, sizeof(int));
+    for(int i=0;i<selection->count;i++) {
+        int index;
+        XtVaGetValues(xs->selected_items[i], XmNpositionIndex, &index, NULL);
+        selection->rows[i] = index;
+    }
+    return selection;
+}
+
+Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2) {
+    if(!s1 || !s2) {
+        return FALSE;
+    } 
+    if(s1->count != s2->count) {
+        return FALSE;
+    }
+    for(int i=0;i<s1->count;i++) {
+        if(s1->rows[i] != s2->rows[i]) {
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/tree.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,76 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TREE_H
+#define	TREE_H
+
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiTreeEventData {
+    UiObject        *obj;
+    ui_callback     activate;
+    ui_callback     selection;
+    void            *userdata;
+    UiListSelection *last_selection;
+} UiTreeEventData;    
+
+typedef struct UiTableView {
+    Widget      widget;
+    UiVar       *var;
+    UiModel     *model;
+} UiTableView;
+
+void ui_table_update(UiEvent *event, UiTableView *view);
+int ui_add_icon_gadget(Widget container, UiModel *model, void *data);
+char* ui_type_to_string(UiModelType type, void *data, Boolean *free);
+
+void ui_table_action_callback(
+        Widget widget,
+        UiTreeEventData *event,
+        XmContainerSelectCallbackStruct *sel);
+void ui_table_select_callback(
+        Widget widget,
+        UiTreeEventData *event,
+        XmContainerSelectCallbackStruct *sel);
+
+UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs);
+
+Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TREE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/window.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,211 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "toolkit.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+#include "../ui/window.h"
+#include "../common/context.h"
+
+#include <cx/basic_mempool.h>
+
+static int nwindows = 0;
+
+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;
+    UiEvent ev;
+    ev.window = obj->window;
+    ev.document = obj->ctx->document;
+    ev.obj = obj;
+    ev.eventdata = NULL;
+    ev.intval = 0;
+    
+    if(obj->ctx->close_callback) {
+        obj->ctx->close_callback(&ev, obj->ctx->close_data);
+    }
+    // TODO: free UiObject
+    
+    nwindows--;
+    if(nwindows == 0) {
+        ui_exit_mainloop();
+    }
+}
+
+static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    const CxAllocator *a = mp->allocator;
+    UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
+    obj->ctx = uic_context(obj, a);
+    obj->window = window_data;
+    
+    Arg args[16];
+    int n = 0;
+    
+    XtSetArg(args[0], XmNtitle, title);
+    //XtSetArg(args[1], XmNbaseWidth, window_default_width);
+    //XtSetArg(args[2], XmNbaseHeight, window_default_height);
+    XtSetArg(args[1], XmNminWidth, 100);
+    XtSetArg(args[2], XmNminHeight, 50);
+    XtSetArg(args[3], XmNwidth, window_default_width);
+    XtSetArg(args[4], XmNheight, window_default_height);
+    
+    Widget toplevel = XtAppCreateShell(
+            "Test123",
+            "abc",
+            //applicationShellWidgetClass,
+            vendorShellWidgetClass,
+            ui_get_display(),
+            args,
+            5);
+    
+    Atom wm_delete_window;
+    wm_delete_window = XmInternAtom(
+            XtDisplay(toplevel),
+            "WM_DELETE_WINDOW",
+            0);
+    XmAddWMProtocolCallback(
+            toplevel,
+            wm_delete_window,
+            window_close_handler,
+            obj);
+    
+    // TODO: use callback
+    ui_set_active_window(toplevel);
+    
+    Widget window = XtVaCreateManagedWidget(
+            title,
+            xmMainWindowWidgetClass,
+            toplevel,
+            NULL);
+    obj->widget = window;
+    Widget form = XtVaCreateManagedWidget(
+            "window_form",
+            xmFormWidgetClass,
+            window,
+            NULL);
+    Widget toolbar = NULL;
+    
+    if(!simple) {
+        ui_create_menubar(obj);
+        toolbar = ui_create_toolbar(obj, form);
+    }
+    
+    // window content
+    XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
+    XtSetArg(args[1], XmNshadowThickness, 0);
+    XtSetArg(args[2], XmNleftAttachment, XmATTACH_FORM);
+    XtSetArg(args[3], XmNrightAttachment, XmATTACH_FORM);
+    XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+    if(toolbar) {
+        XtSetArg(args[5], XmNtopAttachment, XmATTACH_WIDGET);
+        XtSetArg(args[6], XmNtopWidget, toolbar);
+        n = 7;
+    } else {
+        XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
+        n = 6;
+    }
+    Widget frame = XmCreateFrame(form, "content_frame", args, n);
+    XtManageChild(frame);
+    
+    Widget content_form = XmCreateForm(frame, "content_form", NULL, 0);
+    XtManageChild(content_form);
+    obj->container = ui_box_container(obj, content_form, 0, 0, UI_BOX_VERTICAL);
+    
+    XtManageChild(form);
+      
+    obj->widget = toplevel;
+    nwindows++;
+    return obj;
+}
+
+UiObject* ui_window(char *title, void *window_data) {
+    return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+    return create_window(title, window_data, TRUE);
+}
+
+void ui_close(UiObject *obj) {
+    XtDestroyWidget(obj->widget);
+    window_close_handler(obj->widget, obj, NULL);
+}
+
+typedef struct FileDialogData {
+    int  running;
+    char *file;
+} FileDialogData;
+
+static void filedialog_select(
+        Widget widget,
+        FileDialogData *data,
+        XmFileSelectionBoxCallbackStruct *selection)
+{
+    char *path = NULL;
+    XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &path);
+    data->running = 0;
+    data->file = strdup(path);
+    XtFree(path);
+    XtUnmanageChild(widget);
+}
+
+static void filedialog_cancel(
+        Widget widget,
+        FileDialogData *data,
+        XmFileSelectionBoxCallbackStruct *selection)
+
+{
+    data->running = 0;
+    XtUnmanageChild(widget);
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+    Widget dialog = XmCreateFileSelectionDialog(obj->widget, "openfiledialog", NULL, 0);
+    XtManageChild(dialog);
+    
+    FileDialogData data;
+    data.running = 1;
+    data.file = NULL;
+    
+    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, &data);
+    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, &data);
+    
+    ui_secondary_event_loop(&data.running);
+    return data.file;
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+    return ui_openfiledialog(obj);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+
+QT_MAKEFILE = ../build/ui/qt/Makefile.mk
+
+UI_QT_LIB = ../build/ui/qt/
+
+$(QT_MAKEFILE): qt/qt4.pro
+	qmake-qt4 -o - qt/qt4.pro > $(QT_MAKEFILE)
+
+$(UI_LIB): $(QT_MAKEFILE) $(OBJ) FORCE
+	$(MAKE) -f $(QT_MAKEFILE)
+	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
+FORCE:
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/button.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,90 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "button.h"
+#include "container.h"
+#include "toolkit.h"
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+    QString str = QString::fromUtf8(label);
+    QPushButton *button = new QPushButton(str);
+    
+    if(f) {
+        UiEventWrapper *event = new UiEventWrapper(obj, f, data);
+        button->connect(button, SIGNAL(clicked()), event, SLOT(slot()));
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(button, false);
+    
+    return button;
+}
+
+
+
+// TODO: checkbox
+
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup)  {
+    QString str = QString::fromUtf8(label);
+    QRadioButton *button = new QRadioButton(str);
+    button->setAutoExclusive(false);
+    
+    if(rgroup) {
+        QButtonGroup *buttonGroup = (QButtonGroup*)rgroup->obj;
+        if(!buttonGroup) {
+            buttonGroup = new QButtonGroup();
+            rgroup->obj = buttonGroup;
+            button->setChecked(true);
+        }
+        buttonGroup->addButton(button, buttonGroup->buttons().size());
+        
+        rgroup->get = ui_radiobutton_get;
+        rgroup->set = ui_radiobutton_set;
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(button, false);
+    
+    return button;
+}
+
+int 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) {
+    QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
+    QAbstractButton *button = buttonGroup->button(i);
+    if(button) {
+        button->setChecked(true);
+        value->value = i;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 BUTTON_H
+#define	BUTTON_H
+
+#include "toolkit.h"
+#include "../ui/button.h"
+#include <QPushButton>
+#include <QRadioButton>
+#include <QButtonGroup>
+
+extern "C" {
+    
+int ui_radiobutton_get(UiInteger *value);
+
+void ui_radiobutton_set(UiInteger *value, int i);
+
+}
+
+#endif	/* BUTTON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/container.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,256 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "container.h"
+
+#include <QSpacerItem>
+#include <QStackedWidget>
+
+
+/* -------------------- UiBoxContainer -------------------- */
+
+UiBoxContainer::UiBoxContainer(QBoxLayout* box) {
+    this->current = NULL;
+    this->menu = NULL;
+    this->box = box;
+    box->setContentsMargins(QMargins(0,0,0,0));
+    box->setSpacing(0);
+    
+    ui_reset_layout(layout);
+}
+
+void UiBoxContainer::add(QWidget* widget, bool fill) {
+    if(layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(layout.fill);
+    }
+    
+    if(hasStretchedWidget && fill) {
+        fill = false;
+        fprintf(stderr, "UiError: container has 2 filled widgets");
+    }
+    
+    box->addWidget(widget, fill);
+    
+    if(!hasStretchedWidget) {
+        QSpacerItem *newspace = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+        box->removeItem(space);
+        box->addSpacerItem(newspace);
+        space = newspace; 
+    }
+    
+    if(fill) {
+        hasStretchedWidget = true;
+    }
+    ui_reset_layout(layout);
+    current = widget;
+}
+
+UIWIDGET ui_box(UiObject *obj, QBoxLayout::Direction dir) {
+    UiContainer *ct = uic_get_current_container(obj);
+    QWidget *widget = new QWidget();
+    QBoxLayout *box = new QBoxLayout(dir);
+    widget->setLayout(box);
+    ct->add(widget, true);
+    
+    UiObject *newobj = uic_object_new(obj, widget);
+    newobj->container = new UiBoxContainer(box);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_box(obj, QBoxLayout::TopToBottom);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_box(obj, QBoxLayout::LeftToRight);
+}
+
+
+
+/* -------------------- UiGridContainer -------------------- */
+
+UiGridContainer::UiGridContainer(QGridLayout* grid, int margin, int columnspacing, int rowspacing) {
+    this->current = NULL;
+    this->menu = NULL;
+    this->grid = grid;
+    grid->setContentsMargins(QMargins(margin, margin, margin, margin));
+    grid->setHorizontalSpacing(columnspacing);
+    grid->setVerticalSpacing(rowspacing);
+    ui_reset_layout(layout);
+}
+
+void UiGridContainer::add(QWidget* widget, bool fill) {
+    if(layout.newline) {
+        x = 0;
+        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 gwidth = layout.gridwidth > 0 ? layout.gridwidth : 1;
+    
+    grid->addWidget(widget, y, x, 1, gwidth, alignment);
+    x += gwidth;
+    
+    ui_reset_layout(layout);
+    current = widget;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    QWidget *widget = new QWidget();
+    QGridLayout *grid = new QGridLayout();
+    widget->setLayout(grid);
+    ct->add(widget, true);
+    
+    UiObject *newobj = uic_object_new(obj, widget);
+    newobj->container = new UiGridContainer(grid, margin, columnspacing, rowspacing);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+
+/* -------------------- UiTabViewContainer -------------------- */
+
+UiTabViewContainer::UiTabViewContainer(QTabWidget* tabwidget) {
+    this->current = NULL;
+    this->menu = NULL;
+    this->tabwidget = tabwidget;
+}
+
+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 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);
+}
+
+
+/* -------------------- 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;
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.newline = TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,119 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 CONTAINER_H
+#define	CONTAINER_H
+
+#include "toolkit.h"
+#include "window.h"
+
+#include <string.h>
+#include <QBoxLayout>
+#include <QGridLayout>
+#include <QTabWidget>
+#include <QStackedWidget>
+#include <QSplitter>
+
+#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;
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+typedef enum UiLayoutBool UiLayoutBool;
+
+struct UiLayout {
+    UiLayoutBool fill;
+    bool newline;
+    char *label;
+    bool hexpand;
+    bool vexpand;
+    int  gridwidth;
+};
+
+struct UiContainer {
+    UiLayout layout; 
+    UIWIDGET current;
+    QMenu    *menu;
+
+    virtual void add(QWidget *widget, bool fill) = 0;
+};
+
+class UiBoxContainer : public UiContainer {
+public:
+    QBoxLayout  *box;
+    bool        hasStretchedWidget = false;
+    QSpacerItem *space;
+    
+    UiBoxContainer(QBoxLayout *box);
+    
+    virtual void add(QWidget *widget, bool fill);
+};
+
+class UiGridContainer : public UiContainer {
+public:
+    QGridLayout *grid;
+    int x = 0;
+    int y = 0;
+    
+    UiGridContainer(QGridLayout *grid, int margin, int columnspacing, int rowspacing);
+    
+    virtual void add(QWidget *widget, bool fill);
+};
+
+class UiTabViewContainer : public UiContainer {
+public:
+    QTabWidget *tabwidget;
+    
+    UiTabViewContainer(QTabWidget *tabwidget);
+    virtual void add(QWidget *widget, bool fill);
+};
+
+class UiStackContainer : public UiContainer {
+public:
+    QStackedWidget *stack;
+    
+    UiStackContainer(QStackedWidget *stack);
+    virtual void add(QWidget *widget, bool fill);
+};
+
+class UiSidebarContainer : public UiContainer {
+public:
+    QSplitter *splitter;
+    
+    UiSidebarContainer(QSplitter *splitter);
+    virtual void add(QWidget *widget, bool fill);
+};
+
+#endif	/* CONTAINER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/graphics.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,151 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "graphics.h"
+#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);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,67 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 GRAPHICS_H
+#define GRAPHICS_H
+
+#include "toolkit.h"
+#include "../ui/graphics.h"
+
+#include <QWidget>
+#include <QPainter>
+#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 */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/label.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "label.h"
+#include "container.h"
+#include "toolkit.h"
+
+UIWIDGET ui_label(UiObject *obj, char *label) {
+    QString str = QString::fromUtf8(label);
+    QLabel *widget = new QLabel(str);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/label.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,36 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 LABEL_H
+#define	LABEL_H
+
+#include "toolkit.h"
+#include <QLabel>
+
+#endif	/* LABEL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/menu.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,425 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <QMenuBar>
+#include <QAction>
+
+#include "menu.h"
+#include "toolkit.h"
+#include "../common/context.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);
+}
+
+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);
+    }
+}
+
+
+/* -------------------------- UiMenuItem -------------------------- */
+
+UiMenuItem::UiMenuItem(char* label, ui_callback f, void* userdata) {
+    this->label = label;
+    this->callback = f;
+    this->userdata = userdata;
+    this->groups = NULL;
+}
+
+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) {
+    QString str = QString::fromUtf8(label);
+    UiAction *action = new UiAction(obj, str, callback, userdata);
+    action->setCheckable(checkable);
+    if(checkable) {
+        action->setChecked(false);
+    }
+    menu->addAction(action);
+    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+/* -------------------------- 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);
+    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+/* -------------------------- UiMenuSeparator -------------------------- */
+
+void UiMenuSeparator::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
+    menu->addSeparator();
+}
+
+
+/* -------------------------- UiCheckItemNV -------------------------- */
+
+UiCheckItemNV::UiCheckItemNV(char* label, char* varname) {
+    this->label = label;
+    this->varname = varname;
+}
+
+void UiCheckItemNV::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
+    QString str = QString::fromUtf8(label);
+    UiAction *action = new UiAction(obj, str, NULL, NULL);
+    action->setCheckable(true);
+    menu->addAction(action);
+    QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+    
+    UiVar *var = uic_connect_var(obj->ctx, 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
+    }
+}
+
+
+/* -------------------------- 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;
+    }
+    
+    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);
+}
+
+
+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_submenu(char *label) {
+    UiMenu *menu = new UiMenu(label);
+    
+    // add submenu to current menu
+    UiMenu *cm = (UiMenu*)current->data;
+    cm->addMenuItem(menu);
+    
+    // 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) {
+        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);        
+    }
+}
+
+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_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()));
+}
+
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
+    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+}
+
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    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()));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,134 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 MENU_H
+#define	MENU_H
+
+#include "../ui/menu.h"
+#include <ucx/list.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);
+
+class UiContextMenuHandler : public QObject {
+    Q_OBJECT
+    
+    QWidget *widget;
+    QMenu *menu;
+    
+public:
+    UiContextMenuHandler(QWidget *widget, QMenu *menu);
+    
+public slots:
+    void contextMenuEvent(const QPoint & pos);
+};
+
+#endif	/* MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/model.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,170 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/model.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,91 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 MODEL_H
+#define	MODEL_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "../common/context.h"
+#include <QListView>
+#include <QTreeView>
+#include <QAbstractListModel>
+#include <QAbstractTableModel>
+#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/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+
+QT_SRC_DIR = ui/qt/
+QT_OBJPRE = $(OBJ_DIR)/$(QT_SRC_DIR)
+
+#QTOBJ = 
+
+TOOLKITOBJS += $(QTOBJ:%=$(QT_OBJPRE)%)
+TOOLKITSOURCE += $(QTOBJ:%.o=qt/%.cpp)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/qt4.pro	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,63 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2014 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
+
+DEFINES += UI_QT4
+
+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
+
+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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/stock.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,77 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <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;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/stock.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 STOCK_H
+#define	STOCK_H
+
+#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 */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/text.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,195 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "text.h"
+#include "container.h"
+
+#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();
+    
+    if(value) {
+        if(value->value && value->obj) {
+            QString str = QString::fromUtf8(value->value);
+            txtdoc->setPlainText(str);
+        }
+        
+        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->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->remove = ui_textarea_remove;
+        value->obj = txtdoc;
+        value->value = NULL;
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj); 
+    QTextEdit *textedit = new QTextEdit();
+    textedit->setDocument(txtdoc);
+    ct->add(textedit, true);
+    
+    return textedit;
+}
+
+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;
+}
+
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value) {
+        free(text->value);
+    }
+    
+    QTextDocument *doc = (QTextDocument*)text->obj;
+    QString str = doc->toPlainText();
+    QByteArray array = str.toLocal8Bit();
+    const char *cstr = array.constData();
+    
+    if(text->value) {
+        free(text->value);
+    }
+    text->value = strdup(cstr);
+    return text->value;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    // set text
+    QTextDocument *doc = (QTextDocument*)text->obj;
+    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
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    // TODO
+}
+
+int ui_textarea_position(UiText *text) {
+    QTextDocument *doc = (QTextDocument*)text->obj;
+    return 0; // TODO
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+int ui_textarea_length(UiText *text) {
+    QTextDocument *doc = (QTextDocument*)text->obj;
+    return 0; // TODO
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+    QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+
+/* ------------------- TextField ------------------- */
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    QLineEdit *textfield = new QLineEdit();
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(textfield, false);
+    
+    if(value) {
+        if(value->value) {
+            QString str = QString::fromUtf8(value->value);
+            textfield->setText(str);
+            free(value->value);
+            value->value = NULL;
+        }
+        value->set = ui_textfield_set;
+        value->get = ui_textfield_get;
+        value->obj = textfield;
+    }
+    
+    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;
+}
+
+char* ui_textfield_get(UiString *str) {
+    QLineEdit *textfield = (QLineEdit*)str->obj;
+    QString qstr = textfield->text();
+    
+    if(str->value) {
+        free(str->value);
+    }
+    QByteArray array = qstr.toLocal8Bit();
+    const char *cstr = array.constData();
+    str->value = strdup(cstr);
+    
+    return str->value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    QLineEdit *textfield = (QLineEdit*)str->obj;
+    QString qstr = QString::fromUtf8(value);
+    textfield->setText(qstr);
+    
+    if(str->value) {
+        free(str->value);
+    }
+    str->value = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,56 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TEXT_H
+#define	TEXT_H
+
+#include "toolkit.h"
+#include "../ui/text.h"
+#include <QTextEdit>
+#include <QLineEdit>
+
+// 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);
+}
+
+
+
+#endif	/* TEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/toolbar.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,165 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,83 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TOOLBAR_H
+#define	TOOLBAR_H
+
+#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 */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/toolkit.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,121 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <string.h>
+
+#include "toolkit.h"
+#include "window.h"
+#include "stock.h"
+
+#include "../common/document.h"
+#include "../common/properties.h"
+
+static char *application_name;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+//static QApplication app(qargc, qargv);
+int app_argc;
+char **app_argv;
+QApplication *application = NULL;
+
+void ui_init(char *appname, int argc, char **argv) {
+    application_name = appname;
+    
+    app_argc = argc;
+    app_argv = argv;
+    application = new QApplication(app_argc, app_argv);
+    
+    uic_docmgr_init();
+    
+    uic_load_app_properties();
+    
+    ui_stock_init();
+}
+
+char* ui_appname() {
+    return application_name;
+}
+
+void ui_exitfunc(ui_callback f, void *udata) {
+    appclose_fnc = f;
+    appclose_udata = udata;
+}
+
+void ui_openfilefunc(ui_callback f, void *userdata) {
+    // OS X only
+}
+
+void ui_main() {
+    application->exec();
+    
+    if(appclose_fnc) {
+        appclose_fnc(NULL, appclose_udata);
+    }
+    uic_store_app_properties();
+    
+    delete application;
+}
+
+void ui_show(UiObject *obj) {
+    obj->widget->show();
+}
+
+void ui_close(UiObject *obj) {
+    QMainWindow *window = (QMainWindow*)obj->widget;
+    window->close();
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    
+}
+
+
+
+
+UiEventWrapper::UiEventWrapper(UiObject *obj, ui_callback f, void* userdata) {
+    this->obj = obj;
+    this->callback = f;
+    this->userdata = userdata;
+}
+
+void UiEventWrapper::slot() {
+    UiEvent e;
+    e.obj = obj;
+    e.window = obj->window;
+    e.document = obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = 0;
+    callback(&e, userdata);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,54 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <QApplication>
+
+class UiEventWrapper : public QObject {
+    Q_OBJECT
+    
+    UiObject *obj;
+    ui_callback callback;
+    void *userdata;
+
+public:
+    UiEventWrapper(UiObject *obj, ui_callback f, void *userdata);
+    
+public slots:
+    void slot();
+};
+
+
+#endif	/* TOOLKIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/tree.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,142 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "tree.h"
+#include "container.h"
+
+#include <QTreeView>
+#include <QTreeWidgetItem>
+#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;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/tree.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 TREE_H
+#define	TREE_H
+
+#include "../ui/tree.h"
+#include "model.h"
+
+#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/window.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,95 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <ucx/mempool.h>
+#include "../common/context.h"
+
+#include "window.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+
+#include <QVBoxLayout>
+#include <QFileDialog>
+
+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));
+    obj->ctx = uic_context(obj, mp);
+    obj->window = window_data;
+    obj->next = NULL;
+    
+    QMainWindow *window = new QMainWindow();
+    obj->widget = window;
+    
+    if(!simple) {
+        ui_add_menus(obj, window);
+        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);
+    
+    obj->widget = window;
+    return obj;
+}
+
+UiObject* ui_window(char *title, void *window_data) {
+    return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+    return create_window(title, window_data, TRUE);
+}
+
+
+char* ui_openfiledialog(UiObject *obj) {
+    QString fileName = QFileDialog::getOpenFileName(obj->widget);
+    if(fileName.size() > 0) {
+        QByteArray array = fileName.toLocal8Bit();
+        const char *cstr = array.constData();
+        return strdup(cstr);
+    } else {
+        return NULL;
+    }
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+    QString fileName = QFileDialog::getSaveFileName(obj->widget);
+    if(fileName.size() > 0) {
+        QByteArray array = fileName.toLocal8Bit();
+        const char *cstr = array.constData();
+        return strdup(cstr);
+    } else {
+        return NULL;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/qt/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,39 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WINDOW_H
+#define	WINDOW_H
+
+#include "../ui/window.h"
+
+#include <QMainWindow>
+
+
+
+#endif	/* WINDOW_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,85 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_BUTTON_H
+#define	UI_BUTTON_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiButtonArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    const char* label;
+    const char* stockid;
+    ui_callback onclick;
+    void* onclickdata;
+} UiButtonArgs;
+
+typedef struct UiToggleArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    const char* label;
+    const char* stockid;
+    UiInteger* value;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+} UiToggleArgs;
+   
+#define ui_button(obj, ...) ui_button_create(obj, (UiButtonArgs){ __VA_ARGS__ } )
+#define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_checkbox(obj, ...) ui_checkbox_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_switch(obj, ...) ui_switch_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args);
+UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args);
+
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_BUTTON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,184 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_CONTAINER_H
+#define UI_CONTAINER_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+   
+typedef enum UiSubContainerType {
+    UI_CONTAINER_VBOX = 0,
+    UI_CONTAINER_HBOX,
+    UI_CONTAINER_GRID
+} UiSubContainerType;
+
+typedef enum UiTabViewType {
+    UI_TABVIEW_DEFAULT = 0,
+    UI_TABVIEW_DOC,
+    UI_TABVIEW_NAVIGATION_SIDE,
+    UI_TABVIEW_NAVIGATION_TOP,
+    UI_TABVIEW_NAVIGATION_TOP2,
+    UI_TABVIEW_INVISIBLE
+} UiTabViewType;
+
+typedef struct UiContainerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+} UiContainerArgs;
+
+typedef struct UiFrameArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    UiSubContainerType subcontainer;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    const char* label;
+    UiBool isexpanded;
+} UiFrameArgs;
+
+typedef struct UiTabViewArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    UiTabViewType tabview;
+
+    UiSubContainerType subcontainer;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    const char* label;
+    UiBool isexpanded;
+} UiTabViewArgs;
+
+
+
+#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox(obj, ...) for(ui_vbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox(obj, ...) for(ui_hbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid(obj, ...) for(ui_grid_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame(obj, ...) for(ui_frame_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander(obj, ...) for(ui_expander_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow(obj, ...) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview(obj, ...) for(ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid0(obj) for(ui_grid_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame0(obj) for(ui_frame_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander0(obj) for(ui_expande_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview0(obj) for(ui_tabview_create(obj, (UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
+
+UIEXPORT void ui_end(UiObject *obj);
+    
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args);
+
+UIEXPORT void ui_tab_create(UiObject* obj, const char* title);
+
+UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj);
+
+UIEXPORT UIWIDGET ui_sidebar(UiObject *obj);
+
+UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max);
+UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max);
+
+UIEXPORT UIWIDGET ui_tabview_deprecated(UiObject *obj);
+
+UIEXPORT void ui_select_tab(UIWIDGET tabview, int tab);
+
+// box container layout functions
+UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
+// grid container layout functions
+UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_width(UiObject *obj, int width);
+UIEXPORT void ui_layout_height(UiObject* obj, int width);
+UIEXPORT void ui_layout_colspan(UiObject *obj, int cols);
+UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows);
+UIEXPORT void ui_newline(UiObject *obj);
+
+
+UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
+
+UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
+
+
+/* used for macro */
+UIEXPORT void ui_container_begin_close(UiObject *obj);
+UIEXPORT int ui_container_finish(UiObject *obj);
+
+#define UI_APPLY_LAYOUT1(obj, args) \
+    if(args.fill != UI_DEFAULT) ui_layout_fill(obj, args.fill == UI_ON ? 1 : 0 ); \
+    if(args.hexpand) ui_layout_hexpand(obj, 1); \
+    if(args.vexpand) ui_layout_vexpand(obj, 1); \
+    if(args.colspan > 0) ui_layout_colspan(obj, args.colspan); \
+    if(args.rowspan > 0) ui_layout_rowspan(obj, args.rowspan); \
+    /*force caller to add ';'*/(void)0
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_CONTAINER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/display.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,117 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * display widgets without user input
+ */
+
+#ifndef UI_DISPLAY_H
+#define UI_DISPLAY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+enum UiAlignment {
+    UI_ALIGN_DEFAULT = 0,
+    UI_ALIGN_LEFT,
+    UI_ALIGN_RIGHT,
+    UI_ALIGN_CENTER
+};
+
+typedef enum UiAlignment UiAlignment;
+
+typedef struct UiLabelArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    const char* label;
+    UiAlignment align;
+    UiString* value;
+    const char* varname;
+} UiLabelArgs;
+
+typedef struct UiProgressbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+    int width;
+
+    double min;
+    double max;
+    UiDouble* value;
+    const char* varname;
+} UiProgressbarArgs;
+
+typedef struct UiProgressbarSpinnerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    UiInteger* value;
+    const char* varname;
+} UiProgressbarSpinnerArgs;
+
+/* label widgets */
+
+#define ui_label(obj, ...) ui_label_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_llabel(obj, ...) ui_llabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_rlabel(obj, ...) ui_rlabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+
+
+UIEXPORT UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args);
+
+UIWIDGET ui_space(UiObject *obj);
+UIWIDGET ui_separator(UiObject *obj);
+
+/* progress bar/spinner */
+
+#define ui_progressbar(obj, ...) ui_progressbar_create(obj, (UiProgressbarArgs) { __VA_ARGS__ } )
+#define ui_progressspinner(obj, ...) ui_progressspinner_create(obj, (UiProgressbarSpinnerArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args);
+UIEXPORT UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DISPLAY_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/dnd.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_DND_H
+#define UI_DND_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define UI_DND_FILE_TARGET "XdndDirectSave0"
+    
+UIEXPORT void ui_selection_settext(UiDnD *sel, char *str, int len);
+UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm);
+
+UIEXPORT char* ui_selection_gettext(UiDnD *sel);
+UIEXPORT char** ui_selection_geturis(UiDnD *sel, size_t *nelm);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DND_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/entry.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,54 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ENTRY_H
+#define UI_ENTRY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i);
+UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d);
+UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r);
+
+UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname);
+UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname);
+UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname);
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
+void ui_spinner_setdigits(UIWIDGET spinner, int digits);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ENTRY_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,73 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_GRAPHICS_H
+#define	UI_GRAPHICS_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGraphics      UiGraphics;
+typedef struct UiTextLayout    UiTextLayout;
+
+typedef void(*ui_drawfunc)(UiEvent*, UiGraphics*, void*);
+
+struct UiGraphics {
+    int width;
+    int height;
+};
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata);
+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
+UiTextLayout* ui_text(UiGraphics *g);
+void ui_text_free(UiTextLayout *text);
+void ui_text_setstring(UiTextLayout *layout, char *str);
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len);
+void ui_text_setfont(UiTextLayout *layout, char *font, int size);
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height);
+void ui_text_setwidth(UiTextLayout *layout, int width);
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue);
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2);
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill);
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_GRAPHICS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/image.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_IMAGE_H
+#define UI_IMAGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	/*
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
+UIEXPORT UiIcon* ui_imageicon(const char* file);
+UIEXPORT void ui_icon_free(UiIcon* icon);
+*/
+
+/*
+UiIcon* ui_icon(const char *name, int size);
+UiIcon* ui_icon_unscaled(const char *name, int size);
+void ui_free_icon(UiIcon *icon);
+
+UiImage* ui_icon_image(UiIcon *icon);
+UiImage* ui_image(const char *filename);
+UiImage* ui_named_image(const char *filename, const char *name);
+UiImage* ui_load_image_from_path(const char *path, const char *name);
+void ui_free_image(UiImage *img);
+*/
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_IMAGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,124 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_MENU_H
+#define	UI_MENU_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiMenuItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	ui_callback onclick;
+	void* onclickdata;
+
+	const int* groups;
+} UiMenuItemArgs;
+
+typedef struct UiMenuToggleItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	const char* varname;
+	ui_callback onchange;
+	void* onchangedata;
+
+	const int* groups;
+} UiMenuToggleItemArgs;
+
+typedef struct UiMenuItemListArgs {
+	const char* varname;
+	ui_callback onselect;
+	void* onselectdata;
+} UiMenuItemListArgs;
+
+#define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close())
+
+#define ui_menuitem(...) ui_menuitem_create((UiMenuItemArgs){ __VA_ARGS__ })
+#define ui_menu_toggleitem(...) ui_menu_toggleitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+#define ui_menu_radioitem(...) ui_menu_radioitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+
+UIEXPORT void ui_menu_create(const char* label);
+UIEXPORT void ui_menuitem_create(UiMenuItemArgs args);
+UIEXPORT void ui_menu_toggleitem_create(UiMenuToggleItemArgs args);
+UIEXPORT void ui_menu_radioitem_create(UiMenuToggleItemArgs args);
+
+UIEXPORT void ui_menuseparator();
+
+UIEXPORT void ui_menu_itemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_toggleitemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_radioitemlist_create(UiMenuItemListArgs args);
+
+UIEXPORT void ui_menu_deprecated(char *label);
+UIEXPORT void ui_submenu_deprecated(char *label); // deprecated
+UIEXPORT void ui_submenu_end_deprecated(); // deprecated
+
+UIEXPORT void ui_menuitem_deprecated(char *label, ui_callback f, void *userdata);
+UIEXPORT void ui_menuitem_st(char *stockid, ui_callback f, void *userdata);
+UIEXPORT void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...);
+UIEXPORT void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...);
+
+
+UIEXPORT void ui_checkitem_deprecated(char *label, ui_callback f, void *userdata);
+UIEXPORT void ui_checkitem_nv_deprecated(char *label, char *vname);
+
+UIEXPORT void ui_menuitem_list_deprecated(UiList *items, ui_callback f, void *userdata);
+
+UIEXPORT void ui_menu_end(void);
+
+/*
+ * widget menu functions
+ */
+UIEXPORT UIMENU ui_contextmenu(UiObject *obj);
+UIEXPORT UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget);
+UIEXPORT void ui_contextmenu_popup(UIMENU menu);
+
+UIEXPORT void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata);
+UIEXPORT void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata);
+UIEXPORT void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...);
+UIEXPORT void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...);
+
+
+// used for macro
+UIEXPORT void ui_menu_close(void);
+UIEXPORT int ui_menu_is_open(void);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/properties.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_PROPERTIES_H
+#define	UI_PROPERTIES_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct CxMap UiProperties;
+    
+char* ui_getappdir();
+char* ui_configfile(char *name);
+
+char* ui_get_property(char *name);
+void  ui_set_property(char *name, char *value);
+
+void ui_set_default_property(char *name, char *value);
+
+void ui_locales_dir(char *path);
+void ui_pixmaps_dir(char *path);
+
+void ui_load_lang(char *locale);
+void ui_load_lang_def(char *locale, char *default_locale);
+    
+char* uistr(char *name);
+char* uistr_n(char *name);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_PROPERTIES_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/range.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_RANGE_H
+#define UI_RANGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_RANGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/stock.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,89 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_STOCK_H
+#define	UI_STOCK_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+// motif stock ids
+#if UI_MOTIF || UI_COCOA || UI_QT4 || UI_QT5
+    
+#define UI_STOCK_NEW                    "ui.stock.New"
+#define UI_STOCK_OPEN                   "ui.stock.Open"
+#define UI_STOCK_SAVE                   "ui.stock.Save"
+#define UI_STOCK_SAVE_AS                "ui.stock.SaveAs"
+#define UI_STOCK_REVERT_TO_SAVED        "ui.stock.RevertToSaved"
+#define UI_STOCK_GO_BACK                "ui.stock.GoBack"
+#define UI_STOCK_GO_FORWARD             "ui.stock.GoForward"
+#define UI_STOCK_ADD                    "ui.stock.Add"
+#define UI_STOCK_CLOSE                  "ui.stock.Close"
+    
+#define UI_STOCK_UNDO                   "ui.stock.Undo"
+#define UI_STOCK_REDO                   "ui.stock.Redo"
+#define UI_STOCK_CUT                    "ui.stock.Cut"
+#define UI_STOCK_COPY                   "ui.stock.Copy"
+#define UI_STOCK_PASTE                  "ui.stock.Paste"
+#define UI_STOCK_DELETE                 "ui.stock.Delete"
+    
+#endif
+    
+#if UI_GTK2 || UI_GTK3
+    
+#define UI_STOCK_NEW                    GTK_STOCK_NEW
+#define UI_STOCK_OPEN                   GTK_STOCK_OPEN
+#define UI_STOCK_SAVE                   GTK_STOCK_SAVE
+#define UI_STOCK_SAVE_AS                GTK_STOCK_SAVE_AS
+#define UI_STOCK_REVERT_TO_SAVED        GTK_STOCK_REVERT_TO_SAVED
+#define UI_STOCK_UNDO                   GTK_STOCK_UNDO
+#define UI_STOCK_REDO                   GTK_STOCK_REDO
+#define UI_STOCK_GO_BACK                GTK_STOCK_GO_BACK
+#define UI_STOCK_GO_FORWARD             GTK_STOCK_GO_FORWARD
+#define UI_STOCK_ADD                    GTK_STOCK_ADD
+#define UI_STOCK_CLOSE                  GTK_STOCK_CLOSE
+
+#define UI_STOCK_UNDO                   GTK_STOCK_UNDO
+#define UI_STOCK_REDO                   GTK_STOCK_REDO
+#define UI_STOCK_CUT                    GTK_STOCK_CUT
+#define UI_STOCK_COPY                   GTK_STOCK_COPY
+#define UI_STOCK_PASTE                  GTK_STOCK_PASTE
+#define UI_STOCK_DELETE                 GTK_STOCK_DELETE
+    
+#endif
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_STOCK_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,111 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TEXT_H
+#define	UI_TEXT_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiTextFieldArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+    int width;
+
+    UiString* value;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+} UiTextFieldArgs;
+
+typedef struct UiPathElmRet {
+    char* name;
+    size_t name_len;
+    char* path;
+    size_t path_len;
+} UiPathElm;
+
+typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void* data);
+
+
+
+typedef struct UiPathTextFieldArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    UiString *value;
+    const char* varname;
+
+    ui_pathelm_func getpathelm;
+    void* getpathelmdata;
+
+    ui_callback onactivate;
+    void* onactivatedata;
+    
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropsdata;
+} UiPathTextFieldArgs;
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value);
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname);
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+
+void ui_text_undo(UiText *value);
+void ui_text_redo(UiText *value);
+
+#define ui_textfield(obj, ...) ui_textfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_frameless_textfield(obj, ...) ui_frameless_field_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_passwordfield(obj, ...) ui_passwordfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_path_textfield(obj, ...) ui_path_textfield_create(obj, (UiPathTextFieldArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args);
+
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args);
+        
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_TEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,106 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLBAR_H
+#define	UI_TOOLBAR_H
+
+#include "toolkit.h"
+#include <stdarg.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolbarItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	ui_callback onclick;
+	void* onclickdata;
+} UiToolbarItemArgs;
+
+typedef struct UiToolbarToggleItemArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+
+	const char* varname;
+	ui_callback onchange;
+	void* onchangedata;
+} UiToolbarToggleItemArgs;
+
+typedef struct UiToolbarMenuArgs {
+	const char* label;
+	const char* stockid;
+	const char* icon;
+} UiToolbarMenuArgs;
+
+enum UiToolbarPos {
+	UI_TOOLBAR_LEFT = 0,
+	UI_TOOLBAR_CENTER,
+	UI_TOOLBAR_RIGHT
+};
+
+#define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, (UiToolbarItemArgs){ __VA_ARGS__ } )
+#define ui_toolbar_toggleitem(name, ...) ui_toolbar_toggleitem_create(name, (UiToolbarToggleItemArgs){ __VA_ARGS__ } )
+
+#define ui_toolbar_menu(obj, ...) for(ui_toolbar_menu_create(obj, (UiToolbarMenuArgs){ __VA_ARGS__ });ui_menu_is_open();ui_menu_close())
+
+
+UIEXPORT void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args);
+UIEXPORT void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args);
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args);
+
+
+void ui_toolitem_deprecated(char *name, char *label, ui_callback f, void *udata);
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata);
+void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *udata);
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...);
+void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...);
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata);
+
+void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i);
+void ui_toolitem_toggle_st_deprecated(const char *name, const char *stockid, UiInteger *i);
+void ui_toolitem_toggle_nv_deprecated(const char *name, const char *label, const char *img, const char *intvar);
+void ui_toolitem_toggle_stnv_deprecated(const char *name, const char *stockid, const char *intvar);
+
+void ui_toolbar_combobox_deprecated(char *name, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+void ui_toolbar_combobox_str_deprecated(char *name, UiList *list, ui_callback f, void *udata);
+void ui_toolbar_combobox_nv_deprecated(char *name, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+UIEXPORT void ui_toolbar_add_default(const char *name, enum UiToolbarPos pos);
+
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_TOOLBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,451 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLKIT_H
+#define	UI_TOOLKIT_H
+
+#include <inttypes.h>
+
+#ifdef UI_COCOA
+
+#ifdef __OBJC__
+#import <Cocoa/Cocoa.h>
+#define UIWIDGET NSView*
+#define UIMENU   NSMenu*
+#else
+typedef void* UIWIDGET;
+typedef void* UIMENU;
+#endif
+
+#elif UI_GTK2 || UI_GTK3
+
+#include <gtk/gtk.h>
+#define UIWIDGET GtkWidget*
+#define UIMENU   GtkMenu*
+#define UI_GTK
+
+#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 */
+#define UIWIDGET void*
+#define UIMENU   void*
+#endif
+
+#elif UI_WINUI
+
+#define UIEXPORT __declspec(dllexport) 
+
+#ifdef __cplusplus
+
+#ifndef UI_WINUI_PCH
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#endif
+
+class UiWindow {
+public:
+    winrt::Microsoft::UI::Xaml::Window window { nullptr };
+
+    UiWindow(winrt::Microsoft::UI::Xaml::Window& win);
+};
+
+class UiWidget {
+public:
+    winrt::Microsoft::UI::Xaml::UIElement uielement;
+    void* data1 = nullptr;
+    void* data2 = nullptr;
+    void* data3 = nullptr;
+    UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm);
+
+};
+
+#define UIWIDGET UiWidget*
+#define UIWINDOW UiWindow*
+#define UIMENU   void*
+
+/*
+// winrt::Microsoft::UI::Xaml::UIElement
+#define UIWIDGET void*
+// winrt::Microsoft::UI::Xaml::Window
+#define UIWINDOW void*
+#define UIMENU   void*
+*/
+
+#else
+#define UIWIDGET void*
+#define UIWINDOW void*
+#define UIMENU   void*
+#endif
+
+
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef UIEXPORT
+#define UIEXPORT
+#endif
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define UI_GROUP_SELECTION  20000
+
+#define UI_GROUPS(...) (int[]){ __VA_ARGS__, -1 }
+    
+/* public types */
+typedef int UiBool;
+
+typedef struct UiObject     UiObject;
+typedef struct UiEvent      UiEvent;
+typedef struct UiMouseEvent UiMouseEvent;
+typedef struct UiObserver   UiObserver;
+
+typedef struct UiInteger    UiInteger;
+typedef struct UiDouble     UiDouble;
+typedef struct UiString     UiString;
+typedef struct UiText       UiText;
+typedef struct UiList       UiList;
+typedef struct UiRange      UiRange;
+
+typedef struct UiStr        UiStr;
+
+/* begin opaque types */
+typedef struct UiContext    UiContext;
+typedef struct UiContainer  UiContainer;
+
+typedef struct UiIcon       UiIcon;
+typedef struct UiImage      UiImage;
+
+typedef struct UiDnD        UiDnD;
+/* end opaque types */
+
+typedef struct UiTabbedPane UiTabbedPane;
+
+typedef enum UiTri UiTri;
+
+enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
+
+
+  
+typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
+
+typedef void*(*ui_getvaluefunc)(void*, int);
+
+typedef int(*ui_threadfunc)(void*);
+
+typedef void(*ui_freefunc)(void*);
+
+typedef void(*ui_enablefunc)(void*, UiBool);
+
+struct UiObject {
+    /*
+     * native widget
+     */
+    UIWIDGET    widget;
+
+#ifdef UI_WINUI
+    /*
+     * native window object 
+     */
+    UIWINDOW    wobj;
+#endif
+    
+    /*
+     * user window data
+     */
+    void        *window;
+    
+    /*
+     * window context
+     */
+    UiContext   *ctx;
+    
+    /*
+     * container interface
+     */
+    UiContainer *container;
+    
+    /*
+     * next container object
+     */
+    UiObject    *next;
+};
+
+struct UiTabbedPane {
+    /*
+     * native widget
+     */
+    UIWIDGET  widget;
+    
+    /*
+     * current document
+     */
+    void      *document;
+    
+    /*
+     * parent context
+     */
+    UiContext *ctx;
+};
+
+struct UiEvent {
+    UiObject *obj;
+    void     *document;
+    void     *window;
+    void     *eventdata;
+    int      intval;
+};
+
+struct UiMouseEvent {
+    int x;
+    int y;
+    enum UiMouseEventType type;
+    int button;
+};
+
+struct UiObserver {
+    ui_callback callback;
+    void *data;
+    UiObserver *next;
+};
+
+struct UiStr {
+    char *ptr;
+    void (*free)(void *v);
+};
+
+struct UiInteger {
+    int64_t  (*get)(UiInteger*);
+    void (*set)(UiInteger*, int64_t);
+    void *obj;
+    
+    int64_t value;
+    UiObserver *observers;
+};
+
+struct UiDouble {
+    double  (*get)(UiDouble*);
+    void (*set)(UiDouble*, double);
+    void *obj;
+    
+    double value;
+    UiObserver *observers;
+};
+
+struct UiString {
+    char* (*get)(UiString*);
+    void  (*set)(UiString*, const char*);
+    void  *obj;
+    
+    UiStr value;
+    UiObserver *observers;
+};
+
+struct UiText {
+    void  (*set)(UiText*, 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  (*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;
+    // TODO: replacefunc, ...
+    UiObserver *observers;
+};
+    
+/*
+ * abstract list
+ */
+struct UiList {
+    /* get the first element */
+    void*(*first)(UiList *list);
+    /* get the next element */
+    void*(*next)(UiList *list);
+    /* get the nth element */
+    void*(*get)(UiList *list, int i);
+    /* get the number of elements */
+    int(*count)(UiList *list);
+    /* iterator changes after first() next() and get() */
+    void *iter;
+    /* private - implementation dependent */
+    void *data;
+    
+    /* binding function */
+    void (*update)(UiList *list, int i);
+    /* binding object */
+    void *obj;
+    
+    /* list of observers */
+    UiObserver *observers;
+};
+
+struct UiRange {
+    double (*get)(UiRange *range);
+    void   (*set)(UiRange *range, double value);
+    void   (*setrange)(UiRange *range, double min, double max);
+    void   (*setextent)(UiRange *range, double extent);
+    double value;
+    double min;
+    double max;
+    double extent;
+    void   *obj;
+    /* list of observers */
+    UiObserver *observers;
+};
+
+enum UiTri {
+    UI_DEFAULT = 0,
+    UI_ON,
+    UI_OFF
+};
+
+
+UIEXPORT void ui_init(const char *appname, int argc, char **argv);
+UIEXPORT const char* ui_appname();
+
+UIEXPORT UiContext* ui_global_context(void);
+
+UIEXPORT void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
+
+UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
+UIEXPORT void ui_onopen(ui_callback f, void *userdata);
+UIEXPORT void ui_onexit(ui_callback f, void *userdata);
+
+UIEXPORT void ui_main();
+UIEXPORT void ui_show(UiObject *obj);
+UIEXPORT void ui_close(UiObject *obj);
+
+UIEXPORT void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd);
+
+UIEXPORT void* ui_document_new(size_t size);
+UIEXPORT void  ui_document_destroy(void *doc);
+
+UIEXPORT void ui_set_document(UiObject *obj, void *document);    // deprecated
+UIEXPORT void ui_detach_document(UiObject *obj);                 // deprecated
+UIEXPORT void* ui_get_document(UiObject *obj);                   // deprecated
+UIEXPORT void ui_set_subdocument(void *document, void *sub);     // deprecated
+UIEXPORT void ui_detach_subdocument(void *document, void *sub);  // deprecated
+UIEXPORT void* ui_get_subdocument(void *document);               // deprecated
+
+UIEXPORT UiContext* ui_document_context(void *doc);
+
+UIEXPORT void ui_attach_document(UiContext *ctx, void *document);
+UIEXPORT void ui_detach_document2(UiContext *ctx, void *document);
+
+UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
+
+UIEXPORT void ui_set_group(UiContext *ctx, int group);
+UIEXPORT void ui_unset_group(UiContext *ctx, int group);
+UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups);
+    
+UIEXPORT void* ui_malloc(UiContext *ctx, size_t size);
+UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
+UIEXPORT void  ui_free(UiContext *ctx, void *ptr);
+UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+
+// types
+
+UIEXPORT UiInteger* ui_int_new(UiContext *ctx, char *name);
+UIEXPORT UiDouble* ui_double_new(UiContext *ctx, char *name);
+UIEXPORT UiString* ui_string_new(UiContext *ctx, char *name);
+UIEXPORT UiText* ui_text_new(UiContext *ctx, char *name);
+UIEXPORT UiRange* ui_range_new(UiContext *ctx, char *name);
+
+UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data);
+UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer);
+UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data);
+UIEXPORT void ui_notify(UiObserver *observer, void *data);
+UIEXPORT void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data);
+UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event);
+
+
+UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name);
+UIEXPORT void* ui_list_first(UiList *list);
+UIEXPORT void* ui_list_next(UiList *list);
+UIEXPORT void* ui_list_get(UiList *list, int i);
+UIEXPORT int   ui_list_count(UiList *list);
+UIEXPORT void  ui_list_append(UiList *list, void *data);
+UIEXPORT void  ui_list_prepend(UiList *list, void *data);
+UIEXPORT void ui_list_clear(UiList *list);
+UIEXPORT void  ui_list_addobsv(UiList *list, ui_callback f, void *data);
+UIEXPORT void  ui_list_notify(UiList *list);
+
+UIEXPORT void ui_clipboard_set(char *str);
+UIEXPORT char* ui_clipboard_get();
+
+UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove?
+
+// general widget functions
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled);
+UIEXPORT void ui_set_show_all(UIWIDGET widget, int value);
+UIEXPORT void ui_set_visible(UIWIDGET widget, int visible);
+
+
+
+
+
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
+UIEXPORT UiIcon* ui_imageicon(const char* file);
+UIEXPORT void ui_icon_free(UiIcon* icon);
+
+UIEXPORT UiIcon* ui_foldericon(size_t size);
+UIEXPORT UiIcon* ui_fileicon(size_t size);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_TOOLKIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/tree.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,162 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TREE_H
+#define	UI_TREE_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+typedef struct UiModel         UiModel;
+typedef struct UiListCallbacks UiListCallbacks;
+typedef struct UiListSelection UiListSelection;
+typedef struct UiListDnd       UiListDnd;
+
+typedef struct UiListArgs      UiListArgs;
+
+typedef enum UiModelType {
+    UI_STRING = 0,
+    UI_INTEGER,
+    UI_ICON,
+    UI_ICON_TEXT,
+} UiModelType;
+
+struct UiModel {
+    /*
+     * number of columns
+     */
+    int columns;
+    
+    /*
+     * array of column types
+     * array length is the number of columns
+     */
+    UiModelType *types;
+    
+    /*
+     * array of column titles
+     * array length is the number of columns
+     */
+    char **titles;
+    
+    /*
+     * function for translating model data to view data
+     * first argument is the pointer returned by UiList->get or UiTree->get
+     * second argument is the column index
+     * TODO: return
+     */
+    void*(*getvalue)(void*, int);
+};
+
+struct UiListCallbacks {
+    /*
+     * selection callback
+     */
+    ui_callback activate;
+    
+    /*
+     * cursor callback
+     */
+    ui_callback selection;
+    
+    /*
+     * userdata for all callbacks
+     */
+    void *userdata;
+};
+
+struct UiListSelection {
+    /*
+     * number of selected items
+     */
+    int count;
+    
+    /*
+     * indices of selected rows
+     */
+    int *rows;
+};
+
+struct UiListDnd {
+    UiListSelection selection;
+    UiDnD *dnd;
+};
+
+struct UiListArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    int colspan;
+    int rowspan;
+
+    UiList* list;
+    const char* varname;
+    UiModel* model;
+    ui_getvaluefunc getvalue;
+    ui_callback onactivate;
+    void* onactivatedata;
+    ui_callback onselection;
+    void* onselectiondata;
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropsdata;
+    UiBool multiselection;
+};
+
+UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
+UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
+UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
+
+#define ui_listview(obj, ...) ui_listview_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_combobox(obj, ...) ui_combobox_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, (UiListArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args);
+
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UI_TREE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/ui.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_H
+#define	UI_H
+
+#include "toolkit.h"
+#include "container.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "window.h"
+#include "stock.h"
+#include "button.h"
+#include "text.h"
+#include "properties.h"
+#include "tree.h"
+#include "graphics.h"
+#include "entry.h"
+#include "range.h"
+#include "image.h"
+#include "display.h"
+#include "dnd.h"
+
+#endif	/* UI_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WINDOW_H
+#define	UI_WINDOW_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UIEXPORT UiObject* ui_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_simplewindow(char *title, void *window_data);
+
+char* ui_openfiledialog(UiObject *obj);
+char* ui_savefiledialog(UiObject *obj);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* WINDOW_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.idl	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+namespace winui
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Application
+    x:Class="winui.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:winui">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
+                <!-- Other merged dictionaries here -->
+            </ResourceDictionary.MergedDictionaries>
+            <!-- Other app resources here -->
+            <SolidColorBrush x:Key="WindowCaptionBackground">Transparent</SolidColorBrush>
+            <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">Transparent</SolidColorBrush>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
+
+#include "App.xaml.h"
+#include "MainWindow.xaml.h"
+
+#include "toolkit.h"
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::Navigation;
+using namespace winui;
+using namespace winui::implementation;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+/// <summary>
+/// Initializes the singleton application object.  This is the first line of authored code
+/// executed, and as such is the logical equivalent of main() or WinMain().
+/// </summary>
+App::App()
+{
+    InitializeComponent();
+
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
+    UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)
+    {
+        if (IsDebuggerPresent())
+        {
+            auto errorMessage = e.Message();
+            __debugbreak();
+        }
+    });
+#endif
+}
+
+/// <summary>
+/// Invoked when the application is launched.
+/// </summary>
+/// <param name="e">Details about the launch request and process.</param>
+void App::OnLaunched(LaunchActivatedEventArgs const&)
+{
+    ui_app_run_startup();
+    //window = make<MainWindow>();
+    //window.Activate();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/App.xaml.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "App.xaml.g.h"
+
+namespace winrt::winui::implementation
+{
+    struct App : AppT<App>
+    {
+        App();
+
+        void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
+
+    private:
+        winrt::Microsoft::UI::Xaml::Window window{ nullptr };
+    };
+}
Binary file ui/winui/Assets/LockScreenLogo.scale-200.png has changed
Binary file ui/winui/Assets/SplashScreen.scale-200.png has changed
Binary file ui/winui/Assets/Square150x150Logo.scale-200.png has changed
Binary file ui/winui/Assets/Square44x44Logo.scale-200.png has changed
Binary file ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png has changed
Binary file ui/winui/Assets/StoreLogo.png has changed
Binary file ui/winui/Assets/Wide310x150Logo.scale-200.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.idl	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+namespace winui
+{
+    [default_interface]
+    runtimeclass MainWindow : Microsoft.UI.Xaml.Window
+    {
+        MainWindow();
+        Int32 MyProperty;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Window
+    x:Class="winui.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:winui"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d">
+
+    <Window.SystemBackdrop>
+        <MicaBackdrop />
+    </Window.SystemBackdrop>
+
+    <Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*"  Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+        <StackPanel x:Name="AppTitleBar" Orientation="Horizontal" Grid.Column="0" Grid.Row="0" Padding="10,5,5,10">
+            <TextBlock x:Name="AppTitleTextBlock" Text="Window Title" CanDrag="True"></TextBlock>
+        </StackPanel>
+    </Grid>
+</Window>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "MainWindow.xaml.h"
+#if __has_include("MainWindow.g.cpp")
+#include "MainWindow.g.cpp"
+#endif
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace winrt::winui::implementation
+{
+    MainWindow::MainWindow()
+    {
+        InitializeComponent();
+        //ExtendsContentIntoTitleBar(true);
+        //SetTitleBar(AppTitleBar());
+    }
+
+    int32_t MainWindow::MyProperty()
+    {
+        throw hresult_not_implemented();
+    }
+
+    void MainWindow::MyProperty(int32_t /* value */)
+    {
+        throw hresult_not_implemented();
+    }
+
+    void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
+    {
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/MainWindow.xaml.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "MainWindow.g.h"
+
+namespace winrt::winui::implementation
+{
+    struct MainWindow : MainWindowT<MainWindow>
+    {
+        MainWindow();
+
+        int32_t MyProperty();
+        void MyProperty(int32_t value);
+
+        void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
+    };
+}
+
+namespace winrt::winui::factory_implementation
+{
+    struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
+    {
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/Package.appxmanifest	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+  IgnorableNamespaces="uap rescap">
+
+  <Identity
+    Name="e3554f39-079b-4a0b-aa08-4ce5c4306644"
+    Publisher="CN=Olaf"
+    Version="1.0.0.0" />
+
+  <mp:PhoneIdentity PhoneProductId="e3554f39-079b-4a0b-aa08-4ce5c4306644" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+  <Properties>
+    <DisplayName>winui</DisplayName>
+    <PublisherDisplayName>Olaf</PublisherDisplayName>
+    <Logo>Assets\StoreLogo.png</Logo>
+  </Properties>
+
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+  </Dependencies>
+
+  <Resources>
+    <Resource Language="x-generate"/>
+  </Resources>
+
+  <Applications>
+    <Application Id="App"
+      Executable="$targetnametoken$.exe"
+      EntryPoint="$targetentrypoint$">
+      <uap:VisualElements
+        DisplayName="winui"
+        Description="winui"
+        BackgroundColor="transparent"
+        Square150x150Logo="Assets\Square150x150Logo.png"
+        Square44x44Logo="Assets\Square44x44Logo.png">
+        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
+        <uap:SplashScreen Image="Assets\SplashScreen.png" />
+      </uap:VisualElements>
+    </Application>
+  </Applications>
+
+  <Capabilities>
+    <rescap:Capability Name="runFullTrust" />
+  </Capabilities>
+</Package>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/app.manifest	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="winui.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. 
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 
+      
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+    </application>
+  </compatibility>
+  
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+</assembly>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/appmenu.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,172 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "appmenu.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#include "util.h"
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+static void add_top_menu_widget(MenuBar &parent, int i, UiMenuItemI* item, UiObject* obj);
+
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_radioitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menucheckitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+static void add_menuradioitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);
+
+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      */ NULL, // TODO
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ NULL, // TODO
+    /* UI_MENU_RADIOITEM_LIST  */ NULL, // TODO
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj) {
+	MenuBar mb = MenuBar();
+
+    UiMenu* menus_begin = uic_get_menu_list();
+
+    UiMenu* ls = menus_begin;
+    while (ls) {
+        UiMenu* menu = ls;
+        add_top_menu_widget(mb, 0, &menu->item, obj);
+
+        ls = (UiMenu*)ls->item.next;
+    }
+
+	return mb;
+}
+
+static void add_top_menu_widget(MenuBar& parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenu* menu = (UiMenu*)item;
+
+    MenuBarItem mi = MenuBarItem();
+    wchar_t* wlabel = str2wstr(menu->label, NULL);
+    mi.Title(wlabel);
+    free(wlabel);
+
+    UiMenuItemI* it = menu->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](mi.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    parent.Items().Append(mi);
+}
+
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenu* menu = (UiMenu*)item;
+
+    MenuFlyoutSubItem mi = MenuFlyoutSubItem();
+    wchar_t* wlabel = str2wstr(menu->label, NULL);
+    mi.Text(wlabel);
+    free(wlabel);
+
+    parent.Append(mi);
+
+    UiMenuItemI* it = menu->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](mi.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    
+}
+
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+    UiMenuItem* it = (UiMenuItem*)item;
+    
+    MenuFlyoutItem mi = MenuFlyoutItem();
+    wchar_t* wlabel = str2wstr(it->label, NULL);
+    mi.Text(wlabel);
+    free(wlabel);
+
+    parent.Append(mi);
+}
+
+static void add_menuitem_st_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+
+}
+
+static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+
+}
+
+static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+
+}
+
+static void add_checkitemnv_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+
+}
+
+static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {
+
+}
+
+
+
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef) {
+    MenuFlyout flyout = MenuFlyout();
+
+    UiMenuItemI* it = menudef->items_begin;
+    int index = 0;
+    while (it) {
+        createMenuItem[it->type](flyout.Items(), index, it, obj);
+
+        it = it->next;
+        index++;
+    }
+
+    return flyout;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/appmenu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../common/menu.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
+
+typedef void(*ui_menu_add_f)(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int, UiMenuItemI*, UiObject*);
+
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj);
+
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/button.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,346 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "button.h"
+
+#include "util.h"
+#include "container.h"
+#include "icons.h"
+
+#include "../common/object.h"
+#include "../common/context.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+
+
+static void set_button_label(ButtonBase button, const char* label, const char* stockid) {
+	if (label) {
+		wchar_t* wlabel = str2wstr(label, nullptr);
+		button.Content(box_value(wlabel));
+		free(wlabel);
+	}
+	// TODO: stockid
+}
+
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// create button with label
+	Button button = Button();
+	set_button_label(button, args.label, args.stockid);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// register callback
+	if (args.onclick) {
+		ui_callback cbfunc = args.onclick;
+		void* cbdata = args.onclickdata;
+		button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.window = obj->window;
+			evt.document = obj->ctx->document;
+			evt.eventdata = nullptr;
+			evt.intval = 0;
+			cbfunc(&evt, cbdata);
+			});
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+
+void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) {
+	button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) {
+	button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) {
+	ui_callback callback = args.onchange;
+	void* cbdata = args.onchangedata;
+	if (callback) {
+		button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, true);
+			callback(&evt, cbdata);
+			});
+		button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, false);
+			callback(&evt, cbdata);
+			});
+	}
+}
+
+// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same
+// code again, because although everything is basically the same, it is named differently
+static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) {
+	button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) {
+		UiInteger* i = (UiInteger*)var->value;
+		UiEvent evt = ui_create_int_event(obj, i->get(i));
+		ui_notify_evt(i->observers, &evt);
+		});
+}
+
+static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) {
+	ui_callback callback = args.onchange;
+	void* cbdata = args.onchangedata;
+	if (callback) {
+		button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt = ui_create_int_event(obj, button.IsOn());
+			callback(&evt, cbdata);
+			});
+	}
+}
+
+
+static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// set label
+	set_button_label(button, args.label, args.stockid);
+	togglebutton_register_callback(button, obj, args);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// bind variable
+	UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+		value->obj = widget;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+
+		// listener for notifying observers
+		togglebutton_register_checked_observers(button, obj, var);
+		togglebutton_register_unchecked_observers(button, obj, var);
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+	ToggleButton button = ToggleButton();
+	return create_togglebutton(obj, button, args);
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+	CheckBox button = CheckBox();
+	return create_togglebutton(obj, button, args);
+}
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+	ToggleSwitch button = ToggleSwitch();
+	if (args.label) {
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		button.Header(box_value(wlabel));
+		free(wlabel);
+	}
+	switch_register_callback(button, obj, args);
+
+	UiObject* current = uic_current_obj(obj);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// bind variable
+	UiVar* var = nullptr;
+	if (args.value) {
+		var = uic_create_value_var(current->ctx, args.value);
+	}
+	else if (args.varname) {
+		var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);
+	}
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+		value->obj = widget;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+
+		// listener for notifying observers
+		switch_register_observers(button, obj, var);
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {
+	RadioButton button = RadioButton();
+
+	UiObject* current = uic_current_obj(obj);
+
+	// set label
+	set_button_label(button, args.label, args.stockid);
+	togglebutton_register_callback(button, obj, args);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	UiVar* var = nullptr;
+	if (args.value) {
+		var = uic_create_value_var(current->ctx, args.value);
+	}
+	else if (args.varname) {
+		var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);
+	}
+
+	// bind radio button to the value
+	if (var) {
+		UiInteger* value = (UiInteger*)var->value;
+
+		// store a list of radio buttons in the value
+		CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS));
+		// get or create the group name
+		static int groupCount = 0;
+		winrt::hstring groupName;
+		if (radioButtons->size == 0) {
+			groupName = winrt::to_hstring(groupCount++);
+		} else {
+			UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0);
+			RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>();
+			groupName = firstRadioButton.GroupName();
+		}
+
+		// set the group name for the new radiobutton
+		cxListAdd(radioButtons, widget);
+		button.GroupName(groupName);
+
+		value->obj = radioButtons;
+		value->get = ui_radio_button_get;
+		value->set = ui_radio_button_set;
+
+		// listener for notifying observers (only checked, not unchecked)
+		togglebutton_register_checked_observers(button, obj, var);
+	}
+
+	// add button to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(button, false);
+
+	return widget;
+}
+
+
+int64_t ui_toggle_button_get(UiInteger* integer) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleButton toggleButton = widget->uielement.as<ToggleButton>();
+	int val = toggleButton.IsChecked().GetBoolean();
+	integer->value = val;
+	return val;
+}
+
+void ui_toggle_button_set(UiInteger* integer, int64_t value) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleButton toggleButton = widget->uielement.as<ToggleButton>();
+	toggleButton.IsChecked((bool)value);
+	integer->value = value;
+}
+
+int64_t ui_switch_get(UiInteger * integer) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();
+	int val = toggleButton.IsOn();
+	integer->value = val;
+	return val;
+}
+
+void ui_switch_set(UiInteger * integer, int64_t value) {
+	UiWidget* widget = (UiWidget*)integer->obj;
+	ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();
+	toggleButton.IsOn((bool)value);
+	integer->value = value;
+}
+
+int64_t ui_radio_button_get(UiInteger * integer) {
+	CxList* list = (CxList*)integer->obj;
+	CxIterator i = cxListIterator(list);
+	int selection = -1;
+	cx_foreach(UiWidget*, widget, i) {
+		ToggleButton button = widget->uielement.as<ToggleButton>();
+		if (button.IsChecked().GetBoolean()) {
+			selection = i.index;
+			break;
+		}
+	}
+	integer->value = selection;
+	return selection;
+}
+
+void ui_radio_button_set(UiInteger * integer, int64_t value) {
+	CxList* list = (CxList*)integer->obj;
+	UiWidget* widget = (UiWidget*)cxListAt(list, value);
+	if (widget) {
+		ToggleButton button = widget->uielement.as<ToggleButton>();
+		button.IsChecked(true);
+		integer->value = value;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/container.h"
+
+#include "../ui/button.h"
+
+#include "../common/context.h"
+
+
+void togglebutton_register_checked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);
+void togglebutton_register_unchecked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);
+void togglebutton_register_callback(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiToggleArgs& args);
+
+extern "C" int64_t ui_toggle_button_get(UiInteger * integer);
+extern "C" void ui_toggle_button_set(UiInteger * integer, int64_t value);
+
+extern "C" int64_t ui_switch_get(UiInteger * integer);
+extern "C" void ui_switch_set(UiInteger * integer, int64_t value);
+
+extern "C" int64_t ui_radio_button_get(UiInteger * integer);
+extern "C" void ui_radio_button_set(UiInteger * integer, int64_t value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/commandbar.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,212 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "commandbar.h"
+
+#include "util.h"
+#include "../common/object.h"
+#include "../common/context.h"
+
+#include "button.h"
+#include "appmenu.h"
+#include "icons.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i);
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item);
+static void create_toggleitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item);
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item);
+
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i);
+
+CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu) {
+	
+	CommandBar cb = CommandBar();
+	cb.DefaultLabelPosition(CommandBarDefaultLabelPosition::Right);
+
+	// add pre-configured items
+	CxIterator i = cxListIterator(defaults);
+	cx_foreach(char*, def, i) {
+		UiToolbarItemI* item = uic_toolbar_get_item(def);
+		if (!item) {
+			exit(-1); // TODO: maybe an error dialog?
+		}
+		create_item(obj, cb.PrimaryCommands(), item);
+	}
+
+	// add appmenu
+	if (addappmenu) {
+		UiToolbarMenuItem* appmenu = uic_get_appmenu();
+		if (appmenu) {
+			create_appmenu_items(obj, cb.SecondaryCommands(), appmenu);
+		}
+	}
+
+	return cb;
+}
+
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i) {
+	switch (i->type) {
+		case UI_TOOLBAR_ITEM: {
+			create_cmditem(obj, cb, (UiToolbarItem*)i);
+			break;
+		}
+		case UI_TOOLBAR_TOGGLEITEM: {
+			create_toggleitem(obj, cb, (UiToolbarToggleItem*)i);
+			break;
+		}
+		case UI_TOOLBAR_MENU: {
+			create_menuitem(obj, cb, (UiToolbarMenuItem*)i);
+			break;
+		}
+	}
+}
+
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i) {
+	for (UiMenuItemI* mi = i->menu.items_begin; mi; mi = mi->next) {
+		// convert UiMenuItemI to UiToolbarItemI
+		switch (mi->type) {
+			case UI_MENU: {
+				UiMenu* mitem = (UiMenu*)mi;
+				UiToolbarMenuItem tbitem;
+				memset(&tbitem, 0, sizeof(UiToolbarMenuItem));
+				tbitem.item.type = UI_TOOLBAR_MENU;
+				tbitem.args.label = mitem->label;
+				tbitem.menu.items_begin = mitem->items_begin;
+				tbitem.menu.items_end = mitem->items_end;
+				create_menuitem(obj, cb, &tbitem);
+				break;
+			}
+			case UI_MENU_ITEM: {
+				UiMenuItem* mitem = (UiMenuItem*)mi;
+				UiToolbarItem tbitem;
+				memset(&tbitem, 0, sizeof(UiToolbarItem));
+				tbitem.item.type = UI_TOOLBAR_ITEM;
+				tbitem.args.label = mitem->label;
+				tbitem.args.onclick = mitem->callback;
+				tbitem.args.onclickdata = mitem->userdata;
+				create_cmditem(obj, cb, &tbitem);
+				break;
+			}
+		}
+	}
+}
+
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item) {
+	AppBarButton button = AppBarButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if(item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	// register callback
+	if (item->args.onclick) {
+		ui_callback cbfunc = item->args.onclick;
+		void* cbdata = item->args.onclickdata;
+		button.Click([cbfunc, cbdata, obj](Windows::Foundation::IInspectable const& sender, RoutedEventArgs) {
+			UiEvent evt;
+			evt.obj = obj;
+			evt.window = obj->window;
+			evt.document = obj->ctx->document;
+			evt.eventdata = nullptr;
+			evt.intval = 0;
+			cbfunc(&evt, cbdata);
+			});
+	}
+
+	cb.Append(button);
+}
+
+static void create_toggleitem(UiObject *obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item) {
+	AppBarToggleButton button = AppBarToggleButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if (item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER);
+	if (var) {	
+		UIElement elm = button;
+		UiWidget* widget = new UiWidget(elm);
+		ui_context_add_widget_destructor(obj->ctx, widget);
+
+		UiInteger* value = (UiInteger*)var->value;
+		int64_t i = value->value;
+		value->get = ui_toggle_button_get;
+		value->set = ui_toggle_button_set;
+		value->obj = widget;
+		ui_toggle_button_set(value, i); // init togglebutton state
+
+		// listener for notifying observers
+		togglebutton_register_checked_observers(button, obj, var);
+		togglebutton_register_unchecked_observers(button, obj, var);
+	}
+
+	UiToggleArgs args = {};
+	args.onchange = item->args.onchange;
+	args.onchangedata = item->args.onchangedata;
+	togglebutton_register_callback(button, obj, args);
+
+
+	cb.Append(button);
+}
+
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item) {
+	AppBarButton button = AppBarButton();
+	if (item->args.label) {
+		wchar_t* wlabel = str2wstr(item->args.label, nullptr);
+		button.Label(wlabel);
+		free(wlabel);
+	}
+	if (item->args.icon) {
+		button.Icon(ui_get_icon(item->args.icon));
+	}
+
+	MenuFlyoutItem mi = MenuFlyoutItem();
+
+	MenuFlyout flyout = ui_create_menu_flyout(obj, &item->menu);
+	button.Flyout(flyout);
+
+	cb.Append(button);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/commandbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+winrt::Microsoft::UI::Xaml::Controls::CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu);
+
+extern "C" int64_t ui_appbar_togglebutton_get(UiInteger * integer);
+extern "C" void ui_appbar_togglebutton_set(UiInteger * integer, int64_t value);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/container.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,705 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "container.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include "util.h"
+
+
+void ui_container_begin_close(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->close = 1;
+}
+
+int ui_container_finish(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	if (ct->close) {
+		ui_end(obj);
+		return 0;
+	}
+	return 1;
+}
+
+
+// --------------------- UiBoxContainer ---------------------
+
+static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+
+	Grid grid = Grid();
+	current->container->Add(grid, true);
+
+	UIElement elm = grid;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing);
+	ui_context_add_container_destructor(current->ctx, newobj->container);
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) {
+	return ui_box(obj, args, UI_BOX_CONTAINER_VBOX);
+}
+
+UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) {
+	return ui_box(obj, args, UI_BOX_CONTAINER_HBOX);
+}
+
+UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) {
+	this->grid = grid;
+	this->type = type;
+
+	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
+	grid.Margin(t);
+	grid.ColumnSpacing((double)spacing);
+	grid.RowSpacing((double)spacing);
+
+	GridLength gl;
+	gl.Value = 1;
+	gl.GridUnitType = GridUnitType::Star;
+
+	// hbox needs one row def, vbox needs one col def
+	// all other col/row defs are created when elements are added
+	if (type == UI_BOX_CONTAINER_HBOX) {
+		boxRowDef = RowDefinition();
+		boxRowDef.Height(gl);
+		grid.RowDefinitions().Append(boxRowDef);
+	} else {
+		boxColDef = ColumnDefinition();
+		boxColDef.Width(gl);
+		grid.ColumnDefinitions().Append(boxColDef);
+	}
+
+	ui_reset_layout(layout);
+}
+
+void UiBoxContainer::Add(FrameworkElement control, UiBool fill) {
+	if (this->layout.fill != UI_LAYOUT_UNDEFINED) {
+		fill = ui_lb2bool(this->layout.fill);
+	}
+
+	GridLength gl;
+	if (fill) {
+		gl.Value = 1;
+		gl.GridUnitType = GridUnitType::Star;
+	}
+	else {
+		gl.Value = 0;
+		gl.GridUnitType = GridUnitType::Auto;
+	}
+	
+	control.HorizontalAlignment(HorizontalAlignment::Stretch);
+	control.VerticalAlignment(VerticalAlignment::Stretch);
+
+	if (type == UI_CONTAINER_HBOX) {
+		ColumnDefinition coldef = ColumnDefinition();
+		coldef.Width(gl);
+		grid.ColumnDefinitions().Append(coldef);
+		grid.SetColumn(control, grid.Children().Size());
+		grid.SetRow(control, 0);
+	} else {
+		RowDefinition rowdef = RowDefinition();
+		rowdef.Height(gl);
+		grid.RowDefinitions().Append(rowdef);
+		grid.SetRow(control, grid.Children().Size());
+		grid.SetColumn(control, 0);
+	}
+
+	grid.Children().Append(control);
+
+	ui_reset_layout(layout);
+}
+
+
+// --------------------- UiGridContainer ---------------------
+
+UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) {
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+
+	Grid grid = Grid();
+	current->container->Add(grid, true);
+
+	UIElement elm = grid;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing);
+	ui_context_add_container_destructor(current->ctx, newobj->container);
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) {
+	this->grid = grid;
+	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
+	grid.Margin(t);
+	grid.ColumnSpacing((double)columnspacing);
+	grid.RowSpacing((double)rowspacing);
+	ui_reset_layout(layout);
+}
+
+void UiGridContainer::Add(FrameworkElement control, UiBool fill) {
+	GridLength gl;
+
+	int hexpand = FALSE;
+	int vexpand = FALSE;
+	if (layout.hexpand != UI_LAYOUT_UNDEFINED) {
+		hexpand = layout.hexpand;
+	}
+	if (layout.vexpand != UI_LAYOUT_UNDEFINED) {
+		vexpand = layout.vexpand;
+	}
+
+	// create new RowDefinition for the new line
+	if (layout.newline || y == -1) {
+		x = 0;
+		y++;
+		RowDefinition rowdef = RowDefinition();
+		if (vexpand) {
+			gl.GridUnitType = GridUnitType::Star;
+			gl.Value = 1;
+		}
+		else {
+			gl.GridUnitType = GridUnitType::Auto;
+			gl.Value = 0;
+		}
+		rowdef.Height(gl);
+		grid.RowDefinitions().Append(rowdef);
+	}
+
+	// create new columndefinition, if a new column is added
+	if (x == cols) {
+		if (hexpand) {
+			gl.GridUnitType = GridUnitType::Star;
+			gl.Value = 1;
+		}
+		else {
+			gl.GridUnitType = GridUnitType::Auto;
+			gl.Value = 0;
+		}
+		ColumnDefinition coldef = ColumnDefinition();
+		coldef.Width(gl);
+		grid.ColumnDefinitions().Append(coldef);
+		cols++;
+	}
+
+	// add control
+	control.HorizontalAlignment(HorizontalAlignment::Stretch);
+	control.VerticalAlignment(VerticalAlignment::Stretch);
+
+	if (layout.colspan > 0) {
+		grid.SetColumnSpan(control, layout.colspan);
+	}
+	if (layout.rowspan > 0) {
+		grid.SetRowSpan(control, layout.rowspan);
+	}
+
+	grid.SetRow(control, y);
+	grid.SetColumn(control, x);
+	grid.Children().Append(control);
+
+	x++;
+
+	ui_reset_layout(layout);
+}
+
+// --------------------- UI Frame ---------------------
+
+UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) {
+	// create a grid for the frame, that contains the label and a sub-frame
+	Grid frame = Grid();
+
+	GridLength gl;
+	gl.GridUnitType = GridUnitType::Star;
+	gl.Value = 1;
+
+	ColumnDefinition coldef = ColumnDefinition();
+	coldef.Width(gl);
+	frame.ColumnDefinitions().Append(coldef);
+
+	RowDefinition rowdefFrame = RowDefinition();
+	rowdefFrame.Height(gl);
+
+	// label
+	int row = 0;
+	if (args.label) {
+		RowDefinition rowdefLabel = RowDefinition();
+		gl.GridUnitType = GridUnitType::Auto;
+		gl.Value = 0;
+		rowdefLabel.Height(gl);
+		frame.RowDefinitions().Append(rowdefLabel);
+
+		TextBlock label = TextBlock();
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		winrt::hstring hstr(wlabel);
+		label.Text(hstr);
+		free(wlabel);
+
+		frame.SetRow(label, row++);
+		frame.SetColumn(label, 0);
+		frame.Children().Append(label);
+	}
+	
+	// workarea frame
+	frame.RowDefinitions().Append(rowdefFrame);
+
+	Grid workarea = Grid();
+	frame.SetRow(workarea, row);
+	frame.SetColumn(workarea, 0);
+	frame.Children().Append(workarea);
+
+	// some styling for the workarea
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) };
+	workarea.BorderBrush(brush);
+	CornerRadius radius{ 8, 8, 8, 8 };
+	Thickness t = { 1, 1, 1, 1 };
+	workarea.CornerRadius(radius);
+	workarea.BorderThickness(t);
+
+	Thickness padding = { 10, 10, 10, 10 };
+	workarea.Padding(padding);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(frame, true);
+
+	UIElement elm = frame;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// sub container
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI Expander ---------------------
+
+UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) {
+	Expander expander = Expander();
+	if (args.label) {
+		wchar_t* wlabel = str2wstr(args.label, nullptr);
+		expander.Header(box_value(wlabel));
+		free(wlabel);
+	}
+	expander.IsExpanded(args.isexpanded);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(expander, true);
+
+	UIElement elm = expander;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	Grid content = Grid();
+	expander.Content(content);
+
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default: 
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI ScrolledWindow ---------------------
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+	ScrollViewer scrollW = ScrollViewer();
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(scrollW, true);
+
+	UIElement elm = scrollW;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// create child container
+	Grid content = Grid();
+	scrollW.Content(content);
+
+	UiContainer* ctn = nullptr;
+	switch (args.subcontainer) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+// --------------------- UI TabView ---------------------
+
+UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) {
+	this->tabview = tabview;
+}
+
+void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) {
+	// noop
+}
+
+static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) {
+	UiContainer* ctn = nullptr;
+	switch (type) {
+		default:
+		case UI_CONTAINER_VBOX: {
+			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing);
+			break;
+		}
+		case UI_CONTAINER_HBOX: {
+			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing);
+			break;
+		}
+		case UI_CONTAINER_GRID: {
+			ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing);
+			break;
+		}
+	}
+	ui_context_add_container_destructor(current->ctx, ctn);
+
+	UIElement elm = subcontainer;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	UiObject* newobj = uic_object_new(current, widget);
+	newobj->container = ctn;
+	return newobj;
+}
+
+UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) {
+	this->current = obj;
+	this->pivot = pivot;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+}
+
+UiObject* UiPivotTabView::AddTab(const char* label) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	PivotItem item = PivotItem();
+	item.Header(text);
+
+	// sub container
+	Grid subcontainer = Grid();
+	item.Content(subcontainer);
+	pivot.Items().Append(item);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+FrameworkElement UiPivotTabView::GetFrameworkElement() {
+	return pivot;
+}
+
+static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) {
+	Pivot pivot = Pivot();
+	UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args);
+
+	return tabview;
+}
+
+UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) {
+	this->current = obj;
+	this->tabview = tabview;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+}
+
+UiObject* UiMainTabView::AddTab(const char* label) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	TabViewItem item = TabViewItem();
+	item.Header(text);
+	item.CanDrag(false);
+	item.IsClosable(false);
+
+	// sub container
+	Grid subcontainer = Grid();
+	item.Content(subcontainer);
+	tabview.TabItems().Append(item);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+FrameworkElement UiMainTabView::GetFrameworkElement() {
+	return tabview;
+}
+
+static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) {
+	TabView tabview = TabView();
+	tabview.IsAddTabButtonVisible(false);
+	//tabview.CanDragTabs(false);
+	//tabview.CanReorderTabs(false);
+	UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args);
+
+	return uitabview;
+}
+
+UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) {
+	this->current = obj;
+	this->navigationview = navigationview;
+	this->type = type;
+	this->margin = args.margin;
+	this->spacing = args.spacing;
+	this->columnspacing = args.columnspacing;
+	this->rowspacing = args.rowspacing;
+
+	if (type == UI_TABVIEW_NAVIGATION_TOP) {
+		navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top);
+	}
+
+	navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged });
+}
+
+UiObject* UiNavigationTabView::AddTab(const char* label) {
+	TextBlock text = TextBlock();
+	wchar_t* wlabel = str2wstr(label, nullptr);
+	winrt::hstring hstr(wlabel);
+	text.Text(hstr);
+	free(wlabel);
+
+	NavigationViewItem item = NavigationViewItem();
+	item.Content(text);
+
+	// sub container
+	Grid subcontainer = Grid();
+	if (pages.size() == 0) {
+		navigationview.Content(subcontainer);
+		navigationview.SelectedItem(item);
+	}
+
+	navigationview.MenuItems().Append(item);
+	auto page = std::tuple<NavigationViewItem, FrameworkElement>{ item, subcontainer };
+	pages.push_back(page);
+
+	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
+}
+
+FrameworkElement UiNavigationTabView::GetFrameworkElement() {
+	return navigationview;
+}
+
+void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) {
+	for (auto page : pages) {
+		NavigationViewItem item = std::get<0>(page);
+		FrameworkElement elm = std::get<1>(page);
+		if (item == navigationview.SelectedItem()) {
+			navigationview.Content(elm);
+			break;
+		}
+	}
+}
+
+static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) {
+	NavigationView navigationview = NavigationView();
+	UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type);
+	navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed);
+	navigationview.IsSettingsVisible(false);
+
+	return tabview;
+}
+
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
+	UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview;
+	UiTabView* tabview = nullptr;
+	switch (type) {
+		default: {
+			tabview = tabview_pivot_create(obj, args);
+			break;
+		}
+		case UI_TABVIEW_DOC: { 
+			tabview = tabview_main_create(obj, args);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_SIDE: { 
+			tabview = tabview_navigationview_create(obj, args, type);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_TOP: { 
+			tabview = tabview_navigationview_create(obj, args, type);
+			break;
+		}
+		case UI_TABVIEW_NAVIGATION_TOP2: { 
+			tabview = tabview_pivot_create(obj, args);
+			break;
+		}
+	}
+	UiTabViewContainer* ctn = new UiTabViewContainer(tabview);
+
+	// add frame to the parent container
+	UiObject* current = uic_current_obj(obj);
+	UI_APPLY_LAYOUT1(current, args);
+	current->container->Add(tabview->GetFrameworkElement(), true);
+
+	UIElement elm = tabview->GetFrameworkElement();
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+	widget->data1 = tabview;
+
+	UiObject* newobj = uic_object_new(obj, widget);
+	newobj->container = ctn;
+	uic_obj_add(obj, newobj);
+
+	return widget;
+}
+
+void ui_tab_create(UiObject* obj, const char* title) {
+	UiObject* current = uic_current_obj(obj);
+	UiTabView* tabview = (UiTabView*)current->widget->data1;
+	UiObject* newobj = tabview->AddTab(title);
+	uic_obj_add(current, newobj);
+}
+
+/*
+ * -------------------- Layout Functions --------------------
+ *
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+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_width(UiObject* obj, int width) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.width = width;
+}
+
+void ui_layout_height(UiObject* obj, int height) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.height = height;
+}
+
+void ui_layout_colspan(UiObject* obj, int cols) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.colspan = cols;
+}
+
+void ui_layout_rowspan(UiObject* obj, int rows) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.rowspan = rows;
+}
+
+void ui_newline(UiObject* obj) {
+	UiContainer* ct = uic_get_current_container(obj);
+	ct->layout.newline = TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,165 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+#include "../ui/container.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiLayoutBool fill;
+    UiBool       newline;
+    char* label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    int          width;
+    int          height;
+    int          colspan;
+    int          rowspan;
+};
+
+struct UiContainer {
+    UiLayout layout;
+    int close = 0;
+
+    virtual void Add(FrameworkElement control, UiBool fill) = 0;
+};
+
+enum UiBoxContainerType {
+    UI_BOX_CONTAINER_VBOX = 0,
+    UI_BOX_CONTAINER_HBOX
+};
+
+enum UiNavigationViewType {
+    UI_NAVIGATIONVIEW_TOP = 0,
+    UI_NAVIGATIONVIEW_SIDE
+};
+
+struct UiBoxContainer : UiContainer {
+    Grid grid;
+    enum UiBoxContainerType type;
+    RowDefinition boxRowDef;
+    ColumnDefinition boxColDef;
+
+    UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiGridContainer : UiContainer {
+    Grid grid;
+    int x = 0;
+    int y = -1;
+    int cols = 0;
+
+    UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiTabView {
+    UiObject* current;
+    UiSubContainerType subcontainer;
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    virtual UiObject* AddTab(const char* label) = 0;
+
+    virtual FrameworkElement GetFrameworkElement() = 0;
+};
+
+struct UiTabViewContainer : UiContainer {
+    UiTabView* tabview;
+
+    UiTabViewContainer(UiTabView* tabview);
+
+    void Add(FrameworkElement control, UiBool fill);
+};
+
+struct UiPivotTabView : UiTabView {
+    Pivot pivot;
+
+    UiPivotTabView(UiObject *obj, Pivot pivot, UiTabViewArgs args);
+
+    UiObject* AddTab(const char* label);
+    FrameworkElement GetFrameworkElement();
+};
+
+struct UiMainTabView : UiTabView {
+    TabView tabview;
+
+    UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args);
+
+    UiObject* AddTab(const char* label);
+    FrameworkElement GetFrameworkElement();
+};
+
+struct UiNavigationTabView : UiTabView {
+    NavigationView navigationview;
+    UiTabViewType type;
+    std::vector<std::tuple<NavigationViewItem, FrameworkElement> > pages;
+
+    UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type);
+
+    UiObject* AddTab(const char* label);
+    FrameworkElement GetFrameworkElement();
+
+    void SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/dnd.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,59 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "dnd.h"
+#include "util.h"
+
+UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) {
+	if (dnd->data) {
+		if (len < 0) {
+			len = strlen(str);
+		}
+		wchar_t *wstr = str2wstr_len(str, len, nullptr);
+
+		dnd->data.SetText(wstr);
+
+		free(wstr);
+
+	}
+}
+
+UIEXPORT void ui_selection_seturis(UiDnD* dnd, char** uris, int nelm) {
+
+}
+
+
+UIEXPORT char* ui_selection_gettext(UiDnD* dnd) {
+	return nullptr;
+}
+
+UIEXPORT char** ui_selection_geturis(UiDnD* dnd, size_t* nelm) {
+	return nullptr;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/dnd.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,39 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/dnd.h"
+
+struct UiDnD {
+	int evttype = 0;
+	winrt::Microsoft::UI::Xaml::DragStartingEventArgs dndstartargs = { nullptr };
+	winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr };
+	winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr };
+	winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/icons.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,420 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pch.h"
+
+#include "icons.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+#include <Windows.h>
+#include <Shellapi.h>
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;
+//using namespace Windows::Storage::Streams;
+
+static UiIcon* sys_folder_icon16;
+static UiIcon* sys_file_icon16;
+
+static UiIcon* sys_folder_icon32;
+static UiIcon* sys_file_icon32;
+
+std::unordered_map<std::string, Symbol> ui_symbol_icons = {
+	{"Accept", Symbol::Accept },
+	{"Account", Symbol::Account },
+	{"Add", Symbol::Add },
+	{"AddFriend", Symbol::AddFriend },
+	{"Admin", Symbol::Admin },
+	{"AlignCenter", Symbol::AlignCenter },
+	{"AlignLeft", Symbol::AlignLeft },
+	{"AlignRight", Symbol::AlignRight },
+	{"AllApps", Symbol::AllApps },
+	{"Attach", Symbol::Attach },
+	{"AttachCamera", Symbol::AttachCamera },
+	{"Audio", Symbol::Audio },
+	{"Back", Symbol::Back },
+	{"BackToWindow", Symbol::BackToWindow },
+	{"BlockContact", Symbol::BlockContact },
+	{"Bold", Symbol::Bold },
+	{"Bookmarks", Symbol::Bookmarks },
+	{"BrowsePhotos", Symbol::BrowsePhotos },
+	{"Bullets", Symbol::Bullets },
+	{"Calculator", Symbol::Calculator },
+	{"Calendar", Symbol::Calendar },
+	{"CalendarDay", Symbol::CalendarDay },
+	{"CalendarReply", Symbol::CalendarReply },
+	{"CalendarWeek", Symbol::CalendarWeek },
+	{"Camera", Symbol::Camera },
+	{"Cancel", Symbol::Cancel },
+	{"Caption", Symbol::Caption },
+	{"CellPhone", Symbol::CellPhone },
+	{"Character", Symbol::Character },
+	{"Clear", Symbol::Clear },
+	{"ClearSelection", Symbol::ClearSelection },
+	{"Clock", Symbol::Clock },
+	{"ClosedCaption", Symbol::ClosedCaption },
+	{"ClosePane", Symbol::ClosePane },
+	{"Comment", Symbol::Comment },
+	{"Contact", Symbol::Contact },
+	{"Contact2", Symbol::Contact2 },
+	{"ContactInfo", Symbol::ContactInfo },
+	{"ContactPresence", Symbol::ContactPresence },
+	{"Copy", Symbol::Copy },
+	{"Crop", Symbol::Crop },
+	{"Cut", Symbol::Cut },
+	{"Delete", Symbol::Delete },
+	{"Directions", Symbol::Directions },
+	{"DisableUpdates", Symbol::DisableUpdates },
+	{"DisconnectDrive", Symbol::DisconnectDrive },
+	{"Dislike", Symbol::Dislike },
+	{"DockBottom", Symbol::DockBottom },
+	{"DockLeft", Symbol::DockLeft },
+	{"DockRight", Symbol::DockRight },
+	{"Document", Symbol::Document },
+	{"Download", Symbol::Download },
+	{"Edit", Symbol::Edit },
+	{"Emoji", Symbol::Emoji },
+	{"Emoji2", Symbol::Emoji2 },
+	{"Favorite", Symbol::Favorite },
+	{"Filter", Symbol::Filter },
+	{"Find", Symbol::Find },
+	{"Flag", Symbol::Flag },
+	{"Folder", Symbol::Folder },
+	{"Font", Symbol::Font },
+	{"FontColor", Symbol::FontColor },
+	{"FontDecrease", Symbol::FontDecrease },
+	{"FontIncrease", Symbol::FontIncrease },
+	{"FontSize", Symbol::FontSize },
+	{"Forward", Symbol::Forward },
+	{"FourBars", Symbol::FourBars },
+	{"FullScreen", Symbol::FullScreen },
+	{"GlobalNavigationButton", Symbol::GlobalNavigationButton },
+	{"Globe", Symbol::Globe },
+	{"Go", Symbol::Go },
+	{"GoToStart", Symbol::GoToStart },
+	{"GoToToday", Symbol::GoToToday },
+	{"HangUp", Symbol::HangUp },
+	{"Help", Symbol::Help },
+	{"HideBcc", Symbol::HideBcc },
+	{"Highlight", Symbol::Highlight },
+	{"Home", Symbol::Home },
+	{"Import", Symbol::Import },
+	{"ImportAll", Symbol::ImportAll },
+	{"Important", Symbol::Important },
+	{"Italic", Symbol::Italic },
+	{"Keyboard", Symbol::Keyboard },
+	{"LeaveChat", Symbol::LeaveChat },
+	{"Library", Symbol::Library },
+	{"Like", Symbol::Like },
+	{"LikeDislike", Symbol::LikeDislike },
+	{"Link", Symbol::Link },
+	{"List", Symbol::List },
+	{"Mail", Symbol::Mail },
+	{"MailFilled", Symbol::MailFilled },
+	{"MailForward", Symbol::MailForward },
+	{"MailReply", Symbol::MailReply },
+	{"MailReplyAll", Symbol::MailReplyAll },
+	{"Manage", Symbol::Manage },
+	{"Map", Symbol::Map },
+	{"MapDrive", Symbol::MapDrive },
+	{"MapPin", Symbol::MapPin },
+	{"Memo", Symbol::Memo },
+	{"Message", Symbol::Message },
+	{"Microphone", Symbol::Microphone },
+	{"More", Symbol::More },
+	{"MoveToFolder", Symbol::MoveToFolder },
+	{"MusicInfo", Symbol::MusicInfo },
+	{"Mute", Symbol::Mute },
+	{"NewFolder", Symbol::NewFolder },
+	{"NewWindow", Symbol::NewWindow },
+	{"Next", Symbol::Next },
+	{"OneBar", Symbol::OneBar },
+	{"OpenFile", Symbol::OpenFile },
+	{"OpenLocal", Symbol::OpenLocal },
+	{"OpenPane", Symbol::OpenPane },
+	{"OpenWith", Symbol::OpenWith },
+	{"Orientation", Symbol::Orientation },
+	{"OtherUser", Symbol::OtherUser },
+	{"OutlineStar", Symbol::OutlineStar },
+	{"Page", Symbol::Page },
+	{"Page2", Symbol::Page2 },
+	{"Paste", Symbol::Paste },
+	{"Pause", Symbol::Pause },
+	{"People", Symbol::People },
+	{"Permissions", Symbol::Permissions },
+	{"Phone", Symbol::Phone },
+	{"PhoneBook", Symbol::PhoneBook },
+	{"Pictures", Symbol::Pictures },
+	{"Pin", Symbol::Pin },
+	{"Placeholder", Symbol::Placeholder },
+	{"Play", Symbol::Play },
+	{"PostUpdate", Symbol::PostUpdate },
+	{"Preview", Symbol::Preview },
+	{"PreviewLink", Symbol::PreviewLink },
+	{"Previous", Symbol::Previous },
+	{"Print", Symbol::Print },
+	{"Priority", Symbol::Priority },
+	{"ProtectedDocument", Symbol::ProtectedDocument },
+	{"Read", Symbol::Read },
+	{"Redo", Symbol::Redo },
+	{"Refresh", Symbol::Refresh },
+	{"Remote", Symbol::Remote },
+	{"Remove", Symbol::Remove },
+	{"Rename", Symbol::Rename },
+	{"Repair", Symbol::Repair },
+	{"RepeatAll", Symbol::RepeatAll },
+	{"RepeatOne", Symbol::RepeatOne },
+	{"ReportHacked", Symbol::ReportHacked },
+	{"ReShare", Symbol::ReShare },
+	{"Rotate", Symbol::Rotate },
+	{"RotateCamera", Symbol::RotateCamera },
+	{"Save", Symbol::Save },
+	{"SaveLocal", Symbol::SaveLocal },
+	{"Scan", Symbol::Scan },
+	{"SelectAll", Symbol::SelectAll },
+	{"Send", Symbol::Send },
+	{"SetLockScreen", Symbol::SetLockScreen },
+	{"SetTile", Symbol::SetTile },
+	{"Setting", Symbol::Setting },
+	{"Share", Symbol::Share },
+	{"Shop", Symbol::Shop },
+	{"ShowBcc", Symbol::ShowBcc },
+	{"ShowResults", Symbol::ShowResults },
+	{"Shuffle", Symbol::Shuffle },
+	{"SlideShow", Symbol::SlideShow },
+	{"SolidStar", Symbol::SolidStar },
+	{"Sort", Symbol::Sort },
+	{"Stop", Symbol::Stop },
+	{"StopSlideShow", Symbol::StopSlideShow },
+	{"Street", Symbol::Street },
+	{"Switch", Symbol::Switch },
+	{"SwitchApps", Symbol::SwitchApps },
+	{"Sync", Symbol::Sync },
+	{"SyncFolder", Symbol::SyncFolder },
+	{"Tag", Symbol::Tag },
+	{"Target", Symbol::Target },
+	{"ThreeBars", Symbol::ThreeBars },
+	{"TouchPointer", Symbol::TouchPointer },
+	{"Trim", Symbol::Trim },
+	{"TwoBars", Symbol::TwoBars },
+	{"TwoPage", Symbol::TwoPage },
+	{"Underline", Symbol::Underline },
+	{"Undo", Symbol::Undo },
+	{"UnFavorite", Symbol::UnFavorite },
+	{"UnPin", Symbol::UnPin },
+	{"UnSyncFolder", Symbol::UnSyncFolder },
+	{"Up", Symbol::Up },
+	{"Upload", Symbol::Upload },
+	{"Video", Symbol::Video },
+	{"VideoChat", Symbol::VideoChat },
+	{"View", Symbol::View },
+	{"ViewAll", Symbol::ViewAll },
+	{"Volume", Symbol::Volume },
+	{"WebCam", Symbol::WebCam },
+	{"World", Symbol::World },
+	{"XboxOneConsole", Symbol::XboxOneConsole },
+	{"ZeroBars", Symbol::ZeroBars },
+	{"Zoom", Symbol::Zoom },
+	{"ZoomIn", Symbol::ZoomIn },
+	{"ZoomOut", Symbol::ZoomOut }
+};
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name) {
+	if (ui_symbol_icons.find(name) == ui_symbol_icons.end()) {
+		SymbolIcon no_icon = { nullptr };
+		return no_icon;
+	}
+
+	Symbol symbol = ui_symbol_icons[name];
+	SymbolIcon icon = SymbolIcon(symbol);
+	return icon;
+}
+
+
+// symbol icon implementation
+UiSymbolIcon::UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym) {
+	symbol = sym;
+}
+
+UiSymbolIcon::~UiSymbolIcon() {
+	
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiSymbolIcon::getIcon() {
+	return SymbolIcon(symbol);
+}
+
+// image icon implementation
+UiImageIcon::UiImageIcon(const char* uristr) {
+	wchar_t* wuri = str2wstr(uristr, nullptr);
+	Windows::Foundation::Uri uri{ wuri };
+	this->uri = uri;
+	free(wuri);
+}
+
+UiImageIcon::~UiImageIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiImageIcon::getIcon() {
+	BitmapIcon icon = BitmapIcon();
+	icon.UriSource(uri);
+	ImageIcon img = ImageIcon();
+	img.Source();
+	return icon;
+}
+
+// bitmap icon implementation
+UiBitmapIcon::UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap) {
+	this->bitmap = bitmap;
+}
+
+UiBitmapIcon::~UiBitmapIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiBitmapIcon::getIcon() {
+	ImageIcon icon = ImageIcon();
+	icon.Source(bitmap);
+	return icon;
+}
+
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size) {
+	Symbol symbol = ui_symbol_icons[name];
+	UiSymbolIcon* icon = new UiSymbolIcon(symbol);
+	return icon;
+}
+
+
+UIEXPORT UiIcon* ui_imageicon(const char* file) {
+	return new UiImageIcon(file);
+}
+
+UIEXPORT void ui_icon_free(UiIcon* icon) {
+	delete icon;
+}
+
+
+struct __declspec(uuid("905a0fef-bc53-11df-8c49-001e4fc686da")) IBufferByteAccess : ::IUnknown
+{
+	virtual HRESULT __stdcall Buffer(uint8_t** value) = 0;
+};
+
+
+
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large) {
+	WriteableBitmap wbitmap = { nullptr };
+
+	// get the icon from the dll
+	HICON hicon_small;
+	HICON hicon_large;
+	if (ExtractIconExA(dll, iconindex, &hicon_large, &hicon_small, 1) > 0) {
+		HICON hicon = large ? hicon_large : hicon_small;
+
+		// convert icon to (gdi) bitmap
+		ICONINFO info;
+		info.hbmColor = nullptr;
+		info.hbmMask = nullptr;
+		if (GetIconInfo(hicon, &info)) {
+			BITMAP bitmap;
+			if (GetObjectW(info.hbmColor, sizeof(BITMAP), &bitmap) != 0) {
+				size_t bitmap_size = bitmap.bmWidthBytes * bitmap.bmHeight;
+				char *bitmap_data = (char*)malloc(bitmap_size);
+
+				// get the pixel data
+				if (GetBitmapBits(info.hbmColor, bitmap_size, bitmap_data) != 0) {
+					WriteableBitmap wb = WriteableBitmap(bitmap.bmWidth, bitmap.bmHeight);
+					void *wb_data = wb.PixelBuffer().data();
+					memcpy(wb_data, bitmap_data, bitmap_size);
+					wbitmap = wb;
+				}
+				free(bitmap_data);
+			}
+			if (info.hbmMask) {
+				DeleteObject(info.hbmMask);
+			}
+			if (info.hbmColor) {
+				DeleteObject(info.hbmColor);
+			}
+		}
+
+		DestroyIcon(hicon_small);
+		DestroyIcon(hicon_large);
+	}
+
+	return wbitmap;
+}
+
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large) {
+	WriteableBitmap wbitmap = ui_dllicon2bitmap(dll, iconindex, large);
+	return new UiBitmapIcon(wbitmap);
+}
+
+UIEXPORT UiIcon* ui_foldericon(size_t size) {
+	bool large = true;
+	UiIcon** sys_folder_icon = &sys_folder_icon32;
+	if (size <= 24) {
+		large = false;
+		sys_folder_icon = &sys_folder_icon16;
+	}
+
+	if (*sys_folder_icon) {
+		return *sys_folder_icon;
+	}
+
+	UiIcon* icon = ui_dllicon("shell32.dll", 3, large);
+	*sys_folder_icon = icon;
+	return icon;
+}
+
+UIEXPORT UiIcon* ui_fileicon(size_t size) {
+	bool large = true;
+	UiIcon** sys_folder_icon = &sys_file_icon32;
+	if (size <= 24) {
+		large = false;
+		sys_folder_icon = &sys_file_icon16;
+	}
+
+	if (*sys_folder_icon) {
+		return *sys_folder_icon;
+	}
+
+	UiIcon* icon = ui_dllicon("shell32.dll", 0, large);
+	*sys_folder_icon = icon;
+	return icon;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/icons.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,76 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "../ui/toolkit.h"
+
+
+
+struct UiIcon {
+	//virtual ~UiIcon() = 0;
+	
+	virtual winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon() = 0;
+};
+
+struct UiSymbolIcon : UiIcon {
+	winrt::Microsoft::UI::Xaml::Controls::Symbol symbol;
+
+	UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym);
+
+	~UiSymbolIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+struct UiImageIcon : UiIcon {
+	winrt::Windows::Foundation::Uri uri{ nullptr };
+
+	UiImageIcon(const char* uristr);
+
+	~UiImageIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+struct UiBitmapIcon : UiIcon {
+	winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap{ nullptr };
+
+	UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap);
+
+	~UiBitmapIcon();
+
+	winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();
+};
+
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name);
+
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large);
+
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/label.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,199 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "label.h"
+#include "text.h"
+#include "util.h"
+
+#include "toolkit.h"
+#include "container.h"
+#include "../common/object.h"
+#include "../common/context.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    TextBlock label = TextBlock();
+    if (args.label) {
+        wchar_t* wlabel = str2wstr(args.label, nullptr);
+        label.Text(wlabel);
+        free(wlabel);
+    }
+
+    UIElement elm = label;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_label_get;
+        value->set = ui_label_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add label to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(label, false);
+
+    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);
+}
+
+
+
+char* ui_label_get(UiString* str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBlock box = widget->uielement.as<TextBlock>();
+    std::wstring wstr(box.Text());
+    return ui_string_get(str, wstr);
+}
+
+void  ui_label_set(UiString* str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    box.Text(ui_string_set(str, newvalue));
+}
+
+
+// -------------------- progressbar -------------------------
+
+UIWIDGET ui_progressbar_create(UiObject* obj, UiProgressbarArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    ProgressBar progressbar = ProgressBar();
+    progressbar.Minimum(args.min);
+    progressbar.Maximum(args.max == 0 ? 100 : args.max);
+    if (args.width > 0) {
+        progressbar.Width(args.width);
+    }
+
+    UIElement elm = progressbar;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+    if (var) {
+        UiDouble* value = (UiDouble*)var->value;
+        value->obj = widget;
+        value->get = ui_progressbar_get;
+        value->set = ui_progressbar_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(progressbar, false);
+
+    return widget;
+}
+
+double ui_progressbar_get(UiDouble * d) {
+    UiWidget* widget = (UiWidget*)d->obj;
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();
+    d->value = progressbar.Value();
+    return d->value;
+}
+
+void  ui_progressbar_set(UiDouble * d, double newvalue) {
+    UiWidget* widget = (UiWidget*)d->obj;
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();
+    d->value = newvalue;
+    progressbar.Value(newvalue);
+}
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    ProgressRing spinner = ProgressRing();
+    spinner.IsActive(false);
+
+    UIElement elm = spinner;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+    if (var) {
+        UiInteger* value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = ui_progressspinner_get;
+        value->set = ui_progressspinner_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(spinner, false);
+
+    return widget;
+}
+
+int64_t ui_progressspinner_get(UiInteger * i) {
+    UiWidget* widget = (UiWidget*)i->obj;
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();
+    i->value = spinner.IsActive();
+    return i->value;
+}
+
+void  ui_progressspinner_set(UiInteger * i, int64_t newvalue) {
+    UiWidget* widget = (UiWidget*)i->obj;
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();
+    i->value = newvalue != 0 ? 1 : 0;
+    spinner.IsActive(i->value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/label.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,42 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+
+#pragma once
+
+#include "../ui/toolkit.h"
+#include "../ui/display.h"
+
+extern "C" char* ui_label_get(UiString * str);
+extern "C" void  ui_label_set(UiString * str, const char* newvalue);
+
+extern "C" double ui_progressbar_get(UiDouble *d);
+extern "C" void  ui_progressbar_set(UiDouble *d, double newvalue);
+
+extern "C" int64_t ui_progressspinner_get(UiInteger * i);
+extern "C" void  ui_progressspinner_set(UiInteger * i, int64_t newvalue);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/list.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,293 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "list.h"
+#include "container.h"
+#include "util.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Microsoft::UI::Xaml::Media;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+
+
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    ListView listview = ListView();
+    if (args.multiselection) {
+        listview.SelectionMode(ListViewSelectionMode::Extended);
+    }
+
+    bool clickEnabled = listview.IsItemClickEnabled();
+    listview.IsItemClickEnabled(true);
+    
+
+    UIElement elm = listview;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_simple_list_update;
+        list->obj = widget;
+        ui_simple_list_update(list, 0);
+    }
+
+    if (args.onselection) {
+        ui_callback onselection = args.onselection;
+        void* cbdata = args.onselectiondata;
+        listview.SelectionChanged([onselection, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());
+
+            UiListSelection selection;
+            selection.rows = selectedRows.data();
+            selection.count = selectedRows.size();
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = 0;
+            onselection(&evt, cbdata);
+            });
+    }
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        listview.ItemClick([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());
+            UiListSelection selection;
+            selection.rows = selectedRows.data();
+            selection.count = selectedRows.size();
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = 0;
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(listview, false);
+
+    return widget;
+}
+
+
+UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    ComboBox combobox = ComboBox();
+
+    UIElement elm = combobox;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_simple_list_update;
+        list->obj = widget;
+        ui_simple_list_update(list, 0);
+    }
+
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        combobox.SelectionChanged([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {
+            int selectedrow = sender.as<ComboBox>().SelectedIndex();
+            UiListSelection selection;
+            selection.count = 1;
+            selection.rows = &selectedrow;
+
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = &selection;
+            evt.intval = selectedrow;
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(combobox, false);
+
+    return widget;
+}
+
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create listview and toolkit wrapper
+    BreadcrumbBar bcbar = BreadcrumbBar();
+
+    UIElement elm = bcbar;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = args.model;
+    widget->data2 = args.getvalue;
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiList* list = (UiList*)var->value;
+        list->update = ui_breadcrumbbar_update;
+        list->obj = widget;
+        ui_breadcrumbbar_update(list, 0);
+    }
+
+    if (args.onactivate) {
+        ui_callback cb = args.onactivate;
+        void* cbdata = args.onactivatedata;
+        bcbar.ItemClicked([cb, cbdata, obj](IInspectable const& sender, BreadcrumbBarItemClickedEventArgs evtargs) {
+            UiEvent evt;
+            evt.obj = obj;
+            evt.window = obj->window;
+            evt.document = obj->ctx->document;
+            evt.eventdata = nullptr;
+            evt.intval = evtargs.Index();
+            cb(&evt, cbdata);
+            });
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(bcbar, false);
+
+    return widget;
+}
+
+static void* getstrvalue(void* elm, int ignore) {
+    return elm;
+}
+
+void ui_simple_list_update(UiList* list, int i) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    UiModel* model = (UiModel*)widget->data1;
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;
+    ItemsControl listview = widget->uielement.as<ItemsControl>();
+    auto items = listview.Items();
+
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)
+    if (getvalue == nullptr) {
+        if (model && model->getvalue) {
+            getvalue = model->getvalue;
+        } else {
+            getvalue = getstrvalue;
+        }
+    }
+
+    // add list elements to listview.Items
+    items.Clear();
+    void* elm = list->first(list);
+    while (elm) {
+        char* value = (char*)getvalue(elm, 0);
+        wchar_t* wstr = str2wstr(value, nullptr);
+        items.Append(box_value(wstr));
+        free(wstr);
+
+        elm = list->next(list);
+    }
+}
+
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i) {
+    UiWidget* widget = (UiWidget*)list->obj;
+    UiModel* model = (UiModel*)widget->data1;
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;
+
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)
+    if (getvalue == nullptr) {
+        if (model && model->getvalue) {
+            getvalue = model->getvalue;
+        }
+        else {
+            getvalue = getstrvalue;
+        }
+    }
+
+    BreadcrumbBar bar = widget->uielement.as<BreadcrumbBar>();
+    
+    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> items { winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };
+    void* elm = list->first(list);
+    while (elm) {
+        char* value = (char*)getvalue(elm, 0);
+        wchar_t* wstr = str2wstr(value, nullptr);
+        items.Append(box_value(wstr));
+        free(wstr);
+
+        elm = list->next(list);
+    }
+
+    bar.ItemsSource(items);
+}
+
+
+std::vector<int> ui_create_listview_selection(ListView listview) {
+    std::vector<int> selection;
+    int p = 0;
+    auto ranges = listview.SelectedRanges();
+    for (auto range : ranges) {
+        int begin = range.FirstIndex();
+        int end = range.LastIndex();
+        for (int i = begin; i <= end; i++) {
+            selection.push_back(i);
+        }
+    }
+    return selection;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/list.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,42 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+
+#include "../ui/container.h"
+
+
+extern "C" void ui_simple_list_update(UiList * list, int i);
+
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i);
+
+std::vector<int> ui_create_listview_selection(winrt::Microsoft::UI::Xaml::Controls::ListView listview);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/packages.config	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.220929.3" targetFramework="native" />
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" />
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.755" targetFramework="native" />
+  <package id="Microsoft.WindowsAppSDK" version="1.3.230602002" targetFramework="native" />
+</packages>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/pch.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#include "pch.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/pch.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation and Contributors.
+// Licensed under the MIT License.
+
+#pragma once
+#include <windows.h>
+#include <unknwn.h>
+#include <restrictederrorinfo.h>
+#include <hstring.h>
+
+// Undefine GetCurrentTime macro to prevent
+// conflict with Storyboard::GetCurrentTime
+#undef GetCurrentTime
+
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.ApplicationModel.Activation.h>
+#include <winrt/Microsoft.UI.Composition.h>
+#include <winrt/Microsoft.UI.Xaml.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.Data.h>
+#include <winrt/Microsoft.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+#include <winrt/Microsoft.UI.Xaml.Media.h>
+#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>
+#include <winrt/Microsoft.UI.Xaml.Navigation.h>
+#include <winrt/Microsoft.UI.Xaml.Shapes.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Dispatching.h>
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>
+#include <wil/cppwinrt_helpers.h>
+#include <winrt/Microsoft.UI.Xaml.Input.h>
+#include <winrt/Windows.UI.Core.h>
+#include <winrt/Windows.ApplicationModel.h>
+
+#include <winrt/Windows.Storage.Streams.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/readme.txt	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,27 @@
+========================================================================
+    winui Project Overview
+========================================================================
+
+This project demonstrates how to get started writing WinUI3 apps directly
+with standard C++, using the Windows App SDK and C++/WinRT packages and
+XAML compiler support to generate implementation headers from interface
+(IDL) files. These headers can then be used to implement the local
+Windows Runtime classes referenced in the app's XAML pages.
+
+Steps:
+1. Create an interface (IDL) file to define any local Windows Runtime
+    classes referenced in the app's XAML pages.
+2. Build the project once to generate implementation templates under
+    the "Generated Files" folder, as well as skeleton class definitions
+    under "Generated Files\sources".
+3. Use the skeleton class definitions for reference to implement your
+    Windows Runtime classes.
+
+========================================================================
+Learn more about Windows App SDK here:
+https://docs.microsoft.com/windows/apps/windows-app-sdk/
+Learn more about WinUI3 here:
+https://docs.microsoft.com/windows/apps/winui/winui3/
+Learn more about C++/WinRT here:
+http://aka.ms/cppwinrt/
+========================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/stock.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,5 @@
+
+
+#include "pch.h"
+
+#include "stock.h"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/stock.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,32 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "../ui/stock.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/table.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,597 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "table.h"
+#include "container.h"
+#include "util.h"
+#include "icons.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "../common/types.h"
+
+#include <winrt/Microsoft.UI.Xaml.Data.h>
+#include <winrt/Microsoft.UI.Xaml.Media.h>
+#include <winrt/Microsoft.UI.Xaml.Input.h>
+#include <winrt/Windows.UI.Core.h>
+#include <winrt/Windows.ApplicationModel.h>
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media;
+using namespace winrt::Windows::UI::Xaml::Input;
+
+static UINT ui_double_click_time = GetDoubleClickTime();
+
+extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) {
+	// TODO:
+}
+
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) {
+	if (!args.model) {
+		return nullptr;
+	}
+
+	UiObject* current = uic_current_obj(obj);
+
+	// create widgets and wrapper obj
+	ScrollViewer scrollW = ScrollViewer();
+	Grid grid = Grid();
+	scrollW.Content(grid);
+	UiTable* uitable = new UiTable(obj, scrollW, grid);
+	reg_table_destructor(current->ctx, uitable);
+	
+	uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue;
+	uitable->onselection = args.onselection;
+	uitable->onselectiondata = args.onselectiondata;
+	uitable->onactivate = args.onactivate;
+	uitable->onactivatedata = args.onactivatedata;
+	uitable->ondragstart = args.ondragstart;
+	uitable->ondragstartdata = args.ondragstartdata;
+	uitable->ondragcomplete = args.ondragcomplete;
+	uitable->ondrop = args.ondrop;
+	uitable->ondropdata = args.ondropsdata;
+
+	// grid styling
+	winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color
+	SolidColorBrush brush = SolidColorBrush(bg);
+	grid.Background(brush);
+
+	// add columns from args.model
+	uitable->add_header(args.model);
+	
+	// bind var
+	UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+	if (var) {
+		UiList* list = (UiList*)var->value;
+		list->update = ui_table_update;
+		list->obj = uitable;
+		uitable->update(list, 0);
+	}
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = scrollW;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// add scrollW to current container
+	UI_APPLY_LAYOUT1(current, args);
+
+	current->container->Add(scrollW, false);
+
+	return widget;
+}
+
+extern "C" void ui_table_update(UiList * list, int i) {
+	UiTable* table = (UiTable*)list->obj;
+	table->clear();
+	table->update(list, i);
+}
+
+UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) {
+	this->obj = obj;
+
+	this->scrollw = scrollw;
+	this->grid = grid;
+
+	winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 };
+	highlightBrush = SolidColorBrush(highlightBg);
+
+	winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default
+	defaultBrush = SolidColorBrush(defaultBg);
+
+	winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color
+	selectedBrush = SolidColorBrush(selectedBg);
+
+	winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color
+	selectedBorderBrush = SolidColorBrush(selectedFg);
+
+	grid.KeyDown(
+		winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(
+			[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) {
+				// key event for hanling the table cursor or enter
+			})
+	);
+}
+
+UiTable::~UiTable() {
+	ui_model_free(NULL, model);
+}
+
+void UiTable::add_header(UiModel* model) {
+	this->model = ui_model_copy(NULL, model);
+
+	GridLength gl;
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+
+	// add header row definition
+	auto headerRowDef = RowDefinition();
+	headerRowDef.Height(gl);
+	grid.RowDefinitions().Append(headerRowDef);
+
+
+	for (int i = 0; i < model->columns;i++) {
+		char* title = model->titles[i];
+		UiModelType type = model->types[i];
+
+		// add grid column definition
+		auto colDef = ColumnDefinition();
+		colDef.Width(gl);
+		grid.ColumnDefinitions().Append(colDef);
+
+		// add button
+		auto button = Button();
+		wchar_t* wlabel = str2wstr(title, nullptr);
+		button.Content(box_value(wlabel));
+		free(wlabel);
+
+		// some styling for the button
+		Thickness border = { 0,0,1,0 };
+		CornerRadius corner = { 0,0,0,0 };
+		button.BorderThickness(border);
+		button.CornerRadius(corner);
+
+		grid.SetColumn(button, i);
+		grid.SetRow(button, 0);
+		grid.Children().Append(button);
+
+		UiTableColumn h;
+		h.header = button;
+		header.push_back(h);
+	}
+
+	maxrows = 1;
+}
+
+static void textblock_set_str(TextBlock &t, const char *str) {
+	if (str) {
+		wchar_t* wstr = str2wstr(str, nullptr);
+		t.Text(winrt::hstring(wstr));
+		free(wstr);
+	}
+}
+
+static void textblock_set_int(TextBlock& t, int i) {
+	wchar_t buf[16];
+	swprintf(buf, 16, L"%d", i);
+	t.Text(winrt::hstring(buf));
+}
+
+static ULONG64 getsystime() {
+	SYSTEMTIME st;
+	GetSystemTime(&st);
+	return st.wYear   * 10000000000000 +
+		   st.wMonth  * 100000000000   +
+		   st.wDay    * 1000000000     +
+		   st.wHour   * 10000000       +
+		   st.wMinute * 100000         +
+		   st.wSecond * 1000           +
+		   st.wMilliseconds;
+}
+
+void UiTable::update(UiList* list,  int i) {
+	if (getvalue == nullptr) {
+		return;
+	}
+
+	Thickness b1 = { 1, 1, 0, 1 }; // first col
+	Thickness b2 = { 0, 1, 0, 1 }; // middle
+	Thickness b3 = { 0, 1, 1, 1 }; // last col
+
+	GridLength gl;
+	gl.Value = 0;
+	gl.GridUnitType = GridUnitType::Auto;
+
+	// iterate model
+	int row = 1;
+	void* elm = list->first(list);
+	while (elm) {
+		if (row >= maxrows) {
+			auto rowdef = RowDefinition();
+			rowdef.Height(gl);
+			grid.RowDefinitions().Append(rowdef);
+			maxrows = row;
+		}
+
+		Thickness cellpadding = { 10,0,4,0 };
+
+		// model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model
+		int model_col = 0;
+		for (int col = 0; col < header.size(); col++, model_col++) {
+			// create ui elements with the correct cell border
+			// dependeing on the column
+			Border cellBorder = Border();
+			cellBorder.Background(defaultBrush);
+			cellBorder.BorderBrush(defaultBrush);
+			if (col == 0) {
+				cellBorder.BorderThickness(b1);
+			}
+			else if (col + 1 == header.size()) {
+				cellBorder.BorderThickness(b3);
+			}
+			else {
+				cellBorder.BorderThickness(b2);
+			}
+
+			// dnd
+			if (ondragstart) {
+				cellBorder.CanDrag(true);
+				cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) {
+						UiDnD dnd;
+						dnd.evttype = 0;
+						dnd.dndstartargs = args;
+						dnd.dndcompletedargs = { nullptr };
+						dnd.drageventargs = { nullptr };
+						dnd.data = args.Data();
+
+						UiListDnd dndevt;
+						dndevt.selection = uiselection();
+						dndevt.dnd = &dnd;
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dndevt;
+						evt.intval = 0;
+					
+						this->ondragstart(&evt, this->ondragstartdata);
+
+						if (dndevt.selection.rows) {
+							free(dndevt.selection.rows);
+						}
+					});
+				cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) {
+						UiDnD dnd;
+						dnd.evttype = 1;
+						dnd.dndstartargs = { nullptr };
+						dnd.dndcompletedargs = args;
+						dnd.drageventargs = { nullptr };
+						dnd.data = { nullptr };
+
+						UiListDnd dndevt;
+						dndevt.selection = uiselection();
+						dndevt.dnd = &dnd;
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dndevt;
+						evt.intval = 0;
+
+						if (this->ondragcomplete) {
+							this->ondragcomplete(&evt, this->ondragcompletedata);
+						}
+						if (dndevt.selection.rows) {
+							free(dndevt.selection.rows);
+						}
+					});
+			}
+			if (ondrop) {
+				cellBorder.AllowDrop(true);
+				cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){
+						UiDnD dnd;
+						dnd.evttype = 2;
+						dnd.dndstartargs = { nullptr };
+						dnd.dndcompletedargs = { nullptr };
+						dnd.drageventargs = args;
+						dnd.data = args.Data();
+
+						UiListDnd dndevt;
+						dndevt.selection = uiselection();
+						dndevt.dnd = &dnd;
+
+						UiEvent evt;
+						evt.obj = this->obj;
+						evt.window = evt.obj->window;
+						evt.document = obj->ctx->document;
+						evt.eventdata = &dndevt;
+						evt.intval = 0;
+
+						this->ondrop(&evt, this->ondropdata);
+
+						if (dndevt.selection.rows) {
+							free(dndevt.selection.rows);
+						}
+					}));
+			}
+
+			// set the cell value
+			// depending on the type, we create different cell controls
+			UiModelType type = model->types[col];
+			switch (type) {
+				case UI_STRING: {
+					TextBlock cell = TextBlock();
+					cell.Padding(cellpadding);
+					cell.VerticalAlignment(VerticalAlignment::Stretch);
+					textblock_set_str(cell, (char*)getvalue(elm, model_col));
+					cellBorder.Child(cell);
+
+					break;
+				}
+				case UI_INTEGER: {
+					TextBlock cell = TextBlock();
+					cell.Padding(cellpadding);
+					cell.VerticalAlignment(VerticalAlignment::Stretch);
+					int *value = (int*)getvalue(elm, model_col);
+					if (value) {
+						textblock_set_int(cell, *value);
+					}
+					cellBorder.Child(cell);
+					break;
+				}
+				case UI_ICON: {
+					UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col);
+					if (iconConstr) {
+						IconElement icon = iconConstr->getIcon();
+						cellBorder.Child(icon);
+					}
+					break;
+				}
+				case UI_ICON_TEXT: {
+					StackPanel cellPanel = StackPanel();
+					cellPanel.Spacing(2);
+					cellPanel.Padding(cellpadding);
+					cellPanel.VerticalAlignment(VerticalAlignment::Stretch);
+
+					cellPanel.Orientation(Orientation::Horizontal);
+					UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++);
+					char* str = (char*)getvalue(elm, model_col);
+					if (iconConstr) {
+						IconElement icon = iconConstr->getIcon();
+						cellPanel.Children().Append(icon);
+					}
+					TextBlock cell = TextBlock();
+					textblock_set_str(cell, str);
+					cellPanel.Children().Append(cell);
+					cellBorder.Child(cellPanel);
+					break;
+				}
+			}
+
+			// event handler
+			cellBorder.PointerPressed(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers();
+						bool update_selection = true;
+
+						if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) {
+							// add/remove current row
+							if (!is_row_selected(row)) {
+								row_background(row, selectedBrush, selectedBorderBrush);
+								selection.push_back(row);
+							}
+							else {
+								row_background(row, highlightBrush, highlightBrush);
+								remove_from_selection(row);
+							}
+						}
+						else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) {
+							// no modifier or shift is pressed but there is no selection
+							if (selection.size() > 0) {
+								change_rows_bg(selection, defaultBrush, defaultBrush);
+							}
+							
+							row_background(row, selectedBrush, selectedBorderBrush);
+							selection = { row };
+							if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) {
+								SYSTEMTIME st;
+								GetSystemTime(&st);
+								
+								ULONG64 now = getsystime();
+								ULONG64 tdiff = now - lastPointerPress;
+								if (tdiff < ui_double_click_time && onactivate != nullptr) {
+									// two pointer presse events in short time and we have an onactivate handler
+									update_selection = false; // we don't want an additional selection event
+									lastPointerPress = 0; // reset double-click
+
+									int selectedrow = row - 1; // subtract header row
+
+									UiListSelection selection;
+									selection.count = 1;
+									selection.rows = &selectedrow;
+
+									UiEvent evt;
+									evt.obj = obj;
+									evt.window = obj->window;
+									evt.document = obj->ctx->document;
+									evt.eventdata = &selection;
+									evt.intval = selectedrow;
+									onactivate(&evt, onactivatedata);
+								}
+								else {
+									lastPointerPress = now;
+								}
+							}
+						}
+						else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) {
+							// select everything between the first selection and the current row
+							std::sort(selection.begin(), selection.end());
+							int first = selection.front();
+							int last = row;
+							if (first > row) {
+								last = first;
+								first = row;
+							}
+
+							// clear previous selection
+							change_rows_bg(selection, defaultBrush, defaultBrush);
+
+							// create new selection
+							std::vector<int> newselection;
+							for (int s = first; s <= last; s++) {
+								newselection.push_back(s);
+							}
+							selection = newselection;
+							change_rows_bg(selection, selectedBrush, selectedBorderBrush);
+						}
+
+						if (update_selection) {
+							call_handler(onselection, onselectiondata);
+						}
+					})
+			);
+			cellBorder.PointerReleased(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+
+					})
+			);
+			cellBorder.PointerEntered(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						if (!is_row_selected(row)) {
+							row_background(row, highlightBrush, highlightBrush);
+						}
+					})
+			);
+			cellBorder.PointerExited(
+				winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+					[=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+						if (!is_row_selected(row)) {
+							row_background(row, defaultBrush, defaultBrush);
+						}
+					})
+			);
+
+			grid.SetColumn(cellBorder, col);
+			grid.SetRow(cellBorder, row);
+			grid.Children().Append(cellBorder);
+		}
+
+		row++;
+		elm = list->next(list);
+	}
+}
+
+void UiTable::clear() {
+	for (int i = grid.Children().Size()-1; i >= 0; i--) {
+		FrameworkElement elm = grid.Children().GetAt(i).as<FrameworkElement>();
+		int child_row = grid.GetRow(elm);
+		if (child_row > 0) {
+			grid.Children().RemoveAt(i);
+		}
+	}
+
+	// TODO: should we clean row definitions?
+}
+
+void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {
+	Thickness b1 = { 1, 1, 0, 1 }; // first col
+	Thickness b2 = { 0, 1, 0, 1 }; // middle
+	Thickness b3 = { 0, 1, 1, 1 }; // last col
+	
+	for (auto child : grid.Children()) {
+		FrameworkElement elm = child.as<FrameworkElement>();
+		int child_row = grid.GetRow(elm);
+		if (child_row == row) {
+			Border b = elm.as<Border>();
+			b.Background(brush);
+			b.BorderBrush(borderBrush);
+		}
+	}
+}
+
+void UiTable::change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {
+	std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); });
+}
+
+bool UiTable::is_row_selected(int row) {
+	return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false;
+}
+
+void UiTable::remove_from_selection(int row) {
+	selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end());
+	selection.shrink_to_fit();
+}
+
+UiListSelection UiTable::uiselection() {
+	std::sort(selection.begin(), selection.end());
+
+	UiListSelection selobj;
+	selobj.count = selection.size();
+	selobj.rows = nullptr;
+	if (selobj.count > 0) {
+		selobj.rows = (int*)calloc(selobj.count, sizeof(int));
+		memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int));
+		for (int i = 0; i < selobj.count; i++) {
+			selobj.rows[i]--;
+		}
+	}
+	return selobj;
+}
+
+void UiTable::call_handler(ui_callback cb, void* cbdata) {
+	if (!cb) {
+		return;
+	}
+
+	UiListSelection selobj = uiselection();
+
+	UiEvent evt;
+	evt.obj = obj;
+	evt.window = obj->window;
+	evt.document = obj->ctx->document;
+	evt.eventdata = &selobj;
+	evt.intval = 0;
+	cb(&evt, cbdata);
+
+	if (selobj.rows) {
+		free(selobj.rows);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/table.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,93 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+#include "dnd.h"
+
+#include "../ui/container.h"
+
+
+typedef struct UiTableColumn {
+	winrt::Microsoft::UI::Xaml::Controls::Button header;
+
+} UiTableColumn;
+
+typedef struct UiTable {
+	winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollw;
+	winrt::Microsoft::UI::Xaml::Controls::Grid grid;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush defaultBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush highlightBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBrush;
+	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBorderBrush;
+	UiObject* obj;
+	ui_callback onactivate;
+	void* onactivatedata;
+	ui_callback onselection;
+	void* onselectiondata;
+	ui_callback ondragstart;
+	void* ondragstartdata;
+	ui_callback ondragcomplete;
+	void* ondragcompletedata;
+	ui_callback ondrop;
+	void* ondropdata;
+	UiModel* model = nullptr;
+	std::vector<UiTableColumn> header;
+	ui_getvaluefunc getvalue = nullptr;
+	int maxrows = 0;
+	int lastSelection = 0;
+	ULONG64 lastPointerPress = 0;
+	std::vector<int> selection;
+
+	UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid);
+
+	~UiTable();
+	
+	void add_header(UiModel* model);
+
+	void update(UiList* list, int i);
+
+	void clear();
+
+	void row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);
+
+	void change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);
+
+	bool is_row_selected(int row);
+
+	void remove_from_selection(int row);
+
+	UiListSelection uiselection();
+
+	void call_handler(ui_callback cb, void *cbdata);
+} UiTable;
+
+extern "C" void ui_table_update(UiList * list, int i);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/text.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,469 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "text.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <cx/string.h>
+#include <cx/allocator.h>
+
+#include "util.h"
+#include "container.h"
+
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Microsoft::UI::Xaml::Media;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Windows::UI::Xaml::Input;
+
+UIWIDGET ui_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    TextBox textfield = TextBox();
+    UIElement elm = textfield;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+    
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(textfield, false);
+
+    return widget;
+}
+
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return ui_textfield_create(obj, args);
+}
+
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create textbox and toolkit wrapper
+    PasswordBox textfield = PasswordBox();
+    UIElement elm = textfield;
+    UiWidget* widget = new UiWidget(elm);
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = widget;
+        value->get = ui_passwordfield_get;
+        value->set = ui_passwordfield_set;
+
+        // listener for notifying observers
+        // TODO:
+    }
+
+    // add button to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(textfield, false);
+
+    return widget;
+}
+
+
+// -------------------------- getter/setter for textfield UiString --------------------------
+
+char* ui_string_get(UiString* str, std::wstring &value) {
+    if (str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+
+    str->value.ptr = wchar2utf8(value.c_str(), value.length());
+    str->value.free = free;
+
+    return str->value.ptr;
+}
+
+std::wstring ui_string_set(UiString* str, const char* value) {
+    if (str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+
+    str->value.ptr = _strdup(value);
+    str->value.free = free;
+
+    int len;
+    wchar_t* wstr = str2wstr(value, &len);
+    std::wstring s(wstr);
+    free(wstr);
+
+    return s;
+}
+
+char* ui_textfield_get(UiString * str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    std::wstring wstr(box.Text());
+    return ui_string_get(str, wstr);
+}
+
+void  ui_textfield_set(UiString * str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    TextBox box = widget->uielement.as<TextBox>();
+    box.Text(ui_string_set(str, newvalue));
+}
+
+
+char* ui_passwordfield_get(UiString * str) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    PasswordBox box = widget->uielement.as<PasswordBox>();
+    std::wstring wstr(box.Password());
+    return ui_string_get(str, wstr);
+}
+
+void  ui_passwordfield_set(UiString * str, const char* newvalue) {
+    UiWidget* widget = (UiWidget*)str->obj;
+    PasswordBox box = widget->uielement.as<PasswordBox>();
+    box.Password(ui_string_set(str, newvalue));
+}
+
+
+// ------------------------ path textfield --------------------------------------
+
+extern "C" static void destroy_ui_pathtextfield(void* ptr) {
+    UiPathTextField* pb = (UiPathTextField*)ptr;
+    delete pb;
+}
+
+static void ui_context_add_pathtextfield_destructor(UiContext* ctx, UiPathTextField* pb) {
+    cxMempoolRegister(ctx->mp, pb, destroy_ui_pathtextfield);
+}
+
+static void ui_pathtextfield_clear(StackPanel& buttons) {
+    for (int i = buttons.Children().Size() - 1; i >= 0; i--) {
+        buttons.Children().RemoveAt(i);
+    }
+}
+
+static void ui_pathfield_free_pathelms(UiPathElm* elms, size_t nelm) {
+    if (!elms) {
+        return;
+    }
+    for (int i = 0; i < nelm; i++) {
+        UiPathElm e = elms[i];
+        free(e.name);
+        free(e.path);
+    }
+    free(elms);
+}
+
+UiPathTextField::~UiPathTextField() {
+    ui_pathfield_free_pathelms(this->current_path, this->current_path_nelms);
+}
+
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
+    cxstring *pathelms;
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+
+    if (nelm == 0) {
+        *ret_nelm = 0;
+        return nullptr;
+    }
+
+    UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));
+    size_t n = nelm;
+    int j = 0;
+    for (int i = 0; i < nelm; i++) {
+        cxstring c = pathelms[i];
+        if (c.length == 0) {
+            if (i == 0) {
+                c.length = 1;
+            }
+            else {
+                n--;
+                continue;
+            }
+        }
+
+        cxmutstr m = cx_strdup(c);
+        elms[j].name = m.ptr;
+        elms[j].name_len = m.length;
+        
+        size_t elm_path_len = c.ptr + c.length - full_path;
+        cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
+        elms[j].path = elm_path.ptr;
+        elms[j].path_len = elm_path.length;
+
+        j++;
+    }
+    *ret_nelm = n;
+
+    return elms;
+}
+
+void ui_pathtextfield_update(UiPathTextField* pb, const char *full_path) {
+    Grid grid = pb->grid;
+
+    ui_pathelm_func getpathelm = pb->getpathelm;
+    void* getpathelmdata = pb->getpathelmdata;
+
+    // hide textbox, show button panel
+    pb->textbox.Visibility(Visibility::Collapsed);
+    pb->buttons.Visibility(Visibility::Visible);
+
+    // clear old buttons
+    ui_pathtextfield_clear(pb->buttons);
+
+    size_t full_path_len = full_path ? strlen(full_path) : 0;
+    
+    size_t nelm = 0;
+    UiPathElm* path_elm = getpathelm(full_path, full_path_len, &nelm, getpathelmdata);
+    ui_pathfield_free_pathelms(pb->current_path, pb->current_path_nelms);
+    pb->current_path = path_elm;
+    pb->current_path_nelms = nelm;
+
+    // add new buttons
+    int j = 0;
+    for (int i = 0; i < nelm;i++) {
+        UiPathElm elm = path_elm[i];
+        wchar_t* wstr = str2wstr_len(elm.name, elm.name_len, nullptr);
+        Button button = Button();
+        button.Content(box_value(wstr));
+        free(wstr);
+
+        if (pb->onactivate) {
+            button.Click([pb, j, elm](IInspectable const& sender, RoutedEventArgs) {
+                // copy elm.path because it could be a non-terminated string
+                cxmutstr elmpath = cx_strdup(cx_strn(elm.path, elm.path_len));
+
+                UiEvent evt;
+                evt.obj = pb->obj;
+                evt.window = evt.obj->window;
+                evt.document = evt.obj->ctx->document;
+                evt.eventdata = elmpath.ptr;
+                evt.intval = j;
+                pb->onactivate(&evt, pb->onactivatedata);
+
+                free(elmpath.ptr);
+                });
+        }
+
+        Thickness t = { 0, 0, 1, 0 };
+        CornerRadius c = { 0 ,0, 0, 0 };
+        button.BorderThickness(t);
+        button.CornerRadius(c);
+
+        pb->buttons.Children().Append(button);
+
+        j++;
+    }
+}
+
+char* ui_path_textfield_get(UiString * str) {
+    UiPathTextField* widget = (UiPathTextField*)str->obj;
+    TextBox box = widget->textbox;
+    std::wstring wstr(box.Text());
+    return ui_string_get(str, wstr);
+}
+
+void  ui_path_textfield_set(UiString* str, const char* newvalue) {
+    UiPathTextField* widget = (UiPathTextField*)str->obj;
+    TextBox box = widget->textbox;
+    box.Text(ui_string_set(str, newvalue));
+    ui_pathtextfield_update(widget, newvalue);
+}
+
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+
+    // create view and toolkit wrapper
+    Border pathbar = Border();
+
+    IInspectable bgRes = Application::Current().Resources().Lookup(box_value(L"TextControlBackground"));
+    IInspectable borderThicknessRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderThemeThickness"));
+    IInspectable borderBrushRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderBrush"));
+    // IInspectable cornerRes = Application::Current().Resources().Lookup(box_value(L"TextControlCornerRadius"));
+
+    Brush bgBrush = unbox_value<Brush>(bgRes);
+    Thickness border = unbox_value<Thickness>(borderThicknessRes);
+    Brush borderBrush = unbox_value<Brush>(borderBrushRes);
+    CornerRadius cornerRadius = { 4, 4, 4, 4 }; //unbox_value<CornerRadius>(cornerRes);
+
+    pathbar.Background(bgBrush);
+    pathbar.BorderBrush(borderBrush);
+    pathbar.BorderThickness(border);
+    pathbar.CornerRadius(cornerRadius);
+
+    Grid content = Grid();
+    pathbar.Child(content);
+
+    GridLength gl;
+    gl.Value = 0;
+    gl.GridUnitType = GridUnitType::Auto;
+
+    ColumnDefinition coldef = ColumnDefinition();
+    coldef.Width(gl);
+    content.ColumnDefinitions().Append(coldef);
+
+    gl.Value = 1;
+    gl.GridUnitType = GridUnitType::Star;
+
+    ColumnDefinition coldef2 = ColumnDefinition();
+    coldef2.Width(gl);
+    content.ColumnDefinitions().Append(coldef2);
+
+    TextBox pathTextBox = TextBox();
+    Thickness t = { 0, 0, 0, 0 };
+    CornerRadius c = { 0 ,0, 0, 0 };
+    pathTextBox.BorderThickness(t);
+    //pathTextBox.CornerRadius(c);
+
+
+    pathTextBox.HorizontalAlignment(HorizontalAlignment::Stretch);
+    content.SetColumn(pathTextBox, 0);
+    content.SetColumnSpan(pathTextBox, 2);
+
+    content.Children().Append(pathTextBox);
+
+    // stackpanel for buttons
+    StackPanel buttons = StackPanel();
+    buttons.Orientation(Orientation::Horizontal);
+    buttons.Visibility(Visibility::Collapsed);
+    content.SetColumn(buttons, 0);
+    content.Children().Append(buttons);
+
+    TextBlock filler = TextBlock();
+    filler.VerticalAlignment(VerticalAlignment::Stretch);
+    //filler.Text(winrt::hstring(L"hello filler"));
+
+    filler.HorizontalAlignment(HorizontalAlignment::Stretch);
+    filler.VerticalAlignment(VerticalAlignment::Stretch);
+    content.SetColumn(filler, 1);
+    content.Children().Append(filler);
+
+    filler.PointerPressed(
+        winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(
+            [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {
+                pathTextBox.Visibility(Visibility::Visible);
+                buttons.Visibility(Visibility::Collapsed);
+                filler.Visibility(Visibility::Collapsed);
+                pathTextBox.SelectionStart(pathTextBox.Text().size());
+                pathTextBox.SelectionLength(0);
+                pathTextBox.Focus(FocusState::Keyboard);
+            })
+    );
+
+    //pathTextBox.Visibility(Visibility::Collapsed);
+
+    UiPathTextField* uipathbar = new UiPathTextField;
+    ui_context_add_pathtextfield_destructor(current->ctx, uipathbar);
+    uipathbar->grid = content;
+    uipathbar->buttons = buttons;
+    uipathbar->textbox = pathTextBox;
+    uipathbar->filler = filler;
+    uipathbar->obj = obj;
+    uipathbar->getpathelm = args.getpathelm ? args.getpathelm : default_pathelm_func;
+    uipathbar->getpathelmdata = args.getpathelmdata;
+    uipathbar->onactivate = args.onactivate;
+    uipathbar->onactivatedata = args.onactivatedata;
+    uipathbar->ondragstart = args.ondragstart;
+    uipathbar->ondragstartdata = args.ondragstartdata;
+    uipathbar->ondragcomplete = args.ondragcomplete;
+    uipathbar->ondragcompletedata = args.ondragcompletedata;
+    uipathbar->ondrop = args.ondrop;
+    uipathbar->ondropdata = args.ondropsdata;
+
+
+    pathTextBox.KeyDown(
+        winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(
+            [=](winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) {
+                auto key = e.Key();
+                bool showButtons = false;
+                bool update = false;
+                if (key == Windows::System::VirtualKey::Escape) {
+                    showButtons = true;
+                }
+                else if (key == Windows::System::VirtualKey::Enter) {
+                    showButtons = true;
+                    update = true;
+                }
+
+                if (showButtons) {
+                    pathTextBox.Visibility(Visibility::Collapsed);
+                    buttons.Visibility(Visibility::Visible);
+                    filler.Visibility(Visibility::Visible);
+                    if (update) {
+                        std::wstring value(pathTextBox.Text());
+                        char* full_path = wchar2utf8(value.c_str(), value.length());
+                        ui_pathtextfield_update(uipathbar, full_path);
+                        free(full_path);
+                    }
+
+                    //buttons.Focus(FocusState::Keyboard);
+                }
+            })
+    );
+
+
+    UIElement elm = pathbar;
+    UiWidget* widget = new UiWidget(elm);
+    widget->data1 = uipathbar;
+    ui_context_add_widget_destructor(current->ctx, widget);
+
+    // bind var
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = uipathbar;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+    }
+
+    // add listview to current container
+    UI_APPLY_LAYOUT1(current, args);
+
+    current->container->Add(pathbar, false);
+
+    return widget;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,73 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/text.h"
+#include "toolkit.h"
+
+#include "../ui/container.h"
+
+struct UiPathTextField {
+    winrt::Microsoft::UI::Xaml::Controls::Grid grid = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::StackPanel buttons = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::TextBox textbox = { nullptr };
+    winrt::Microsoft::UI::Xaml::Controls::TextBlock filler = { nullptr };
+
+    ~UiPathTextField();
+
+    UiPathElm* current_path = nullptr;
+    size_t current_path_nelms = 0;
+
+    UiObject* obj;
+
+    ui_pathelm_func getpathelm;
+    void* getpathelmdata;
+    
+    ui_callback onactivate;
+    void* onactivatedata;
+
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropdata;
+};
+
+char* ui_string_get(UiString* str, std::wstring& value);
+std::wstring ui_string_set(UiString* str, const char* value);
+
+extern "C" char* ui_textfield_get(UiString *str);
+extern "C" void  ui_textfield_set(UiString *str, const char *newvalue);
+
+extern "C" char* ui_passwordfield_get(UiString * str);
+extern "C" void  ui_passwordfield_set(UiString * str, const char* newvalue);
+
+extern "C" char* ui_path_textfield_get(UiString * str);
+extern "C" void  ui_path_textfield_set(UiString * str, const char* newvalue);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/toolkit.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,232 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "toolkit.h"
+
+#include <cx/allocator.h>
+#include <cx/mempool.h>
+
+#include "../common/context.h"
+#include "../common/toolbar.h"
+
+#include "icons.h"
+
+#include "MainWindow.xaml.h"
+
+#include "App.xaml.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+
+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;
+
+static ui_callback   appclose_fnc;
+
+static void* appclose_udata;
+
+
+static UiObject* active_window;
+
+void ui_app_run_startup() {
+	if (startup_func) {
+		startup_func(NULL, startup_data);
+	}
+}
+
+class App : public ApplicationT<App, IXamlMetadataProvider> {
+public:
+	void OnLaunched(LaunchActivatedEventArgs const&) {
+		Resources().MergedDictionaries().Append(XamlControlsResources());
+		if (startup_func) {
+			startup_func(NULL, startup_data);
+		}
+
+		//auto window = make<winui::implementation::MainWindow>();
+		//window.Activate();
+	}
+	IXamlType GetXamlType(TypeName const& type) {
+		return provider.GetXamlType(type);
+	}
+	IXamlType GetXamlType(hstring const& fullname) {
+		return provider.GetXamlType(fullname);
+	}
+	com_array<XmlnsDefinition> GetXmlnsDefinitions() {
+		return provider.GetXmlnsDefinitions();
+	}
+private:
+	XamlControlsXamlMetaDataProvider provider;
+};
+
+UiWidget::UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm) : uielement(elm) {}
+
+extern "C" void destroy_ui_window_wrapper(void* ptr) {
+	UiWindow* win = (UiWindow*)ptr;
+	delete win;
+}
+
+extern "C" void destroy_ui_widget_wrapper(void* ptr) {
+	UiWidget* widget = (UiWidget*)ptr;
+	delete widget;
+}
+
+extern "C" void destroy_ui_container_wrapper(void* ptr) {
+	UiContainer* ctn = (UiContainer*)ptr;
+	delete ctn;
+}
+
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win) {
+	cxMempoolRegister(ctx->mp, win, destroy_ui_window_wrapper);
+}
+
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget) {
+	cxMempoolRegister(ctx->mp, widget, destroy_ui_widget_wrapper);
+}
+
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container) {
+	cxMempoolRegister(ctx->mp, container, destroy_ui_container_wrapper);
+}
+
+
+UiEvent ui_create_int_event(UiObject* obj, int64_t i) {
+	UiEvent evt;
+	evt.obj = obj;
+	evt.window = obj->window;
+	evt.document = obj->ctx->document;
+	evt.eventdata = nullptr;
+	evt.intval = i;
+	return evt;
+}
+
+
+#include <MddBootstrap.h>
+
+void ui_appsdk_bootstrap(void) {
+	const UINT32 majorMinorVersion{ 0x00010002 };
+	PCWSTR versionTag{ L"" };
+	const PACKAGE_VERSION minVersion{};
+
+	const HRESULT hr = MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion);
+	if (FAILED(hr)) {
+		exit(102);
+	}
+}
+
+void ui_init(const char* appname, int argc, char** argv) {
+	application_name = appname;
+
+	//ui_appsdk_bootstrap();
+
+	uic_toolbar_init();
+}
+
+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() {
+	/*
+	init_apartment();
+	//Application::Start([](auto&&) {make<App>(); });
+
+	::winrt::Microsoft::UI::Xaml::Application::Start(
+		[](auto&&)
+		{
+			::winrt::make<::winrt::winui::implementation::App>();
+		});
+		*/
+	{
+		void (WINAPI * pfnXamlCheckProcessRequirements)();
+		auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll");
+		if (module)
+		{
+			pfnXamlCheckProcessRequirements = reinterpret_cast<decltype(pfnXamlCheckProcessRequirements)>(GetProcAddress(module, "XamlCheckProcessRequirements"));
+			if (pfnXamlCheckProcessRequirements)
+			{
+				(*pfnXamlCheckProcessRequirements)();
+			}
+
+			::FreeLibrary(module);
+		}
+	}
+
+	winrt::init_apartment(winrt::apartment_type::single_threaded);
+	::winrt::Microsoft::UI::Xaml::Application::Start(
+		[](auto&&)
+		{
+			::winrt::make<::winrt::winui::implementation::App>();
+		});
+}
+
+class UiWin {
+public:
+	Window window;
+};
+
+void ui_show(UiObject* obj) {
+	if (obj->wobj) {
+		obj->wobj->window.Activate();
+	} else {
+		// TODO
+	}
+}
+
+void ui_close(UiObject* obj) {
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "../ui/toolkit.h"
+
+typedef void(*ui_eventfunc)(void*, void*);
+
+void ui_app_run_startup();
+
+extern "C" void destroy_ui_window_wrapper(void* ptr);
+extern "C" void destroy_ui_widget_wrapper(void* ptr);
+
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win);
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget);
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container);
+
+UiEvent ui_create_int_event(UiObject* obj, int64_t i);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/util.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,46 @@
+#include "pch.h"
+
+#include "util.h"
+
+#include <stdlib.h>
+
+
+wchar_t* str2wstr(const char* str, int* newlen) {
+    size_t len = strlen(str);
+
+    return str2wstr_len(str, len, newlen);
+}
+
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen) {
+    wchar_t* wstr = (wchar_t*)calloc(len + 1, sizeof(wchar_t));
+    int wlen = MultiByteToWideChar(
+        CP_UTF8,
+        0,
+        str,
+        len,
+        wstr,
+        len + 1
+    );
+    if (newlen) {
+        *newlen = wlen;
+    }
+    wstr[wlen] = 0;
+
+    return wstr;
+}
+
+char* wchar2utf8(const wchar_t* wstr, size_t wlen) {
+    size_t maxlen = wlen * 4;
+    char* ret = (char*)malloc(maxlen + 1);
+    int ret_len = WideCharToMultiByte(
+        CP_UTF8,
+        0,
+        wstr,
+        wlen,
+        ret,
+        maxlen,
+        NULL,
+        NULL);
+    ret[ret_len] = 0;
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/util.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,7 @@
+#pragma once
+
+wchar_t* str2wstr(const char* str, int* newlen);
+
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen);
+
+char* wchar2utf8(const wchar_t* wstr, size_t wlen);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/window.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,177 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 "pch.h"
+
+#include "window.h"
+
+#include "appmenu.h"
+#include "commandbar.h"
+#include "container.h"
+#include "util.h"
+
+#include "../common/context.h"
+
+#include <stdlib.h>
+
+#include <cx/mempool.h>
+
+#include "MainWindow.xaml.h"
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::Controls::Primitives;
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;
+using namespace Microsoft::UI::Xaml::Markup;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+
+UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}
+
+UiObject* ui_window(const char* title, void* window_data) {
+	CxMempool* mp = cxBasicMempoolCreate(256);
+	UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
+
+	obj->ctx = uic_context(obj, mp);
+	obj->window = window_data;
+
+	Window window = Window();
+	//Window window = make<winui::implementation::MainWindow>();
+
+	winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };
+	Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);
+
+	window.ExtendsContentIntoTitleBar(true);
+
+	Grid grid = Grid();
+	window.Content(grid);
+
+	StackPanel titleBar = StackPanel();
+	Thickness titleBarPadding = { 10, 5, 5, 10 };
+	titleBar.Padding(titleBarPadding);
+	titleBar.Orientation(Orientation::Horizontal);
+	TextBlock titleLabel = TextBlock();
+	titleBar.Children().Append(titleLabel);
+
+	if (title) {
+		wchar_t* wtitle = str2wstr(title, nullptr);
+		window.Title(wtitle);
+		titleLabel.Text(hstring(wtitle));
+		free(wtitle);
+	}
+
+	window.SetTitleBar(titleBar);
+
+	obj->wobj = new UiWindow(window);
+	ui_context_add_window_destructor(obj->ctx, obj->wobj);
+
+	window.Closed([obj](IInspectable const& sender, WindowEventArgs) {
+		cxMempoolDestroy(obj->ctx->mp);
+	});
+
+	obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);
+
+	titleBar.VerticalAlignment(VerticalAlignment::Top);
+	obj->container->Add(titleBar, false);
+
+	if (uic_get_menu_list()) {
+		// create/add menubar
+		MenuBar mb = ui_create_menubar(obj);
+		mb.VerticalAlignment(VerticalAlignment::Top);
+		obj->container->Add(mb, false);
+	}
+
+	if (uic_toolbar_isenabled()) {
+		// create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto"
+		Grid toolbar_grid = Grid();
+		GridLength gl;
+		gl.Value = 0;
+		gl.GridUnitType = GridUnitType::Auto;
+
+		ColumnDefinition coldef0 = ColumnDefinition();
+		coldef0.Width(gl);
+		toolbar_grid.ColumnDefinitions().Append(coldef0);
+
+		gl.Value = 1;
+		gl.GridUnitType = GridUnitType::Star;
+		ColumnDefinition coldef1 = ColumnDefinition();
+		coldef1.Width(gl);
+		toolbar_grid.ColumnDefinitions().Append(coldef1);
+
+		gl.Value = 0;
+		gl.GridUnitType = GridUnitType::Auto;
+		ColumnDefinition coldef2 = ColumnDefinition();
+		coldef2.Width(gl);
+		toolbar_grid.ColumnDefinitions().Append(coldef2);
+
+		// rowdef
+		gl.Value = 0;
+		gl.GridUnitType = GridUnitType::Auto;
+		RowDefinition rowdef = RowDefinition();
+		rowdef.Height(gl);
+		toolbar_grid.RowDefinitions().Append(rowdef);
+
+
+		// create commandbar
+		CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+		CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+		CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+
+		bool addappmenu = true;
+		if (def_r->size > 0) {
+			CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu);
+			toolbar_grid.SetColumn(toolbar_r, 2);
+			toolbar_grid.SetRow(toolbar_r, 0);
+			toolbar_grid.Children().Append(toolbar_r);
+			addappmenu = false;
+		}
+		if (def_c->size > 0) {
+			CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu);
+			toolbar_c.HorizontalAlignment(HorizontalAlignment::Center);
+			toolbar_grid.SetColumn(toolbar_c, 1);
+			toolbar_grid.SetRow(toolbar_c, 0);
+			toolbar_grid.Children().Append(toolbar_c);
+			addappmenu = false;
+		}
+		if (def_l->size > 0) {
+			CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu);
+			toolbar_grid.SetColumn(toolbar_l, 0);
+			toolbar_grid.SetRow(toolbar_l, 0);
+			toolbar_grid.Children().Append(toolbar_l);
+		}
+
+		toolbar_grid.VerticalAlignment(VerticalAlignment::Top);
+		obj->container->Add(toolbar_grid, false);
+	}
+
+	obj->window = window_data;
+
+	return obj;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "toolkit.h"
+
+#include "../ui/window.h"
+
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+#include <winrt/Microsoft.UI.Xaml.Markup.h>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/winui.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.props')" />
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" />
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <PropertyGroup Label="Globals">
+    <CppWinRTOptimized>true</CppWinRTOptimized>
+    <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
+    <MinimalCoreWin>true</MinimalCoreWin>
+    <ProjectGuid>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</ProjectGuid>
+    <ProjectName>winui</ProjectName>
+    <RootNamespace>winui</RootNamespace>
+    <!--
+      $(TargetName) should be same as $(RootNamespace) so that the produced binaries (.exe/.pri/etc.)
+      have a name that matches the .winmd
+    -->
+    <TargetName>$(RootNamespace)</TargetName>
+    <DefaultLanguage>de-DE</DefaultLanguage>
+    <MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
+    <AppContainerApplication>false</AppContainerApplication>
+    <AppxPackage>false</AppxPackage>
+    <ApplicationType>Windows Store</ApplicationType>
+    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
+    <UseWinUI>true</UseWinUI>
+    <EnableMsixTooling>true</EnableMsixTooling>
+    <WindowsPackageType>None</WindowsPackageType>
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <DesktopCompatible>true</DesktopCompatible>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
+      <WarningLevel>Level4</WarningLevel>
+      <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
+    <ClCompile>
+      <PreprocessorDefinitions>_DEBUG;DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
+    <ClCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup Condition="'$(WindowsPackageType)'!='None' and Exists('Package.appxmanifest')">
+    <AppxManifest Include="Package.appxmanifest">
+      <SubType>Designer</SubType>
+    </AppxManifest>
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\ui\button.h" />
+    <ClInclude Include="..\ui\container.h" />
+    <ClInclude Include="..\ui\display.h" />
+    <ClInclude Include="..\ui\dnd.h" />
+    <ClInclude Include="..\ui\entry.h" />
+    <ClInclude Include="..\ui\graphics.h" />
+    <ClInclude Include="..\ui\image.h" />
+    <ClInclude Include="..\ui\menu.h" />
+    <ClInclude Include="..\ui\properties.h" />
+    <ClInclude Include="..\ui\range.h" />
+    <ClInclude Include="..\ui\stock.h" />
+    <ClInclude Include="..\ui\text.h" />
+    <ClInclude Include="..\ui\toolbar.h" />
+    <ClInclude Include="..\ui\toolkit.h" />
+    <ClInclude Include="..\ui\tree.h" />
+    <ClInclude Include="..\ui\ui.h" />
+    <ClInclude Include="..\ui\window.h" />
+    <ClInclude Include="appmenu.h" />
+    <ClInclude Include="button.h" />
+    <ClInclude Include="commandbar.h" />
+    <ClInclude Include="container.h" />
+    <ClInclude Include="dnd.h" />
+    <ClInclude Include="icons.h" />
+    <ClInclude Include="label.h" />
+    <ClInclude Include="list.h" />
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="App.xaml.h">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClInclude>
+    <ClInclude Include="MainWindow.xaml.h">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </ClInclude>
+    <ClInclude Include="stock.h" />
+    <ClInclude Include="table.h" />
+    <ClInclude Include="text.h" />
+    <ClInclude Include="toolkit.h" />
+    <ClInclude Include="util.h" />
+    <ClInclude Include="window.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml" />
+    <Page Include="MainWindow.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="appmenu.cpp" />
+    <ClCompile Include="button.cpp" />
+    <ClCompile Include="commandbar.cpp" />
+    <ClCompile Include="container.cpp" />
+    <ClCompile Include="dnd.cpp" />
+    <ClCompile Include="icons.cpp" />
+    <ClCompile Include="label.cpp" />
+    <ClCompile Include="list.cpp" />
+    <ClCompile Include="pch.cpp">
+      <PrecompiledHeader>Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="App.xaml.cpp">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="MainWindow.xaml.cpp">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+    <ClCompile Include="stock.cpp" />
+    <ClCompile Include="table.cpp" />
+    <ClCompile Include="text.cpp" />
+    <ClCompile Include="toolkit.cpp" />
+    <ClCompile Include="util.cpp" />
+    <ClCompile Include="window.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="App.idl">
+      <SubType>Code</SubType>
+      <DependentUpon>App.xaml</DependentUpon>
+    </Midl>
+    <Midl Include="MainWindow.idl">
+      <SubType>Code</SubType>
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </Midl>
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="readme.txt">
+      <DeploymentContent>false</DeploymentContent>
+    </Text>
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\LockScreenLogo.scale-200.png" />
+    <Image Include="Assets\SplashScreen.scale-200.png" />
+    <Image Include="Assets\Square150x150Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
+    <Image Include="Assets\StoreLogo.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-200.png" />
+  </ItemGroup>
+  <!--
+    Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
+    Tools extension to be activated for this project even if the Windows App SDK Nuget
+    package has not yet been restored.
+  -->
+  <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+    <ProjectCapability Include="Msix" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\make\vs\ucx\ucx.vcxproj">
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\make\vs\uicommon\uicommon.vcxproj">
+      <Project>{8b88698e-c185-4383-99fe-0c34d6deed2e}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <!--
+    Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
+    Explorer "Package and Publish" context menu entry to be enabled for this project even if
+    the Windows App SDK Nuget package has not yet been restored.
+  -->
+  <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+    <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.targets')" />
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.props'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.3.230602002\build\native\Microsoft.WindowsAppSDK.targets'))" />
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/winui.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Page Include="MainWindow.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="App.idl" />
+    <Midl Include="MainWindow.idl" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="pch.cpp" />
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+    <ClCompile Include="appmenu.cpp" />
+    <ClCompile Include="button.cpp" />
+    <ClCompile Include="commandbar.cpp" />
+    <ClCompile Include="container.cpp" />
+    <ClCompile Include="list.cpp" />
+    <ClCompile Include="table.cpp" />
+    <ClCompile Include="text.cpp" />
+    <ClCompile Include="toolkit.cpp" />
+    <ClCompile Include="util.cpp" />
+    <ClCompile Include="window.cpp" />
+    <ClCompile Include="stock.cpp" />
+    <ClCompile Include="icons.cpp" />
+    <ClCompile Include="label.cpp" />
+    <ClCompile Include="dnd.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="appmenu.h" />
+    <ClInclude Include="button.h" />
+    <ClInclude Include="commandbar.h" />
+    <ClInclude Include="container.h" />
+    <ClInclude Include="list.h" />
+    <ClInclude Include="table.h" />
+    <ClInclude Include="text.h" />
+    <ClInclude Include="toolkit.h" />
+    <ClInclude Include="util.h" />
+    <ClInclude Include="window.h" />
+    <ClInclude Include="..\ui\button.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\container.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\display.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\dnd.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\entry.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\graphics.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\image.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\menu.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\properties.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\range.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\stock.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\text.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\toolbar.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\toolkit.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\tree.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\ui.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="..\ui\window.h">
+      <Filter>public</Filter>
+    </ClInclude>
+    <ClInclude Include="stock.h" />
+    <ClInclude Include="icons.h" />
+    <ClInclude Include="label.h" />
+    <ClInclude Include="dnd.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\Wide310x150Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LockScreenLogo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+  </ItemGroup>
+  <ItemGroup>
+    <Filter Include="Assets">
+      <UniqueIdentifier>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="public">
+      <UniqueIdentifier>{2b58fe46-d27b-4335-b63c-13ec2204fa24}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="readme.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <Manifest Include="app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/Makefile	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+$(WPF_OBJPRE)%.o: wpf/%.c
+	$(CC) -o $@ -c $(CFLAGS) $<
+
+$(UI_LIB): $(OBJ) uiw
+	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
+uiw:
+	cd wpf/UIwrapper; $(MSBUILD)
+	cp $(BUILD_ROOT)/build/UIwrapper/UIwrapper.dll $(BUILD_ROOT)/build/bin/
+	cp $(BUILD_ROOT)/build/UIcore/UIcore.dll $(BUILD_ROOT)/build/bin/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Application.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace UI
+{  
+    public class Application
+    {
+        private static Application instance;
+
+        private System.Windows.Application application;
+
+        private Thread thread;
+
+        public String Name;
+
+        public IApplicationCallbacks callbacks;
+
+        public List<Window> Windows = new List<Window>();
+        public ApplicationMenu Menu = new ApplicationMenu();
+        public MainToolBar ToolBar = new MainToolBar();
+        
+        private Application() : base()
+        {
+            thread = new Thread(() => RunApplication());
+            thread.SetApartmentState(ApartmentState.STA);
+        }
+
+        public static Application GetInstance()
+        {
+            if (instance == null)
+            {
+                instance = new Application();
+                GC.KeepAlive(instance);
+            }
+            return instance;
+        }
+
+        public Thread Start()
+        {
+            thread.Start();
+            return thread;
+        }
+
+        private void RunApplication()
+        {
+            application = new System.Windows.Application();
+
+            if(callbacks != null)
+            {
+                callbacks.OnStartup();
+            }
+            application.Run();
+            if(callbacks != null)
+            {
+                callbacks.OnExit();
+            }
+        }
+
+        public void AddWindow(Window window)
+        {
+            Windows.Add(window);
+        }
+
+        public void RemoveWindow(Window window)
+        {
+            Windows.Remove(window);
+            if (Windows.Count == 0)
+            {
+                application.Shutdown();
+            }
+        }
+    }
+
+    public class ResultExec<T>
+    {
+        public T Result;
+        public Func<T> Func;
+
+        public void Exec()
+        {
+            Result = Func.Invoke();
+        }
+    }
+
+    public class VoidExec
+    {
+        public Action Action;
+
+        public void Exec()
+        {
+            Action.Invoke();
+        }
+    }
+
+    public interface IApplicationCallbacks
+    {
+        void OnStartup();
+        void OnOpen();
+        void OnExit();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Container.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,321 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace UI
+{
+    public interface Container
+    {
+        Layout Layout { get; set; }
+        
+        void Add(UIElement control, bool fill);
+    }
+
+    public class Layout
+    {
+        public bool? Fill { get; set; }
+        public bool Hexpand { get; set; }
+        public bool Vexpand { get; set; }
+        public bool NewLine { get; set; }
+        public int GridWidth { get; set; }
+        public String Label { get; set; }
+
+        public Layout()
+        {
+            Reset();
+        }
+
+        public bool IsFill(bool fill)
+        {
+            if (Fill != null)
+            {
+                
+                return (bool)Fill;
+            }
+            return fill;
+        }
+
+        public void Reset()
+        {
+            Fill = null;
+            Hexpand = false;
+            Vexpand = false;
+            NewLine = false;
+            GridWidth = 1;
+            Label = null;
+        }
+    }
+
+    public enum BoxOrientation
+    {
+        VERTICAL,
+        HORIZONTAL
+    }
+
+    public class BoxContainer : Grid, Container
+    {
+        public Layout Layout { get; set; }
+        
+        private BoxOrientation Orientation;
+        private int Spacing;
+
+        private int x = 0;
+        private int y = 0;
+
+        private bool filled = false;
+
+        public BoxContainer(BoxOrientation orientation, int margin, int spacing) : base()
+        {
+            Layout = new Layout();
+            Margin = new Thickness(margin);
+            Spacing = spacing;
+            
+            Orientation = orientation;
+            if(Orientation == BoxOrientation.HORIZONTAL)
+            {
+                RowDefinition row = new RowDefinition();
+                row.Height = new GridLength(1, GridUnitType.Star);
+                RowDefinitions.Add(row);
+            }
+            else
+            {
+                ColumnDefinition col = new ColumnDefinition();
+                col.Width = new GridLength(1, GridUnitType.Star);
+                ColumnDefinitions.Add(col);
+            }
+        }
+
+        public BoxContainer(Container parent, BoxOrientation orientation, int margin, int spacing) : this(orientation, margin, spacing)
+        {
+            parent.Add(this, true);
+        }
+        
+        public void Add(UIElement control, bool fill)
+        {
+            fill = Layout.IsFill(fill);
+            
+            if(Orientation == BoxOrientation.HORIZONTAL)
+            {
+                if(Spacing > 0)
+                {
+                    ColumnDefinition spaceCol = new ColumnDefinition();
+                    spaceCol.Width = new GridLength(Spacing, GridUnitType.Pixel);
+                    ColumnDefinitions.Add(spaceCol);
+                    x++;
+                }
+
+                ColumnDefinition col = new ColumnDefinition();
+                if(filled && fill)
+                {
+                    fill = false;
+                    Console.WriteLine("BoxContainer can only contain one filled control");
+                }
+                if(fill)
+                {
+                    col.Width = new GridLength(1, GridUnitType.Star);
+                    filled = true;
+                }
+                else
+                {
+                    col.Width = GridLength.Auto;
+                }
+                ColumnDefinitions.Add(col);
+            }
+            else
+            {
+                if (Spacing > 0)
+                {
+                    RowDefinition spaceRow = new RowDefinition();
+                    spaceRow.Height = new GridLength(Spacing, GridUnitType.Pixel);
+                    RowDefinitions.Add(spaceRow);
+                    y++;
+                }
+
+                RowDefinition row = new RowDefinition();
+                if (filled && fill)
+                {
+                    fill = false;
+                    Console.WriteLine("BoxContainer can only contain one filled control");
+                }
+                if(fill)
+                {
+                    row.Height = new GridLength(1, GridUnitType.Star);
+                    filled = true;
+                }
+                else
+                {
+                    row.Height = GridLength.Auto;
+                }
+                RowDefinitions.Add(row);
+            }
+
+            Grid.SetColumn(control, x);
+            Grid.SetRow(control, y);
+            Children.Add(control);
+
+            if(Orientation == BoxOrientation.HORIZONTAL)
+            {
+                x++;
+            }
+            else
+            {
+                y++;
+            }
+
+            Layout.Reset();
+        }
+    }
+    
+    public class GridContainer : Grid, Container
+    {
+        public Layout Layout { get; set; }
+
+        private int X = 0;
+        private int Y = 0;
+        private int CurrentWidth = 0;
+        private int CurrentHeight = 0;
+
+        private int ColSpacing;
+        private int RowSpacing;
+
+        public GridContainer(Container parent, int margin, int colspacing, int rowspacing) : base()
+        {
+            Layout = new Layout();
+
+            Margin = new Thickness(margin);
+            ColSpacing = colspacing;
+            RowSpacing = rowspacing;
+
+            parent.Add(this, true);
+        }
+
+        public void Add(UIElement control, bool fill)
+        {
+            if(Layout.NewLine)
+            {
+                X = 0;
+                Y++;
+            }
+
+            ColumnDefinition col;
+            RowDefinition row;
+            bool getcol = false;
+            if(X >= CurrentWidth)
+            {
+                if (ColSpacing > 0 && X != 0)
+                {
+                    ColumnDefinition spaceCol = new ColumnDefinition();
+                    spaceCol.Width = new GridLength(ColSpacing, GridUnitType.Pixel);
+                    ColumnDefinitions.Add(spaceCol);
+                    X++;
+                }
+
+                col = new ColumnDefinition();
+                col.Width = GridLength.Auto;
+                ColumnDefinitions.Add(col);
+
+                CurrentWidth = X + 1;
+            }
+            else
+            {
+                if (ColSpacing > 0 && X % 2 > 0)
+                {
+                    X++;
+                }
+                col = ColumnDefinitions.ElementAt(X);
+            }
+
+            if(getcol)
+            {
+                col = ColumnDefinitions.ElementAt(X);
+            }
+
+            if (Y >= CurrentHeight)
+            {
+                if (RowSpacing > 0 && Y != 0)
+                {
+                    RowDefinition spaceRow = new RowDefinition();
+                    spaceRow.Height = new GridLength(RowSpacing, GridUnitType.Pixel);
+                    RowDefinitions.Add(spaceRow);
+                    Y++;
+                }
+
+                row = new RowDefinition();
+                row.Height = GridLength.Auto;
+                RowDefinitions.Add(row);
+                CurrentHeight = Y + 1;
+            }
+            else
+            {
+                row = RowDefinitions.ElementAt(Y);
+            }
+
+            if(Layout.Hexpand)
+            {
+                col.Width = new GridLength(1, GridUnitType.Star);
+            }
+            if(Layout.Vexpand)
+            {
+                row.Height = new GridLength(1, GridUnitType.Star);
+            }
+
+            int gridwidth = Layout.GridWidth;
+            if(gridwidth > 1)
+            {
+                gridwidth++;
+            }
+
+            Grid.SetColumn(control, X);
+            Grid.SetRow(control, Y);
+            Grid.SetColumnSpan(control, gridwidth);
+            Children.Add(control);
+
+            Layout.Reset();
+            X += gridwidth;
+        }
+    }
+
+    public class ScrollViewerContainer : ScrollViewer, Container
+    {
+        public Layout Layout { get; set; }
+
+        public ScrollViewerContainer(Container parent) : base()
+        {
+            Layout = new Layout();
+
+            HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+            VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
+
+            parent.Add(this, true);
+        }
+
+        public void Add(UIElement control, bool fill)
+        {
+            Content = control;
+        }
+    }
+
+    public class TabViewContainer : TabControl, Container
+    {
+        public Layout Layout { get; set; }
+
+        public TabViewContainer(Container parent) : base()
+        {
+            Layout = new Layout();
+            parent.Add(this, true);
+        }
+
+        public void Add(UIElement control, bool fill)
+        {
+            TabItem tab = new TabItem();
+            tab.Header = Layout.Label != null ? Layout.Label : "New Tab";
+            Items.Add(tab);
+            tab.Content = control;
+            Layout.Reset();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Controls.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace UI
+{  
+    public class Controls
+    {
+        public static Button Button(Container container, String label, RoutedEventHandler e)
+        {
+            Button button = new Button();
+            button.Content = label;
+            container.Add(button, false);
+
+            button.Click += e;
+
+            return button;
+        }
+
+        public static Label Label(Container container, String str, int alignment)
+        {
+            HorizontalAlignment a;
+            switch (alignment)
+            {
+                case 0: a = HorizontalAlignment.Left; break;
+                case 1: a = HorizontalAlignment.Right; break;
+                case 2: a = HorizontalAlignment.Center; break;
+                default: a = HorizontalAlignment.Left; break;
+            }
+
+            Label label = new Label();
+            label.HorizontalAlignment = a;
+            label.Content = str;
+            container.Add(label, false);
+
+            return label;
+        }
+
+        public static Label Space(Container container)
+        {
+            return Label(container, null, 2);
+        }
+
+        public static Separator Separator(Container container)
+        {
+            Separator separator = new Separator();
+            container.Add(separator, false);
+            return separator;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/DrawingArea.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using System.Diagnostics;
+
+namespace UI
+{
+    public class DrawingArea : System.Windows.Controls.Canvas
+    {
+        public Action<int,int> resizeCallback;
+
+        public Color Color;
+        private Brush Brush;
+
+        public DrawingArea(Container container) : base()
+        {
+            this.SizeChanged += UpdateSize;
+            ResetGraphics();
+
+            container.Add(this, true);
+        }
+
+        public void UpdateSize(object sender, SizeChangedEventArgs e)
+        {
+            if(resizeCallback != null)
+            {
+                Children.Clear();
+                ResetGraphics();
+
+                Size s = e.NewSize;
+                resizeCallback((int)s.Width, (int)s.Height);
+            }
+        }
+
+        public void Redraw()
+        {
+            if (resizeCallback != null)
+            {
+                Children.Clear();
+                ResetGraphics();
+
+                resizeCallback((int)ActualWidth, (int)ActualHeight);
+            }
+
+        }
+
+        private void ResetGraphics()
+        {
+            Color = Color.FromRgb(0, 0, 0);
+            Brush = System.Windows.Media.Brushes.Black;
+        }
+
+        public void SetColor(int r, int g, int b)
+        {
+            Color = Color.FromRgb((byte)r, (byte)g, (byte)b);
+            Brush = new SolidColorBrush(Color);
+        }
+
+        public void DrawLine(int x1, int y1, int x2, int y2)
+        {
+            Line line = new Line();
+            line.Stroke = Brush;
+            line.StrokeThickness = 1;
+            line.SnapsToDevicePixels = true;
+
+            line.X1 = x1;
+            line.Y1 = y1;
+            line.X2 = x2;
+            line.Y2 = y2;
+
+            Children.Add(line);
+        }
+
+        public void DrawRect(int x, int y, int w, int h, bool fill)
+        {
+            Rectangle rect = new Rectangle();
+            rect.Stroke = Brush;
+            rect.StrokeThickness = 1;
+            rect.SnapsToDevicePixels = true;
+            if(fill)
+            {
+                rect.Fill = Brush;
+            }
+
+            rect.Width = w;
+            rect.Height = h;
+            SetLeft(rect, x);
+            SetTop(rect, y);
+
+            Children.Add(rect);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/MainToolBar.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace UI
+{
+    public class MainToolBar
+    {
+        Dictionary<string, IToolItem> Items = new Dictionary<string, IToolItem>();
+        List<string> Defaults = new List<string>();
+
+        public MainToolBar()
+        {
+
+        }
+
+        public bool HasItems()
+        {
+            return Defaults.Count > 0 ? true : false;
+        }
+
+        public void AddDefault(string itemName)
+        {
+            Defaults.Add(itemName);
+        }
+
+        public void AddToolItem(string name, string label, Action<IntPtr> action)
+        {
+            ToolItem item = new ToolItem();
+            item.Label = label;
+            item.Action = action;
+            Items.Add(name, item);
+        }
+
+        public ToolBarTray CreateToolBarTray(IntPtr objptr)
+        {
+            ToolBarTray tray = new ToolBarTray();
+            ToolBar toolbar = new ToolBar();
+            tray.ToolBars.Add(toolbar);
+            foreach(string s in Defaults)
+            {
+                IToolItem item = Items[s];
+                item.AddTo(toolbar, objptr);
+            }
+
+            return tray;
+        }
+    }
+
+    public interface IToolItem
+    {
+        void AddTo(System.Windows.Controls.ToolBar toolbar, IntPtr uiobj);
+    }
+
+    public class ToolItem : IToolItem
+    {
+        public string Label { get; set; }
+        // TODO: icon
+        public Action<IntPtr> Action { get; set; }
+
+        public void AddTo(System.Windows.Controls.ToolBar toolbar, IntPtr uiobj)
+        {
+            Button button = new Button();
+            button.Content = Label;
+
+            EventCallback e = new EventCallback(uiobj, Action);
+            button.Click += e.Callback;
+
+            toolbar.Items.Add(button);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Menu.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace UI
+{
+    public class ApplicationMenu
+    {
+        private List<Menu> current = new List<Menu>();
+
+        public List<Menu> Menus = new List<Menu>();
+
+        public void AddMenu(String label)
+        {
+            current.Clear();
+            Menu menu = new Menu(label);
+            current.Add(menu);
+            Menus.Add(menu);
+        }
+
+        public Boolean IsEmpty()
+        {
+            return Menus.Count == 0 ? true : false;
+        }
+
+        public void AddSubMenu(String label)
+        {
+            Menu menu = new Menu(label);
+            current.Last().Items.Add(menu);
+            current.Add(menu);
+        }
+
+        public void EndSubMenu()
+        {
+            current.Remove(current.Last());
+        }
+
+        public void AddMenuItem(String label, Action<IntPtr> action)
+        {
+            if(current.Count != 0)
+            {
+                MenuItem item = new MenuItem(label, action);
+                current.Last().Items.Add(item);
+            }
+        }
+
+        public System.Windows.Controls.Menu CreateMenu(IntPtr uiobj)
+        {
+            System.Windows.Controls.Menu menu = new System.Windows.Controls.Menu();
+            menu.Background = new SolidColorBrush(Color.FromRgb(255, 255, 255));
+            foreach (Menu m in Menus)
+            {
+                System.Windows.Controls.MenuItem i = new System.Windows.Controls.MenuItem();
+                i.Header = m.Label;
+                m.AddItems(i, uiobj);
+                menu.Items.Add(i);
+            }
+            return menu;
+        }
+    }
+
+    public interface IMenuItem
+    {
+        void AddTo(System.Windows.Controls.MenuItem menu, IntPtr uiobj);
+    }
+
+    public class Menu : IMenuItem
+    {
+        public String Label;
+        public List<IMenuItem> Items = new List<IMenuItem>();
+
+        public Menu(String label)
+        {
+            Label = label;
+        }
+
+        public void AddItems(System.Windows.Controls.MenuItem i, IntPtr uiobj)
+        {
+            foreach (IMenuItem item in Items)
+            {
+                item.AddTo(i, uiobj);
+            }
+        }
+
+        void IMenuItem.AddTo(System.Windows.Controls.MenuItem menu, IntPtr uiobj)
+        {
+            System.Windows.Controls.MenuItem i = new System.Windows.Controls.MenuItem();
+            i.Header = Label;
+            AddItems(i, uiobj);
+            menu.Items.Add(i);
+        }
+    }
+
+    public class MenuItem : IMenuItem
+    {
+        String Label;
+        Action<IntPtr> Action;
+
+        public MenuItem(String label, Action<IntPtr> action)
+        {
+            Label = label;
+            Action = action;
+        }
+
+        void IMenuItem.AddTo(System.Windows.Controls.MenuItem menu, IntPtr uiobj)
+        {
+            System.Windows.Controls.MenuItem i = new System.Windows.Controls.MenuItem();
+            EventCallback cb = new EventCallback(uiobj, Action);
+            i.Click += cb.Callback;
+            i.Header = Label;
+            menu.Items.Add(i);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Properties/AssemblyInfo.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Allgemeine Informationen über eine Assembly werden über die folgenden 
+// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
+// die mit einer Assembly verknüpft sind.
+[assembly: AssemblyTitle("UIcore")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("UIcore")]
+[assembly: AssemblyCopyright("Copyright ©  2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 
+// für COM-Komponenten.  Wenn Sie auf einen Typ in dieser Assembly von 
+// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("e93c93b6-d270-4178-998f-7e68d9591874")]
+
+// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
+//
+//      Hauptversion
+//      Nebenversion 
+//      Buildnummer
+//      Revision
+//
+// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 
+// übernehmen, indem Sie "*" eingeben:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/TextArea.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace UI
+{
+    public class TextArea : System.Windows.Controls.TextBox
+    {
+        public TextArea(Container container, String text, bool textarea) : base()
+        {
+            bool fill = false;
+            if (textarea)
+            {
+                AcceptsReturn = true;
+                IsUndoEnabled = false; // we need our own undo stack
+                VerticalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Auto;
+                HorizontalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Auto;
+                fill = true;
+            }
+            else
+            {
+                HorizontalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Auto;
+                MinWidth = 15;
+            }
+
+            if (text != null)
+            {
+                Text = text;
+            }
+
+            container.Add(this, fill);
+        }
+
+
+        // ------------------ UiText methods ------------------
+
+        public void SetText(String str)
+        {
+            Text = str;
+        }
+
+        public String GetText()
+        {
+            return Text;
+        }
+
+        public String GetSubString(int begin, int end)
+        {
+            return null;
+        }
+
+        public void Insert(int pos, String str)
+        {
+
+        }
+
+        public int Position()
+        {
+            return CaretIndex;
+        }
+
+        public int Selection()
+        {
+            return 0;
+        }
+
+        public int Length()
+        {
+            return Text.Length;
+        }
+
+        public void Remove(int begin, int end)
+        {
+
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Toolkit.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace UI
+{
+    public class EventCallback
+    {
+        public IntPtr Object;
+        public Action<IntPtr> Action;
+
+        public EventCallback(IntPtr uiobj, Action<IntPtr> action)
+        {
+            Object = uiobj;
+            Action = action;
+        }
+
+        public void Callback(object sender, RoutedEventArgs a)
+        {
+            Action.Invoke(Object);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/UIcore.csproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{8573F7D8-F05F-4195-9005-1C219B976146}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>UIcore</RootNamespace>
+    <AssemblyName>UIcore</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\..\..\build\UIcore\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\..\..\build\UIcore\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Windows" />
+    <Reference Include="System.Windows.Controls.Ribbon" />
+    <Reference Include="System.Xaml" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Container.cs" />
+    <Compile Include="Controls.cs" />
+    <Compile Include="DrawingArea.cs" />
+    <Compile Include="MainToolBar.cs" />
+    <Compile Include="Menu.cs" />
+    <Compile Include="TextArea.cs" />
+    <Compile Include="Toolkit.cs" />
+    <Compile Include="Window.cs" />
+    <Compile Include="Application.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIcore/Window.cs	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace UI
+{
+    public class MainWindow : Window, Container
+    {
+        public Layout Layout
+        {
+            get
+            {
+                return Container.Layout;
+            }
+            set
+            {
+                Container.Layout = value;
+            }
+        }
+        
+        public IntPtr Object;
+        public Container Container;
+        
+        public MainWindow(String title, IntPtr uiobj)
+        {
+            Title = title;
+            Object = uiobj;
+            Width = 300;
+            Height = 300;
+
+            Grid windowGrid = new Grid();
+            ColumnDefinition column = new ColumnDefinition();
+            column.Width = new GridLength(1, GridUnitType.Star);
+            windowGrid.ColumnDefinitions.Add(column);
+            this.AddChild(windowGrid);
+            int rowIndex = 0;
+
+            // menu
+            Application app = Application.GetInstance();
+            System.Windows.Controls.Menu menu = null;
+            if (!app.Menu.IsEmpty())
+            {
+                menu = app.Menu.CreateMenu(uiobj);
+
+                RowDefinition menuRow = new RowDefinition();
+                menuRow.Height = GridLength.Auto;
+                windowGrid.RowDefinitions.Add(menuRow);
+
+                Grid.SetRow(menu, rowIndex);
+                Grid.SetColumn(menu, 0);
+                windowGrid.Children.Add(menu);
+                rowIndex++;
+            }
+
+            // toolbar
+            if(app.ToolBar.HasItems())
+            {
+                System.Windows.Controls.ToolBarTray tray = app.ToolBar.CreateToolBarTray(uiobj);
+                RowDefinition menuRow = new RowDefinition();
+                menuRow.Height = GridLength.Auto;
+                windowGrid.RowDefinitions.Add(menuRow);
+
+                Grid.SetRow(tray, rowIndex);
+                Grid.SetColumn(tray, 0);
+                windowGrid.Children.Add(tray);
+                rowIndex++;
+
+                if(menu != null)
+                {
+                    menu.Background = tray.Background;
+                }
+            }
+
+            // content
+            RowDefinition contentRow = new RowDefinition();
+            contentRow.Height = new GridLength(1, GridUnitType.Star);
+            windowGrid.RowDefinitions.Add(contentRow);
+            BoxContainer vbox = new BoxContainer(BoxOrientation.VERTICAL, 0, 0);
+            Grid.SetColumn(vbox, 0);
+            Grid.SetRow(vbox, rowIndex);
+            windowGrid.Children.Add(vbox);
+            rowIndex++;
+
+            Container = vbox;
+
+            Closed += CloseEvent;
+        }
+
+        public void ShowWindow()
+        {
+            this.Show();
+            Application.GetInstance().AddWindow(this);
+        }
+
+        public void CloseEvent(object sender, System.EventArgs e)
+        {
+            Application.GetInstance().RemoveWindow(this);
+        }
+
+        public void Add(UIElement control, bool fill)
+        {
+            Container.Add(control, fill);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper.sln	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UIwrapper", "UIwrapper\UIwrapper.vcxproj", "{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}"
+	ProjectSection(ProjectDependencies) = postProject
+		{8573F7D8-F05F-4195-9005-1C219B976146} = {8573F7D8-F05F-4195-9005-1C219B976146}
+	EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIcore", "..\UIcore\UIcore.csproj", "{8573F7D8-F05F-4195-9005-1C219B976146}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Debug|Any CPU.ActiveCfg = Debug|x64
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Debug|Any CPU.Build.0 = Debug|x64
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Debug|x64.ActiveCfg = Debug|x64
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Debug|x64.Build.0 = Debug|x64
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Release|Any CPU.ActiveCfg = Release|Win32
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Release|x64.ActiveCfg = Release|x64
+		{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}.Release|x64.Build.0 = Release|x64
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Debug|x64.Build.0 = Debug|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Release|x64.ActiveCfg = Release|Any CPU
+		{8573F7D8-F05F-4195-9005-1C219B976146}.Release|x64.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
Binary file ui/wpf/UIwrapper/UIwrapper.v12.suo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/AssemblyInfo.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,38 @@
+#include "stdafx.h"
+
+using namespace System;
+using namespace System::Reflection;
+using namespace System::Runtime::CompilerServices;
+using namespace System::Runtime::InteropServices;
+using namespace System::Security::Permissions;
+
+//
+// Allgemeine Informationen über eine Assembly werden über die folgenden
+// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
+// die mit einer Assembly verknüpft sind.
+//
+[assembly:AssemblyTitleAttribute(L"UIwrapper")];
+[assembly:AssemblyDescriptionAttribute(L"")];
+[assembly:AssemblyConfigurationAttribute(L"")];
+[assembly:AssemblyCompanyAttribute(L"")];
+[assembly:AssemblyProductAttribute(L"UIwrapper")];
+[assembly:AssemblyCopyrightAttribute(L"Copyright (c)  2015")];
+[assembly:AssemblyTrademarkAttribute(L"")];
+[assembly:AssemblyCultureAttribute(L"")];
+
+//
+// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
+//
+//      Hauptversion
+//      Nebenversion
+//      Buildnummer
+//      Revision
+//
+// Sie können alle Werte angeben oder für die Revisions- und Buildnummer den Standard
+// übernehmen, indem Sie "*" eingeben:
+
+[assembly:AssemblyVersionAttribute("1.0.*")];
+
+[assembly:ComVisible(false)];
+
+[assembly:CLSCompliantAttribute(true)];
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/ReadMe.txt	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,30 @@
+========================================================================
+    DYNAMIC LINK LIBRARY: UIwrapper-Projektübersicht
+========================================================================
+
+Diese UIwrapper-DLL wurde vom Anwendungs-Assistenten für Sie erstellt.  
+
+Diese Datei bietet eine Übersicht über den Inhalt der einzelnen Dateien, aus
+denen Ihre UIwrapper-Anwendung besteht.
+
+UIwrapper.vcxproj
+    Dies ist die Hauptprojektdatei für VC++-Projekte, die mit dem Anwendungs-Assistenten generiert werden. Sie enthält Informationen über die Version von Visual C++, mit der die Datei generiert wurde, sowie über die Plattformen, Konfigurationen und Projektfunktionen, die im Anwendungs-Assistenten ausgewählt wurden.
+
+UIwrapper.vcxproj.filters
+    Dies ist die Filterdatei für VC++-Projekte, die mithilfe eines Anwendungs-Assistenten erstellt werden. Sie enthält Informationen über die Zuordnung zwischen den Dateien im Projekt und den Filtern. Diese Zuordnung wird in der IDE zur Darstellung der Gruppierung von Dateien mit ähnlichen Erweiterungen unter einem bestimmten Knoten verwendet (z. B. sind CPP-Dateien dem Filter "Quelldateien" zugeordnet).
+
+UIwrapper.cpp
+    Dies ist die Hauptquelldatei der DLL.
+
+UIwrapper.h
+    Diese Datei enthält eine Klassendeklaration.
+
+AssemblyInfo.cpp
+	Enthält benutzerdefinierte Attribute zum Ändern von Assemblymetadaten.
+
+/////////////////////////////////////////////////////////////////////////////
+Weitere Hinweise:
+
+Der Anwendungs-Assistent weist Sie mit "TODO:" auf Teile des Quellcodes hin, die Sie ergänzen oder anpassen sollten.
+
+/////////////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/Stdafx.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,5 @@
+// stdafx.cpp : Quelldatei, die nur die Standard-Includes einbindet.
+// UIwrapper.pch ist der vorkompilierte Header.
+// stdafx.obj enthält die vorkompilierten Typinformationen.
+
+#include "stdafx.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/Stdafx.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,7 @@
+// stdafx.h : Includedatei für Standardsystem-Includedateien
+// oder häufig verwendete projektspezifische Includedateien,
+// die nur in unregelmäßigen Abständen geändert werden.
+
+#pragma once
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{367C474F-D7EA-44E3-9CB7-A4A35DCE9CC5}</ProjectGuid>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <Keyword>ManagedCProj</Keyword>
+    <RootNamespace>UIwrapper</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CLRSupport>true</CLRSupport>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CLRSupport>true</CLRSupport>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CLRSupport>true</CLRSupport>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CLRSupport>true</CLRSupport>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)..\..\..\build\UIwrapper\</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)..\..\..\build\UIwrapper\</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)..\..\..\build\UIwrapper\</OutDir>
+    <IntDir>$(SolutionDir)..\..\..\build\UIwrapper\$(Configuration)\</IntDir>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <AdditionalUsingDirectories>$(SolutionDir)..\..\..\build\UIcore\;%(AdditionalUsingDirectories)</AdditionalUsingDirectories>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies />
+      <AdditionalLibraryDirectories>C:\Users\Olaf\Projekte\toolkit\build\UIcore\</AdditionalLibraryDirectories>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <AdditionalUsingDirectories>$(SolutionDir)..\..\..\build\UIcore\;%(AdditionalUsingDirectories)</AdditionalUsingDirectories>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>
+      </AdditionalDependencies>
+      <AdditionalLibraryDirectories>
+      </AdditionalLibraryDirectories>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies />
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <AdditionalUsingDirectories>$(SolutionDir)..\..\..\build\UIcore\;%(AdditionalUsingDirectories)</AdditionalUsingDirectories>
+      <DebugInformationFormat>None</DebugInformationFormat>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>
+      </AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Windows" />
+    <Reference Include="System.Windows.Controls.Ribbon" />
+    <Reference Include="System.Xaml" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="controls.h" />
+    <ClInclude Include="container.h" />
+    <ClInclude Include="graphics.h" />
+    <ClInclude Include="menu.h" />
+    <ClInclude Include="resource.h" />
+    <ClInclude Include="Stdafx.h" />
+    <ClInclude Include="toolbar.h" />
+    <ClInclude Include="toolkit.h" />
+    <ClInclude Include="window.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="AssemblyInfo.cpp" />
+    <ClCompile Include="controls.cpp" />
+    <ClCompile Include="graphics.cpp" />
+    <ClCompile Include="menu.cpp" />
+    <ClCompile Include="container.cpp" />
+    <ClCompile Include="toolbar.cpp" />
+    <ClCompile Include="window.cpp" />
+    <ClCompile Include="Stdafx.cpp">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="toolkit.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="ReadMe.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="app.rc" />
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="app.ico" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj.filters	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Quelldateien">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Headerdateien">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Ressourcendateien">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="Stdafx.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="toolkit.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="window.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="menu.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="controls.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="container.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="toolbar.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+    <ClInclude Include="graphics.h">
+      <Filter>Headerdateien</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="AssemblyInfo.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="Stdafx.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="window.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="toolkit.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="menu.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="controls.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="container.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="toolbar.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+    <ClCompile Include="graphics.cpp">
+      <Filter>Quelldateien</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <Text Include="ReadMe.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="app.rc">
+      <Filter>Ressourcendateien</Filter>
+    </ResourceCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="app.ico">
+      <Filter>Ressourcendateien</Filter>
+    </Image>
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/UIwrapper.vcxproj.user	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+</Project>
\ No newline at end of file
Binary file ui/wpf/UIwrapper/UIwrapper/app.ico has changed
Binary file ui/wpf/UIwrapper/UIwrapper/app.rc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/container.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,78 @@
+
+
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "container.h"
+
+#using "UIcore.dll"
+
+UI_EXPORT void* __stdcall UIvbox(gcroot<UI::Container^> *parent, int margin, int spacing) {
+	UI::BoxContainer ^vbox = gcnew UI::BoxContainer(*parent, UI::BoxOrientation::VERTICAL, margin, spacing);
+	gcroot<UI::BoxContainer^> *container = new gcroot<UI::BoxContainer^>();
+	*container = vbox;
+	return container;
+}
+
+UI_EXPORT void* __stdcall UIhbox(gcroot<UI::Container^> *parent, int margin, int spacing) {
+	UI::BoxContainer ^hbox = gcnew UI::BoxContainer(*parent, UI::BoxOrientation::HORIZONTAL, margin, spacing);
+	gcroot<UI::BoxContainer^> *container = new gcroot<UI::BoxContainer^>();
+	*container = hbox;
+	return container;
+}
+
+UI_EXPORT void* __stdcall UIgrid(gcroot<UI::Container^> *parent, int margin, int columnspacing, int rowspacing) {
+	UI::GridContainer ^grid = gcnew UI::GridContainer(*parent, margin, columnspacing, rowspacing);
+	gcroot<UI::GridContainer^> *container = new gcroot<UI::GridContainer^>();
+	*container = grid;
+	return container;
+}
+
+UI_EXPORT void* __stdcall UIscrolledwindow(gcroot<UI::Container^> *parent) {
+	UI::ScrollViewerContainer ^scrollviewer = gcnew UI::ScrollViewerContainer(*parent);
+	gcroot<UI::ScrollViewerContainer^> *container = new gcroot<UI::ScrollViewerContainer^>();
+	*container = scrollviewer;
+	return container;
+}
+
+UI_EXPORT void* __stdcall UItabview(gcroot<UI::Container^> *parent) {
+	UI::TabViewContainer ^tabview = gcnew UI::TabViewContainer(*parent);
+	gcroot<UI::TabViewContainer^> *container = new gcroot<UI::TabViewContainer^>();
+	*container = tabview;
+	return container;
+}
+
+UI_EXPORT void __stdcall UItab(gcroot<UI::Container^> *container, char *label) {
+	UI::Container ^ct = *container;
+	ct->Layout->Label = gcnew String(label);
+}
+
+
+
+/* ------------------- layout functions ------------------- */
+
+UI_EXPORT void __stdcall UIlayout_fill(gcroot<UI::Container^> *container, int fill) {
+	UI::Container ^ct = *container;
+	ct->Layout->Fill = fill != 0;
+}
+
+UI_EXPORT void __stdcall UIlayout_hexpand(gcroot<UI::Container^> *container, int expand) {
+	UI::Container ^ct = *container;
+	ct->Layout->Hexpand = expand != 0;
+}
+
+UI_EXPORT void __stdcall UIlayout_vexpand(gcroot<UI::Container^> *container, int expand) {
+	UI::Container ^ct = *container;
+	ct->Layout->Vexpand = expand != 0;
+}
+
+UI_EXPORT void __stdcall UIlayout_gridwidth(gcroot<UI::Container^> *container, int width) {
+	UI::Container ^ct = *container;
+	ct->Layout->GridWidth = width;
+}
+
+UI_EXPORT void __stdcall UIlayout_newline(gcroot<UI::Container^> *container) {
+	UI::Container ^ct = *container;
+	ct->Layout->NewLine = true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,5 @@
+
+
+#pragma once
+
+#include "toolkit.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/controls.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,110 @@
+
+
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "controls.h"
+
+#using "UIcore.dll"
+
+/* ------------------------------ Buttons ------------------------------ */
+
+UI_EXPORT void* __stdcall UIbutton(gcroot<UI::Container^> *container, char *label, UIcallback f, void *eventdata) {
+	gcroot<Button^> *button = new gcroot<Button^>();
+
+	EventWrapper ^evt = gcnew EventWrapper(f, eventdata);
+	RoutedEventHandler ^handler = gcnew RoutedEventHandler(evt, &EventWrapper::Callback);
+
+	*button = UI::Controls::Button(*container, gcnew String(label), handler);
+	return button;
+}
+
+
+/* ------------------------------ Labels ------------------------------ */
+
+UI_EXPORT void* __stdcall UIlabel(gcroot<UI::Container^> *container, char *label, int alignment) {
+	gcroot<Label^> *control = new gcroot<Label^>();
+	*control = UI::Controls::Label(*container, gcnew String(label), alignment);
+	return control;
+}
+
+UI_EXPORT void* __stdcall UIspace(gcroot<UI::Container^> *container) {
+	gcroot<Label^> *control = new gcroot<Label^>();
+	*control = UI::Controls::Space(*container);
+	return control;
+}
+
+UI_EXPORT void* __stdcall UIseparator(gcroot<UI::Container^> *container) {
+	gcroot<Separator^> *control = new gcroot<Separator^>();
+	*control = UI::Controls::Separator(*container);
+	return control;
+}
+
+
+
+/* ------------------------------ Textarea ------------------------------ */
+
+UI_EXPORT void* __stdcall UItextarea(gcroot<UI::Container^> *container, char *text) {
+	String ^str = nullptr;
+	if (text) {
+		str = gcnew String(text);
+	}
+	
+	gcroot<UI::TextArea^> *textarea = new gcroot<UI::TextArea^>();
+	*textarea = gcnew UI::TextArea(*container, str, true);
+
+	return textarea;
+}
+
+UI_EXPORT void  __stdcall UItextarea_set(gcroot<UI::TextArea^> *textarea, char *str) {
+	(*textarea)->SetText(gcnew String(str));
+}
+
+UI_EXPORT char* __stdcall UItextarea_get(gcroot<UI::TextArea^> *textarea) {
+	String ^str = (*textarea)->GetText();
+	return (char*)(void*)Marshal::StringToHGlobalAnsi(str);
+}
+
+UI_EXPORT char* __stdcall UItextarea_getsubstr(gcroot<UI::TextArea^> *textarea, int begin, int end) {
+	String ^str = (*textarea)->GetSubString(begin, end);
+	return (char*)(void*)Marshal::StringToHGlobalAnsi(str);
+}
+
+UI_EXPORT void __stdcall UItextarea_insert(gcroot<UI::TextArea^> *textarea, int position, char *str) {
+	// TODO
+}
+
+UI_EXPORT int __stdcall UItextarea_position(gcroot<UI::TextArea^> *textarea) {
+	return (*textarea)->Position();
+}
+
+UI_EXPORT void __stdcall UItextarea_selection(gcroot<UI::TextArea^> *textarea, int *begin, int *end) {
+	// TODO
+}
+
+UI_EXPORT int __stdcall UItextarea_length(gcroot<UI::TextArea^> *textarea) {
+	return (*textarea)->Length();
+}
+
+UI_EXPORT void __stdcall UItextarea_remove(gcroot<UI::TextArea^> *textarea, int begin, int end) {
+	// TODO
+}
+
+UI_EXPORT void __stdcall UIfreestr(char *str) {
+	Marshal::FreeHGlobal((IntPtr)(void*)str);
+}
+
+
+/* ------------------------------ Textfield ------------------------------ */
+
+UI_EXPORT void* __stdcall UItextfield(gcroot<UI::Container^> *container, char *text) {
+	String ^str = nullptr;
+	if (text) {
+		str = gcnew String(text);
+	}
+
+	gcroot<UI::TextArea^> *textfield = new gcroot<UI::TextArea^>();
+	*textfield = gcnew UI::TextArea(*container, str, false);
+
+	return textfield;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/controls.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,6 @@
+
+
+#pragma once
+
+#include "toolkit.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/graphics.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,63 @@
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "graphics.h"
+
+#using "UIcore.dll"
+
+
+DrawEventWrapper::DrawEventWrapper(void *gc, UIdrawfunc callback, void *eventdata) {
+	this->callback = callback;
+	this->eventdata = eventdata;
+	this->gc = gc;
+	action = gcnew Action<int,int>(this, &DrawEventWrapper::Callback);
+}
+
+
+void DrawEventWrapper::Callback(int width, int height)
+{
+	if (callback)
+	{
+		UI::DrawingArea ^d = (UI::DrawingArea^)PtrToObject(gc);
+		callback(gc, eventdata, width, height);
+	}
+}
+
+
+UI_EXPORT void* __stdcall UIdrawingarea(gcroot<UI::Container^> *container, UIdrawfunc f, void *data)
+{
+	gcroot<UI::DrawingArea^> *canvas = new gcroot<UI::DrawingArea^>();
+	*canvas = gcnew UI::DrawingArea(*container);
+
+	DrawEventWrapper ^ev = gcnew DrawEventWrapper(ObjectToPtr(*canvas), f, data);
+	(*canvas)->resizeCallback = ev->action;
+
+	return canvas;
+}
+
+
+UI_EXPORT void __stdcall UIdrawingarea_redraw(gcroot<UI::DrawingArea^> *drawingarea)
+{
+	(*drawingarea)->Redraw();
+}
+
+
+/* ------------------------- drawing functions ------------------------- */
+
+UI_EXPORT void __stdcall UIgraphics_color(void *g, int red, int green, int blue)
+{
+	UI::DrawingArea ^d = (UI::DrawingArea^)PtrToObject(g);
+	d->SetColor(red, green, blue);
+}
+
+UI_EXPORT void __stdcall UIdraw_line(void *g, int x1, int y1, int x2, int y2)
+{
+	UI::DrawingArea ^d = (UI::DrawingArea^)PtrToObject(g);
+	d->DrawLine(x1, y1, x2, y2);
+}
+
+UI_EXPORT void __stdcall UIdraw_rect(void *g, int x, int y, int w, int h, int fill)
+{
+	UI::DrawingArea ^d = (UI::DrawingArea^)PtrToObject(g);
+	d->DrawRect(x, y, w, h, fill ? true : false);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "toolkit.h"
+
+typedef void(*UIdrawfunc)(void *gc, void *event, int width, int height);
+
+public ref class DrawEventWrapper {
+public:
+	UIdrawfunc callback = NULL;
+	void *eventdata = NULL;
+	void *gc;
+	Action<int,int> ^action;
+
+	DrawEventWrapper(void *gc, UIdrawfunc callback, void *eventdata);
+
+	void Callback(int width, int height);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/menu.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,26 @@
+
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "menu.h"
+
+#using "UIcore.dll"
+
+UI_EXPORT void __stdcall UImenu(char *label) {
+	UI::Application::GetInstance()->Menu->AddMenu(gcnew String(label));
+}
+
+UI_EXPORT void __stdcall UIsubmenu(char *label) {
+	UI::Application::GetInstance()->Menu->AddSubMenu(gcnew String(label));
+}
+
+UI_EXPORT void __stdcall UIsubmenu_end() {
+	UI::Application::GetInstance()->Menu->EndSubMenu();
+}
+
+
+UI_EXPORT void __stdcall UImenuitem(char *label, UIcallback f, void *eventdata) {
+	ObjEventWrapper ^e = gcnew ObjEventWrapper(f, eventdata);
+	UI::Application::GetInstance()->Menu->AddMenuItem(gcnew String(label), e->GetAction());
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,4 @@
+
+#pragma once
+
+#include "toolkit.h"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/resource.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,3 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by app.rc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/toolbar.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,49 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+*
+* Copyright 2015 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 "stdafx.h"
+#include <stdio.h>
+
+#include "toolbar.h"
+
+#using "UIcore.dll"
+
+UI_EXPORT void __stdcall UItoolitem(char *name, char *label, UIcallback f, void *eventdata) {
+	ObjEventWrapper ^e = gcnew ObjEventWrapper(f, eventdata);
+	UI::Application::GetInstance()->ToolBar->AddToolItem(gcnew String(name), gcnew String(label), e->GetAction());
+}
+
+
+
+
+
+UI_EXPORT void __stdcall UItoolbar_add_default(char *name) {
+	UI::Application::GetInstance()->ToolBar->AddDefault(gcnew String(name));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,32 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+*
+* Copyright 2015 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.
+*/
+
+
+#pragma once
+
+#include "toolkit.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/toolkit.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,127 @@
+// Dies ist die Haupt-DLL.
+
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "toolkit.h"
+
+#using "UIcore.dll"
+
+static UIcallback startup_func;
+void              *startup_data;
+static UIcallback open_func;
+void              *open_data;
+static UIcallback exit_func;
+void              *exit_data;
+
+public ref class AppCallbacks : public UI::IApplicationCallbacks {
+public:
+	UIcallback startupFunc = NULL;
+	void       *startupData = NULL;
+	UIcallback openFunc = NULL;
+	void       *openData = NULL;
+	UIcallback exitFunc = NULL;
+	void       *exitData = NULL;
+
+	virtual void __clrcall OnStartup() {
+		if (startupFunc) {
+			startupFunc(NULL, startupData);
+		}
+	}
+	virtual void __clrcall OnOpen() {
+		if (openFunc) {
+			openFunc(NULL, openData);
+		}
+	}
+	virtual void __clrcall OnExit() {
+		if (exitFunc) {
+			exitFunc(NULL, exitData);
+		}
+	}
+};
+
+
+void* ObjectToPtr(Object ^obj) {
+	GCHandle handle = GCHandle::Alloc(obj);
+	IntPtr pointer = GCHandle::ToIntPtr(handle);
+	return pointer.ToPointer();
+}
+
+Object^ PtrToObject(void *ptr) {
+	GCHandle h = GCHandle::FromIntPtr(IntPtr(ptr));
+	Object^ object = h.Target;
+	//h.Free();
+	return object;
+}
+
+// EventWrapper
+
+ObjEventWrapper::ObjEventWrapper(UIcallback callback, void *eventdata) {
+	this->callback = callback;
+	this->eventdata = eventdata;
+	action = gcnew Action<IntPtr>(this, &ObjEventWrapper::Callback);
+}
+
+Action<IntPtr>^ ObjEventWrapper::GetAction() {
+	return action;
+}
+
+void ObjEventWrapper::Callback(IntPtr uiobj) {
+	if (callback) {
+		callback(uiobj.ToPointer(), eventdata);
+	}
+}
+
+
+EventWrapper::EventWrapper(UIcallback callback, void *eventdata) {
+	this->callback = callback;
+	this->eventdata = eventdata;
+}
+
+void EventWrapper::Callback(Object ^sender, RoutedEventArgs ^e) {
+	if (callback) {
+		callback(NULL, eventdata);
+	}
+}
+
+
+
+UI_EXPORT void __stdcall UIinit(char *appname) {
+	UI::Application ^app = UI::Application::GetInstance();
+	app->Name = gcnew String(appname);
+}
+
+UI_EXPORT void __stdcall UIonstartup(UIcallback f, void *userdata) {
+	startup_func = f;
+	startup_data = userdata;
+}
+
+UI_EXPORT void __stdcall UIonopen(UIcallback f, void *userdata) {
+	open_func = f;
+	open_data = userdata;
+}
+
+UI_EXPORT void __stdcall UIonexit(UIcallback f, void *userdata) {
+	exit_func = f;
+	exit_data = userdata;
+}
+
+UI_EXPORT void __stdcall UImain() {
+	AppCallbacks ^ac = gcnew AppCallbacks();
+	ac->startupFunc = startup_func;
+	ac->startupData = startup_data;
+	ac->openFunc = open_func;
+	ac->openData = open_data;
+	ac->exitFunc = exit_func;
+	ac->exitData = exit_data;
+	
+	UI::Application ^app = UI::Application::GetInstance();
+	app->callbacks = ac;
+
+	Thread ^thread = app->Start();
+	thread->Join();
+}
+
+UI_EXPORT void __stdcall UIshow(gcroot<UI::MainWindow^> *window) {
+	(*window)->ShowWindow();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,42 @@
+// UIwrapper.h
+
+#pragma once
+
+#include <vcclr.h>
+
+using namespace System;
+using namespace System::Runtime::InteropServices;
+using namespace System::Threading;
+using namespace System::Windows;
+using namespace System::Windows::Controls;
+
+#define UI_EXPORT extern "C" __declspec(dllexport)
+
+extern "C" typedef void(*UIcallback)(void*, void*);
+
+void* ObjectToPtr(Object ^obj);
+Object^ PtrToObject(void *ptr);
+
+public ref class ObjEventWrapper {
+	UIcallback callback = NULL;
+	void *eventdata = NULL;
+	Action<IntPtr> ^action;
+
+public:
+	ObjEventWrapper(UIcallback callback, void *eventdata);
+
+	Action<IntPtr>^ GetAction();
+
+	void Callback(IntPtr uiobj);
+};
+
+public ref class EventWrapper {
+	UIcallback callback = NULL;
+	void *eventdata = NULL;
+	
+
+public:
+	EventWrapper(UIcallback callback, void *eventdata);
+	void Callback(Object ^sender, RoutedEventArgs ^e);
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/window.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,17 @@
+
+
+#include "stdafx.h"
+#include <stdio.h>
+
+#include "window.h"
+
+#using "UIcore.dll"
+
+UI_EXPORT void* __stdcall UIwindow(char *title, void *uiobj) {
+	UI::MainWindow ^window = gcnew UI::MainWindow(gcnew String(title), IntPtr(uiobj));
+	gcroot<UI::MainWindow^> *ptr = new gcroot<UI::MainWindow^>();
+	*ptr = window;
+	return ptr;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/UIwrapper/UIwrapper/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,6 @@
+
+
+#pragma once
+
+#include "toolkit.h"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/button.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,58 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "button.h"
+#include "../common/object.h"
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+    UiEventData *event = NULL;
+    ui_callback callback = NULL;
+    if(f) {
+        event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->callback = f;
+        event->user_data = data;
+        event->value = 0;
+        callback = (ui_callback)ui_button_callback;
+    }
+    
+    UiContainer *container = uic_get_current_container(obj);
+    return UIbutton(container, label, callback, event);
+}
+
+void ui_button_callback(UiObject *obj, UiEventData *e) {
+    UiEvent event;
+    event.obj = e->obj;
+    event.document = event.obj->ctx->document;
+    event.window = event.obj->window;
+    event.intval = 0;
+    e->callback(&event, e->user_data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/button.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 BUTTON_H
+#define	BUTTON_H
+
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT UIWIDGET __stdcall UIbutton(void *container, char *label, ui_callback f, void *event);
+
+void ui_button_callback(UiObject *obj, UiEventData *e);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* BUTTON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/container.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,147 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "container.h"
+#include "../common/object.h"
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_vbox_sp(obj, 0, 0);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_hbox_sp(obj, 0, 0);
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    UIWIDGET vbox = UIvbox(ct, margin, spacing);
+    
+    UiObject *newobj = uic_object_new(obj, vbox);
+    newobj->container = (UiContainer*)vbox;
+    uic_obj_add(obj, newobj);
+    
+    return vbox;
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    UIWIDGET hbox = UIhbox(ct, margin, spacing);
+    
+    UiObject *newobj = uic_object_new(obj, hbox);
+    newobj->container = (UiContainer*)hbox;
+    uic_obj_add(obj, newobj);
+    
+    return hbox;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    UIWIDGET grid = UIgrid(ct, margin, columnspacing, rowspacing);
+    
+    UiObject *newobj = uic_object_new(obj, grid);
+    newobj->container = (UiContainer*)grid;
+    uic_obj_add(obj, newobj);
+    
+    return grid;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    UIWIDGET scrolledwindow = UIscrolledwindow(ct);
+    
+    UiObject *newobj = uic_object_new(obj, scrolledwindow);
+    newobj->container = (UiContainer*)scrolledwindow;
+    uic_obj_add(obj, newobj);
+    
+    return scrolledwindow;
+}
+
+/*
+ * TODO: sidebar
+ */
+
+UIWIDGET ui_tabview(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    UIWIDGET tabview = UItabview(ct);
+    
+    UiObject *newobj = uic_object_new(obj, tabview);
+    newobj->container = (UiContainer*)tabview;
+    uic_obj_add(obj, newobj);
+    
+    return tabview;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UItab(ct, title);
+}
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UIlayout_fill(ct, fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UIlayout_hexpand(ct, expand);
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UIlayout_vexpand(ct, expand);
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UIlayout_gridwidth(ct, width);
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    UIlayout_newline(ct);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/container.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,59 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 CONTAINER_H
+#define	CONTAINER_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT void* __stdcall UIvbox(UiContainer *parent, int margin, int spacing);
+UI_IMPORT void* __stdcall UIhbox(UiContainer *parent, int margin, int spacing);
+UI_IMPORT void* __stdcall UIgrid(UiContainer *parent, int margin, int columnspacing, int rowspacing);
+
+UI_IMPORT void* __stdcall UIscrolledwindow(UiContainer *parent);
+
+UI_IMPORT void* __stdcall UItabview(UiContainer *parent);
+UI_IMPORT void  __stdcall UItab(UiContainer *container, char *label);
+
+UI_IMPORT void __stdcall UIlayout_fill(UiContainer *container, int fill);
+UI_IMPORT void __stdcall UIlayout_hexpand(UiContainer *container, int expand);
+UI_IMPORT void __stdcall UIlayout_vexpand(UiContainer *container, int expand);
+UI_IMPORT void __stdcall UIlayout_gridwidth(UiContainer *container, int width);
+
+UI_IMPORT void __stdcall UIlayout_newline(UiContainer *container);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* CONTAINER_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/graphics.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "graphics.h"
+#include "container.h"
+#include "../../ucx/mempool.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+    UiDrawEvent *eventdata = NULL;
+    ui_draw_callback cb = NULL;
+    if(f) {
+        eventdata = malloc(sizeof(UiDrawEvent));
+        eventdata->obj = obj;
+        eventdata->draw = f;
+        eventdata->userdata = userdata;
+        cb = ui_draw_event;
+    }
+    
+    UiContainer *container = uic_get_current_container(obj);
+    return UIdrawingarea(container, cb, eventdata);
+}
+
+void ui_draw_event(void *gc, UiDrawEvent *event, int width, int height) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = e.obj->window;
+    e.document = e.obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = 0;
+    
+    UiWPFGraphics g;
+    g.g.width = width;
+    g.g.height = height;
+    g.gc = gc;
+    
+    event->draw(&e, &g.g, event->userdata);
+}
+
+
+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) {
+    UIdrawingarea_redraw(drawingarea);
+}
+
+
+/* ------------------------- drawing functions ------------------------- */
+
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+    UiWPFGraphics *wg = (UiWPFGraphics*)g;
+    UIgraphics_color(wg->gc, red, green, blue);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+    UiWPFGraphics *wg = (UiWPFGraphics*)g;
+    UIdraw_line(wg->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    UiWPFGraphics *wg = (UiWPFGraphics*)g;
+    UIdraw_rect(wg->gc, x, y, w, h, fill);
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/graphics.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,56 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/* 
+ * File:   graphics.h
+ * Author: Olaf
+ *
+ * Created on 22. Januar 2017, 18:34
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include "toolkit.h"
+#include "../ui/graphics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+typedef struct UiDrawEvent {
+    UiObject    *obj;
+    ui_drawfunc draw;
+    void        *userdata;
+} UiDrawEvent;
+
+typedef struct UiWPFGraphics {
+    UiGraphics g;
+    void       *gc;
+} UiWPFGraphics;
+
+typedef void(*ui_draw_callback)(void *gc, UiDrawEvent *event, int width, int height);
+    
+UI_IMPORT UIWIDGET __stdcall UIdrawingarea(void *container, ui_draw_callback f, void *userdata);
+
+UI_IMPORT void __stdcall UIdrawingarea_redraw(UIWIDGET drawingarea);
+
+void ui_draw_event(void *gc, UiDrawEvent *event, int width, int height);
+
+// drawing functions
+
+UI_IMPORT void __stdcall UIgraphics_color(UiGraphics *g, int red, int green, int blue);
+UI_IMPORT void __stdcall UIdraw_line(UiGraphics *g, int x1, int y1, int x2, int y2);
+UI_IMPORT void __stdcall UIdraw_rect(UiGraphics *g, int x, int y, int w, int h, int fill);
+//void UIdraw_text(UiGraphics *g, int x, int y, UiTextLayout *text);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRAPHICS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/label.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "label.h"
+#include "container.h"
+#include "../../ucx/mempool.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_label(UiObject *obj, char *label) {
+    return UIlabel(uic_get_current_container(obj), label, 2);
+}
+
+UIWIDGET ui_llabel(UiObject *obj, char *label) {
+    return UIlabel(uic_get_current_container(obj), label, 0);
+}
+
+UIWIDGET ui_rlabel(UiObject *obj, char *label) {
+    return UIlabel(uic_get_current_container(obj), label, 1);
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+    return UIspace(uic_get_current_container(obj));
+}
+
+UIWIDGET ui_separator(UiObject *obj) {
+    return UIseparator(uic_get_current_container(obj));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/label.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,28 @@
+/* 
+ * File:   label.h
+ * Author: Olaf
+ *
+ * Created on 19. Januar 2016, 18:12
+ */
+
+#ifndef LABEL_H
+#define	LABEL_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT UIWIDGET __stdcall UIlabel(void *container, char *label, int alignment);
+
+UI_IMPORT UIWIDGET __stdcall UIspace(void *container);
+
+UI_IMPORT UIWIDGET __stdcall UIseparator(void *container);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* LABEL_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/menu.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,72 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <unistd.h>
+
+#include "menu.h"
+
+void ui_menu(char *label) {
+    UImenu(label);
+}
+
+void ui_submenu(char *label) {
+    UIsubmenu(label);
+}
+
+void ui_submenu_end() {
+    UIsubmenu_end();
+}
+
+void ui_menuitem(char *label, ui_callback f, void *userdata) {
+    UIcallback cb = NULL;
+    void *e = NULL;
+    if (f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = NULL;
+        event->user_data = userdata;
+        event->callback = f;
+        event->value = 0;
+        cb = (UIcallback)ui_obj_callback;
+        e = event;
+    }
+    
+    UImenuitem(label, cb, e);
+}
+
+
+void ui_obj_callback(UiObject *obj, UiEventData *e) {
+    UiEvent event;
+    event.obj = obj;
+    event.window = obj->window;
+    event.intval = 0;
+    event.eventdata = NULL;
+    event.document = obj->ctx->document;
+    e->callback(&event, e->user_data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/menu.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,32 @@
+/* 
+ * File:   menu.h
+ * Author: Olaf
+ *
+ * Created on 25. Januar 2015, 13:37
+ */
+
+#ifndef MENU_H
+#define	MENU_H
+
+#include "../ui/menu.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT void __stdcall UImenu(char *label);
+UI_IMPORT void __stdcall UIsubmenu(char *label);
+UI_IMPORT void __stdcall UIsubmenu_end();
+UI_IMPORT void __stdcall UImenuitem(char *label, UIcallback f, void *udata);
+
+
+
+void ui_obj_callback(UiObject *obj, UiEventData *e);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* MENU_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/objs.mk	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+WPF_SRC_DIR = ui/wpf/
+WPF_OBJPRE = $(OBJ_DIR)$(WPF_SRC_DIR)
+
+WPFOBJ = toolkit.o
+WPFOBJ += window.o
+WPFOBJ += container.o
+WPFOBJ += menu.o
+WPFOBJ += toolbar.o
+WPFOBJ += button.o
+WPFOBJ += label.o
+WPFOBJ += text.o
+WPFOBJ += graphics.o
+
+TOOLKITOBJS += $(WPFOBJ:%=$(WPF_OBJPRE)%)
+TOOLKITSOURCE += $(WPFOBJ:%.o=wpf/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/text.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,140 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "text.h"
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiContainer *container = uic_get_current_container(obj); 
+    UIWIDGET textarea = UItextarea(container, value ? value->value : NULL);
+    
+    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->remove = ui_textarea_remove;
+        value->value = NULL;
+        value->obj = textarea;
+        if(!value->undomgr) {
+            //value->undomgr = ;
+        }
+    }
+    
+    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 = var->value;
+        return ui_textarea(obj, value);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value) {
+        UIfreestr(text->value);
+    }
+    text->value = UItextarea_get(text->obj);
+    return text->value;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    if(text->value) {
+        UIfreestr(text->value);
+        text->value = NULL;
+    }
+    UItextarea_set(text->obj, str);
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value) {
+        UIfreestr(text->value);
+    }
+    text->value = UItextarea_getsubstr(text->obj, begin, end);
+    return text->value;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    if(text->value) {
+        UIfreestr(text->value);
+        text->value = NULL;
+    }
+    UItextarea_insert(text->obj, pos, str);
+}
+
+int ui_textarea_position(UiText *text) {
+    return UItextarea_position(text->obj);
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    UItextarea_selection(text->obj, begin, end);
+}
+
+int ui_textarea_length(UiText *text) {
+    return UItextarea_length(text->obj);
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+    if(text->value) {
+        UIfreestr(text->value);
+        text->value = NULL;
+    }
+    UItextarea_remove(text->obj, begin, end);
+}
+
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    UiContainer *container = uic_get_current_container(obj); 
+    UIWIDGET textfield = UItextfield(container, value ? value->value : NULL);
+    
+    if(value) {
+        // TODO
+    }
+    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 = var->value;
+        return ui_textfield(obj, value);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/text.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,69 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 TEXT_H
+#define	TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT UIWIDGET __stdcall UItextarea(void *container, char *text);
+
+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);
+void ui_textarea_remove(UiText *text, int begin, int end);
+
+UI_IMPORT void __stdcall UItextarea_set(UIWIDGET textarea, char *str);
+UI_IMPORT char* __stdcall UItextarea_get(UIWIDGET textarea);
+UI_IMPORT char* __stdcall UItextarea_getsubstr(UIWIDGET textarea, int begin, int end);
+UI_IMPORT void __stdcall UItextarea_insert(UIWIDGET textarea, int pos, char *str);
+UI_IMPORT int __stdcall UItextarea_position(UIWIDGET textarea);
+UI_IMPORT void __stdcall UItextarea_selection(UIWIDGET textarea, int *begin, int *end);
+UI_IMPORT int __stdcall UItextarea_length(UIWIDGET textarea);
+UI_IMPORT void __stdcall UItextarea_remove(UIWIDGET textarea, int begin, int end);
+
+UI_IMPORT void __stdcall UIfreestr(char *str);
+
+
+UI_IMPORT UIWIDGET __stdcall UItextfield(void *container, char *text);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TEXT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/toolbar.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,56 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 "toolbar.h"
+#include "menu.h"
+#include "../common/context.h"
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
+    UIcallback cb = NULL;
+    void *e = NULL;
+    if (f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = NULL;
+        event->user_data = udata;
+        event->callback = f;
+        event->value = 0;
+        cb = (UIcallback)ui_obj_callback;
+        e = event;
+    }
+    
+    UItoolitem(name, label, cb, e);
+}
+
+void ui_toolbar_add_default(char *name) {
+    UItoolbar_add_default(name);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/toolbar.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 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 TOOLBAR_H
+#define	TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT void __stdcall UItoolitem(char *name, char *label, UIcallback callback, void *eventdata);
+
+
+UI_IMPORT void __stdcall UItoolbar_add_default(char *name);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLBAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/toolkit.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,66 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 <unistd.h>
+
+#include "toolkit.h"
+
+
+void ui_init(char *appname, int argc, char **argv) { 
+    UIinit(appname);
+}
+
+void ui_onstartup(ui_callback f, void *userdata) {
+    UIonstartup(f, userdata);
+}
+
+void ui_onopen(ui_callback f, void *userdata) {
+    UIonopen(f, userdata);
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+    UIonexit(f, userdata);
+}
+
+void ui_main() {
+    UImain();
+}
+
+void ui_show(UiObject *obj) {
+    UIshow(obj->widget);
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/toolkit.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,66 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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
+
+#define UI_IMPORT __declspec(dllimport)
+__declspec(dllimport) int  __stdcall myfunc(char *str);
+
+typedef struct UiEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *user_data;
+    int         value;
+} UiEventData;
+
+typedef void(*UIcallback)(void*,void*);
+
+UI_IMPORT void __stdcall UIinit(char *appname);
+
+UI_IMPORT void __stdcall UIonstartup(ui_callback f, void *userdata);
+UI_IMPORT void __stdcall UIonopen(ui_callback f, void *userdata);
+UI_IMPORT void __stdcall UIonexit(ui_callback f, void *userdata);
+UI_IMPORT void __stdcall UImain();
+UI_IMPORT void __stdcall UIshow(UIWIDGET widget);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* TOOLKIT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/window.c	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../ui/window.h"
+#include "../ui/properties.h"
+#include "../common/context.h"
+
+#include "window.h"
+
+UiObject* ui_window(char *title, void *window_data) {
+    UcxMempool *mp = ucx_mempool_new(256);
+    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));  
+    obj->widget = UIwindow(title, obj);
+    obj->ctx = uic_context(obj, mp);
+    obj->container = (UiContainer*)obj->widget;
+    //obj->window = window_data;
+    //obj->next = NULL;
+    
+    return obj;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/wpf/window.h	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,45 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WINDOW_H
+#define	WINDOW_H
+
+#include "toolkit.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+UI_IMPORT UIWIDGET __stdcall UIwindow(char *title, void *uiobj);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* WINDOW_H */
+

mercurial