]> uap-core.de Git - note.git/commitdiff
add ucx and toolkit code
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Thu, 5 Dec 2024 09:22:08 +0000 (10:22 +0100)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Thu, 5 Dec 2024 09:22:08 +0000 (10:22 +0100)
316 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
application/Makefile [new file with mode: 0644]
application/main.c [new file with mode: 0644]
configure [new file with mode: 0755]
libidav/Makefile [new file with mode: 0644]
libidav/config.c [new file with mode: 0644]
libidav/config.h [new file with mode: 0644]
libidav/crypto.c [new file with mode: 0644]
libidav/crypto.h [new file with mode: 0644]
libidav/davqlexec.c [new file with mode: 0644]
libidav/davqlexec.h [new file with mode: 0644]
libidav/davqlparser.c [new file with mode: 0644]
libidav/davqlparser.h [new file with mode: 0644]
libidav/methods.c [new file with mode: 0644]
libidav/methods.h [new file with mode: 0644]
libidav/pwdstore.c [new file with mode: 0644]
libidav/pwdstore.h [new file with mode: 0644]
libidav/resource.c [new file with mode: 0644]
libidav/resource.h [new file with mode: 0644]
libidav/session.c [new file with mode: 0644]
libidav/session.h [new file with mode: 0644]
libidav/utils.c [new file with mode: 0644]
libidav/utils.h [new file with mode: 0644]
libidav/versioning.c [new file with mode: 0644]
libidav/versioning.h [new file with mode: 0644]
libidav/webdav.c [new file with mode: 0644]
libidav/webdav.h [new file with mode: 0644]
libidav/xml.c [new file with mode: 0644]
libidav/xml.h [new file with mode: 0644]
make/Makefile.mk [new file with mode: 0644]
make/cc.mk [new file with mode: 0644]
make/clang.mk [new file with mode: 0644]
make/configure.vm [new file with mode: 0644]
make/gcc.mk [new file with mode: 0644]
make/mingw.mk [new file with mode: 0644]
make/osx.mk [new file with mode: 0644]
make/package_osx.sh [new file with mode: 0644]
make/package_unix.sh [new file with mode: 0644]
make/package_windows.sh [new file with mode: 0644]
make/project.xml [new file with mode: 0644]
make/suncc.mk [new file with mode: 0644]
make/toolchain.sh [new file with mode: 0644]
make/uwproj.xsd [new file with mode: 0644]
make/vs/idav.sln [new file with mode: 0644]
make/vs/idav/app.manifest [new file with mode: 0644]
make/vs/idav/idav.vcxproj [new file with mode: 0644]
make/vs/idav/idav.vcxproj.filters [new file with mode: 0644]
make/vs/idav/packages.config [new file with mode: 0644]
make/vs/libidav/libidav.vcxproj [new file with mode: 0644]
make/vs/libidav/libidav.vcxproj.filters [new file with mode: 0644]
make/vs/testapp/app.manifest [new file with mode: 0644]
make/vs/testapp/main.c [new file with mode: 0644]
make/vs/testapp/packages.config [new file with mode: 0644]
make/vs/testapp/testapp.vcxproj [new file with mode: 0644]
make/vs/testapp/testapp.vcxproj.filters [new file with mode: 0644]
make/vs/ucx/ucx.vcxproj [new file with mode: 0644]
make/vs/ucx/ucx.vcxproj.filters [new file with mode: 0644]
make/vs/uicommon/uicommon.vcxproj [new file with mode: 0644]
make/vs/uicommon/uicommon.vcxproj.filters [new file with mode: 0644]
make/vs/vcpkg.json [new file with mode: 0644]
resource/.DS_Store [new file with mode: 0644]
resource/locales/de_DE.properties [new file with mode: 0644]
resource/locales/en_EN.properties [new file with mode: 0644]
resource/template.app/Contents/Info.plist [new file with mode: 0644]
resource/template.app/Contents/PkgInfo [new file with mode: 0644]
resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings [new file with mode: 0644]
resource/template.app/Contents/Resources/English.lproj/MainMenu.nib [new file with mode: 0644]
ucx/Makefile [new file with mode: 0644]
ucx/allocator.c [new file with mode: 0644]
ucx/array_list.c [new file with mode: 0644]
ucx/buffer.c [new file with mode: 0644]
ucx/compare.c [new file with mode: 0644]
ucx/cx/allocator.h [new file with mode: 0644]
ucx/cx/array_list.h [new file with mode: 0644]
ucx/cx/buffer.h [new file with mode: 0644]
ucx/cx/collection.h [new file with mode: 0644]
ucx/cx/common.h [new file with mode: 0644]
ucx/cx/common.h.orig [new file with mode: 0644]
ucx/cx/compare.h [new file with mode: 0644]
ucx/cx/hash_key.h [new file with mode: 0644]
ucx/cx/hash_map.h [new file with mode: 0644]
ucx/cx/iterator.h [new file with mode: 0644]
ucx/cx/linked_list.h [new file with mode: 0644]
ucx/cx/list.h [new file with mode: 0644]
ucx/cx/map.h [new file with mode: 0644]
ucx/cx/mempool.h [new file with mode: 0644]
ucx/cx/printf.h [new file with mode: 0644]
ucx/cx/string.h [new file with mode: 0644]
ucx/cx/test.h [new file with mode: 0644]
ucx/cx/tree.h [new file with mode: 0644]
ucx/cx/utils.h [new file with mode: 0644]
ucx/hash_key.c [new file with mode: 0644]
ucx/hash_map.c [new file with mode: 0644]
ucx/iterator.c [new file with mode: 0644]
ucx/linked_list.c [new file with mode: 0644]
ucx/list.c [new file with mode: 0644]
ucx/map.c [new file with mode: 0644]
ucx/mempool.c [new file with mode: 0644]
ucx/printf.c [new file with mode: 0644]
ucx/string.c [new file with mode: 0644]
ucx/szmul.c [new file with mode: 0644]
ucx/tree.c [new file with mode: 0644]
ucx/utils.c [new file with mode: 0644]
ui/Makefile [new file with mode: 0644]
ui/cocoa/Makefile [new file with mode: 0644]
ui/cocoa/container.h [new file with mode: 0644]
ui/cocoa/container.m [new file with mode: 0644]
ui/cocoa/graphics.h [new file with mode: 0644]
ui/cocoa/graphics.m [new file with mode: 0644]
ui/cocoa/menu.h [new file with mode: 0644]
ui/cocoa/menu.m [new file with mode: 0644]
ui/cocoa/objs.mk [new file with mode: 0644]
ui/cocoa/resource.h [new file with mode: 0644]
ui/cocoa/resource.m [new file with mode: 0644]
ui/cocoa/stock.h [new file with mode: 0644]
ui/cocoa/stock.m [new file with mode: 0644]
ui/cocoa/text.h [new file with mode: 0644]
ui/cocoa/text.m [new file with mode: 0644]
ui/cocoa/toolbar.h [new file with mode: 0644]
ui/cocoa/toolbar.m [new file with mode: 0644]
ui/cocoa/toolkit.h [new file with mode: 0644]
ui/cocoa/toolkit.m [new file with mode: 0644]
ui/cocoa/tree.h [new file with mode: 0644]
ui/cocoa/tree.m [new file with mode: 0644]
ui/cocoa/window.h [new file with mode: 0644]
ui/cocoa/window.m [new file with mode: 0644]
ui/common/condvar.c [new file with mode: 0644]
ui/common/condvar.h [new file with mode: 0644]
ui/common/context.c [new file with mode: 0644]
ui/common/context.h [new file with mode: 0644]
ui/common/document.c [new file with mode: 0644]
ui/common/document.h [new file with mode: 0644]
ui/common/menu.c [new file with mode: 0644]
ui/common/menu.h [new file with mode: 0644]
ui/common/object.c [new file with mode: 0644]
ui/common/object.h [new file with mode: 0644]
ui/common/objs.mk [new file with mode: 0644]
ui/common/properties.c [new file with mode: 0644]
ui/common/properties.h [new file with mode: 0644]
ui/common/threadpool.c [new file with mode: 0644]
ui/common/threadpool.h [new file with mode: 0644]
ui/common/toolbar.c [new file with mode: 0644]
ui/common/toolbar.h [new file with mode: 0644]
ui/common/types.c [new file with mode: 0644]
ui/common/types.h [new file with mode: 0644]
ui/common/ucx_properties.c [new file with mode: 0644]
ui/common/ucx_properties.h [new file with mode: 0644]
ui/gtk/Makefile [new file with mode: 0644]
ui/gtk/button.c [new file with mode: 0644]
ui/gtk/button.h [new file with mode: 0644]
ui/gtk/container.c [new file with mode: 0644]
ui/gtk/container.h [new file with mode: 0644]
ui/gtk/display.c [new file with mode: 0644]
ui/gtk/display.h [new file with mode: 0644]
ui/gtk/dnd.c [new file with mode: 0644]
ui/gtk/dnd.h [new file with mode: 0644]
ui/gtk/draw_cairo.c [new file with mode: 0644]
ui/gtk/draw_cairo.h [new file with mode: 0644]
ui/gtk/draw_gdk.c [new file with mode: 0644]
ui/gtk/draw_gdk.h [new file with mode: 0644]
ui/gtk/entry.c [new file with mode: 0644]
ui/gtk/entry.h [new file with mode: 0644]
ui/gtk/graphics.c [new file with mode: 0644]
ui/gtk/graphics.h [new file with mode: 0644]
ui/gtk/headerbar.c [new file with mode: 0644]
ui/gtk/headerbar.h [new file with mode: 0644]
ui/gtk/icon.c [new file with mode: 0644]
ui/gtk/icon.h [new file with mode: 0644]
ui/gtk/image.c [new file with mode: 0644]
ui/gtk/image.h [new file with mode: 0644]
ui/gtk/list.c [new file with mode: 0644]
ui/gtk/list.h [new file with mode: 0644]
ui/gtk/menu.c [new file with mode: 0644]
ui/gtk/menu.h [new file with mode: 0644]
ui/gtk/objs.mk [new file with mode: 0644]
ui/gtk/range.c [new file with mode: 0644]
ui/gtk/range.h [new file with mode: 0644]
ui/gtk/text.c [new file with mode: 0644]
ui/gtk/text.h [new file with mode: 0644]
ui/gtk/toolbar.c [new file with mode: 0644]
ui/gtk/toolbar.h [new file with mode: 0644]
ui/gtk/toolkit.c [new file with mode: 0644]
ui/gtk/toolkit.h [new file with mode: 0644]
ui/gtk/window.c [new file with mode: 0644]
ui/motif/Makefile [new file with mode: 0644]
ui/motif/button.c [new file with mode: 0644]
ui/motif/button.h [new file with mode: 0644]
ui/motif/container.c [new file with mode: 0644]
ui/motif/container.h [new file with mode: 0644]
ui/motif/dnd.c [new file with mode: 0644]
ui/motif/dnd.h [new file with mode: 0644]
ui/motif/graphics.c [new file with mode: 0644]
ui/motif/graphics.h [new file with mode: 0644]
ui/motif/image.c [new file with mode: 0644]
ui/motif/image.h [new file with mode: 0644]
ui/motif/label.c [new file with mode: 0644]
ui/motif/label.h [new file with mode: 0644]
ui/motif/list.c [new file with mode: 0644]
ui/motif/list.h [new file with mode: 0644]
ui/motif/menu.c [new file with mode: 0644]
ui/motif/menu.h [new file with mode: 0644]
ui/motif/objs.mk [new file with mode: 0644]
ui/motif/range.c [new file with mode: 0644]
ui/motif/range.h [new file with mode: 0644]
ui/motif/stock.c [new file with mode: 0644]
ui/motif/stock.h [new file with mode: 0644]
ui/motif/text.c [new file with mode: 0644]
ui/motif/text.h [new file with mode: 0644]
ui/motif/toolbar.c [new file with mode: 0644]
ui/motif/toolbar.h [new file with mode: 0644]
ui/motif/toolkit.c [new file with mode: 0644]
ui/motif/toolkit.h [new file with mode: 0644]
ui/motif/tree.c [new file with mode: 0644]
ui/motif/tree.h [new file with mode: 0644]
ui/motif/window.c [new file with mode: 0644]
ui/qt/Makefile [new file with mode: 0644]
ui/qt/button.cpp [new file with mode: 0644]
ui/qt/button.h [new file with mode: 0644]
ui/qt/container.cpp [new file with mode: 0644]
ui/qt/container.h [new file with mode: 0644]
ui/qt/graphics.cpp [new file with mode: 0644]
ui/qt/graphics.h [new file with mode: 0644]
ui/qt/label.cpp [new file with mode: 0644]
ui/qt/label.h [new file with mode: 0644]
ui/qt/menu.cpp [new file with mode: 0644]
ui/qt/menu.h [new file with mode: 0644]
ui/qt/model.cpp [new file with mode: 0644]
ui/qt/model.h [new file with mode: 0644]
ui/qt/objs.mk [new file with mode: 0644]
ui/qt/qt4.pro [new file with mode: 0644]
ui/qt/stock.cpp [new file with mode: 0644]
ui/qt/stock.h [new file with mode: 0644]
ui/qt/text.cpp [new file with mode: 0644]
ui/qt/text.h [new file with mode: 0644]
ui/qt/toolbar.cpp [new file with mode: 0644]
ui/qt/toolbar.h [new file with mode: 0644]
ui/qt/toolkit.cpp [new file with mode: 0644]
ui/qt/toolkit.h [new file with mode: 0644]
ui/qt/tree.cpp [new file with mode: 0644]
ui/qt/tree.h [new file with mode: 0644]
ui/qt/window.cpp [new file with mode: 0644]
ui/qt/window.h [new file with mode: 0644]
ui/ui/button.h [new file with mode: 0644]
ui/ui/container.h [new file with mode: 0644]
ui/ui/display.h [new file with mode: 0644]
ui/ui/dnd.h [new file with mode: 0644]
ui/ui/entry.h [new file with mode: 0644]
ui/ui/graphics.h [new file with mode: 0644]
ui/ui/icons.h [new file with mode: 0644]
ui/ui/image.h [new file with mode: 0644]
ui/ui/menu.h [new file with mode: 0644]
ui/ui/properties.h [new file with mode: 0644]
ui/ui/range.h [new file with mode: 0644]
ui/ui/stock.h [new file with mode: 0644]
ui/ui/text.h [new file with mode: 0644]
ui/ui/toolbar.h [new file with mode: 0644]
ui/ui/toolkit.h [new file with mode: 0644]
ui/ui/tree.h [new file with mode: 0644]
ui/ui/ui.h [new file with mode: 0644]
ui/ui/window.h [new file with mode: 0644]
ui/winui/App.idl [new file with mode: 0644]
ui/winui/App.xaml [new file with mode: 0644]
ui/winui/App.xaml.cpp [new file with mode: 0644]
ui/winui/App.xaml.h [new file with mode: 0644]
ui/winui/Assets/LockScreenLogo.scale-200.png [new file with mode: 0644]
ui/winui/Assets/SplashScreen.scale-200.png [new file with mode: 0644]
ui/winui/Assets/Square150x150Logo.scale-200.png [new file with mode: 0644]
ui/winui/Assets/Square44x44Logo.scale-200.png [new file with mode: 0644]
ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png [new file with mode: 0644]
ui/winui/Assets/StoreLogo.png [new file with mode: 0644]
ui/winui/Assets/Wide310x150Logo.scale-200.png [new file with mode: 0644]
ui/winui/MainWindow.idl [new file with mode: 0644]
ui/winui/MainWindow.xaml [new file with mode: 0644]
ui/winui/MainWindow.xaml.cpp [new file with mode: 0644]
ui/winui/MainWindow.xaml.h [new file with mode: 0644]
ui/winui/Package.appxmanifest [new file with mode: 0644]
ui/winui/app.manifest [new file with mode: 0644]
ui/winui/appmenu.cpp [new file with mode: 0644]
ui/winui/appmenu.h [new file with mode: 0644]
ui/winui/button.cpp [new file with mode: 0644]
ui/winui/button.h [new file with mode: 0644]
ui/winui/commandbar.cpp [new file with mode: 0644]
ui/winui/commandbar.h [new file with mode: 0644]
ui/winui/condvar.cpp [new file with mode: 0644]
ui/winui/condvar.h [new file with mode: 0644]
ui/winui/container.cpp [new file with mode: 0644]
ui/winui/container.h [new file with mode: 0644]
ui/winui/dnd.cpp [new file with mode: 0644]
ui/winui/dnd.h [new file with mode: 0644]
ui/winui/icons.cpp [new file with mode: 0644]
ui/winui/icons.h [new file with mode: 0644]
ui/winui/image.cpp [new file with mode: 0644]
ui/winui/image.h [new file with mode: 0644]
ui/winui/label.cpp [new file with mode: 0644]
ui/winui/label.h [new file with mode: 0644]
ui/winui/list.cpp [new file with mode: 0644]
ui/winui/list.h [new file with mode: 0644]
ui/winui/packages.config [new file with mode: 0644]
ui/winui/pch.cpp [new file with mode: 0644]
ui/winui/pch.h [new file with mode: 0644]
ui/winui/readme.txt [new file with mode: 0644]
ui/winui/stock.cpp [new file with mode: 0644]
ui/winui/stock.h [new file with mode: 0644]
ui/winui/table.cpp [new file with mode: 0644]
ui/winui/table.h [new file with mode: 0644]
ui/winui/text.cpp [new file with mode: 0644]
ui/winui/text.h [new file with mode: 0644]
ui/winui/toolkit.cpp [new file with mode: 0644]
ui/winui/toolkit.h [new file with mode: 0644]
ui/winui/util.cpp [new file with mode: 0644]
ui/winui/util.h [new file with mode: 0644]
ui/winui/window.cpp [new file with mode: 0644]
ui/winui/window.h [new file with mode: 0644]
ui/winui/winui.vcxproj [new file with mode: 0644]
ui/winui/winui.vcxproj.filters [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..a8bfba4
--- /dev/null
@@ -0,0 +1,8 @@
+build
+config.mk
+make/vs/.vs
+make/vs/packages
+make/vs/**vcxproj.user
+make/vs/vcpkg_installed
+ui/winui/winui.vcxproj.user
+ui/winui/Generated Files
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7162bba
--- /dev/null
+++ b/Makefile
@@ -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:
diff --git a/application/Makefile b/application/Makefile
new file mode 100644 (file)
index 0000000..1ab56e2
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2024 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ..
+include ../config.mk
+
+CFLAGS += -I../ui/ -I../ucx -I..
+
+SRC  = main.c
+
+OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
+
+all: ../build/bin/idav$(APP_EXT)
+
+../build/bin/idav$(APP_EXT): $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
+       $(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS)
+
+../build/application/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
+
diff --git a/application/main.c b/application/main.c
new file mode 100644 (file)
index 0000000..45d80ef
--- /dev/null
@@ -0,0 +1,55 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2024 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+\r
+#ifdef _WIN32\r
+#include <Windows.h>\r
+#endif\r
+\r
+#include <ui/ui.h>\r
+\r
+int app_main(int argc, char **argv) {\r
+    ui_init("notes", argc, argv);\r
+    ui_main();\r
+    return 0;\r
+}\r
+\r
+#ifndef _WIN32\r
+\r
+int main(int argc, char** argv) {\r
+    return app_main(argc, argv);\r
+}\r
+#else\r
+\r
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {\r
+    return app_main(__argc, __argv);\r
+}\r
+#endif\r
+\r
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..4fe9469
--- /dev/null
+++ b/configure
@@ -0,0 +1,1147 @@
+#!/bin/sh
+
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+    :
+else
+    echo "Cannot create tmp dir $TEMP_DIR"
+    echo "Abort"
+    exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
+
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
+
+# custom variables
+
+# features
+
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
+
+# help text
+printhelp()
+{
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+
+Options:
+  --debug                 add extra compile flags for debug builds
+  --release               add extra compile flags for release builds
+  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+
+__EOF__
+}
+
+#
+# parse arguments
+#
+BUILD_TYPE="default"
+for ARG in "$@"
+do
+    case "$ARG" in
+        "--prefix="*)         prefix=${ARG#--prefix=} ;;
+        "--exec-prefix="*)    exec_prefix=${ARG#--exec-prefix=} ;;
+        "--bindir="*)         bindir=${ARG#----bindir=} ;;
+        "--sbindir="*)        sbindir=${ARG#--sbindir=} ;;
+        "--libdir="*)         libdir=${ARG#--libdir=} ;;
+        "--libexecdir="*)     libexecdir=${ARG#--libexecdir=} ;;
+        "--datarootdir="*)    datarootdir=${ARG#--datarootdir=} ;;
+        "--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} ;;
+        "--localedir"*)       localedir=${ARG#--localedir} ;;
+        "--help"*) printhelp; abort_configure ;;
+        "--debug")           BUILD_TYPE="debug" ;;
+        "--release")         BUILD_TYPE="release" ;;
+        "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+        "-"*) echo "unknown option: $ARG"; abort_configure ;;
+    esac
+done
+
+
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+    # CONFIG_SITE may contain space separated file names
+    for cs in $CONFIG_SITE; do
+        printf "loading defaults from $cs... "
+        . "$cs"
+        echo ok
+    done
+elif [ -f "$prefix/share/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/share/config.site"
+    echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/etc/config.site"
+    echo ok
+fi
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+    PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+    PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+    PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+    PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+    PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notisplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
+__EOF__
+
+# toolchain detection utilities
+. make/toolchain.sh
+
+#
+# DEPENDENCIES
+#
+
+# check languages
+lang_c=
+lang_cpp=
+if detect_c_compiler ; then
+    lang_c=1
+fi
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
+{
+    if "$PKG_CONFIG" --exists "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+    else return 1 ; fi
+    return 0
+}
+
+print_check_msg()
+{
+    if [ -z "$1" ]; then
+        shift
+        printf "$@"
+    fi
+}
+
+dependency_error_gtk2legacy()
+{
+    print_check_msg "$dep_checked_gtk2legacy" "checking for gtk2legacy... "
+    # dependency gtk2legacy
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "gtk+-2.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk2legacy" "yes\n"
+        dep_checked_gtk2legacy=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_gtk2legacy" "no\n"
+    dep_checked_gtk2legacy=1
+    return 0
+}
+dependency_error_curl()
+{
+    print_check_msg "$dep_checked_curl" "checking for curl... "
+    # dependency curl platform="macos"
+    while true
+    do
+        if notisplatform "macos"; then
+            break
+        fi
+        if tmp_flags=`curl-config --cflags` ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+        else
+            break
+        fi
+        if tmp_flags=`curl-config --ldflags` ; then
+            TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_curl" "yes\n"
+        dep_checked_curl=1
+        return 1
+    done
+
+    # dependency curl
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "libcurl" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libcurl`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libcurl`"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_curl" "yes\n"
+        dep_checked_curl=1
+        return 1
+    done
+
+    # dependency curl
+    while true
+    do
+        if tmp_flags=`curl-config --cflags` ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+        else
+            break
+        fi
+        if tmp_flags=`curl-config --libs` ; then
+            TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_curl" "yes\n"
+        dep_checked_curl=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_curl" "no\n"
+    dep_checked_curl=1
+    return 0
+}
+dependency_error_gtk2()
+{
+    print_check_msg "$dep_checked_gtk2" "checking for gtk2... "
+    # dependency gtk2
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if pkg-config --atleast-version=2.20 gtk+-2.0 > /dev/null ; then
+            :
+        else
+            break
+        fi
+        if test_pkg_config "gtk+-2.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk2" "yes\n"
+        dep_checked_gtk2=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_gtk2" "no\n"
+    dep_checked_gtk2=1
+    return 0
+}
+dependency_error_gtk3()
+{
+    print_check_msg "$dep_checked_gtk3" "checking for gtk3... "
+    # dependency gtk3
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "gtk+-3.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-3.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-3.0`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK3"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk3" "yes\n"
+        dep_checked_gtk3=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_gtk3" "no\n"
+    dep_checked_gtk3=1
+    return 0
+}
+dependency_error_gtk4()
+{
+    print_check_msg "$dep_checked_gtk4" "checking for gtk4... "
+    # dependency gtk4
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "gtk4" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk4`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk4`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK3"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_gtk4" "yes\n"
+        dep_checked_gtk4=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_gtk4" "no\n"
+    dep_checked_gtk4=1
+    return 0
+}
+dependency_error_openssl()
+{
+    print_check_msg "$dep_checked_openssl" "checking for openssl... "
+    # dependency openssl platform="windows"
+    while true
+    do
+        if notisplatform "windows"; then
+            break
+        fi
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lssl -lcrypto"
+        print_check_msg "$dep_checked_openssl" "yes\n"
+        dep_checked_openssl=1
+        return 1
+    done
+
+    # dependency openssl platform="macos"
+    while true
+    do
+        if notisplatform "macos"; then
+            break
+        fi
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -framework CoreFoundation"
+        print_check_msg "$dep_checked_openssl" "yes\n"
+        dep_checked_openssl=1
+        return 1
+    done
+
+    # dependency openssl platform="bsd"
+    while true
+    do
+        if notisplatform "bsd"; then
+            break
+        fi
+        if isplatform "macos" || istoolchain "macos"; then
+            break
+        fi
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lssl -lcrypto"
+        print_check_msg "$dep_checked_openssl" "yes\n"
+        dep_checked_openssl=1
+        return 1
+    done
+
+    # dependency openssl
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "openssl" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags openssl`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs openssl`"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_openssl" "yes\n"
+        dep_checked_openssl=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_openssl" "no\n"
+    dep_checked_openssl=1
+    return 0
+}
+dependency_error_libadwaita()
+{
+    print_check_msg "$dep_checked_libadwaita" "checking for libadwaita... "
+    # dependency libadwaita
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "libadwaita-1" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libadwaita-1`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libadwaita-1`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK4 -DUI_LIBADWAITA"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_libadwaita" "yes\n"
+        dep_checked_libadwaita=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_libadwaita" "no\n"
+    dep_checked_libadwaita=1
+    return 0
+}
+dependency_error_motif()
+{
+    print_check_msg "$dep_checked_motif" "checking for motif... "
+    # dependency motif platform="bsd"
+    while true
+    do
+        if notisplatform "bsd"; then
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF -I/usr/local/include/X11"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+        print_check_msg "$dep_checked_motif" "yes\n"
+        dep_checked_motif=1
+        return 1
+    done
+
+    # dependency motif
+    while true
+    do
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+        print_check_msg "$dep_checked_motif" "yes\n"
+        dep_checked_motif=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_motif" "no\n"
+    dep_checked_motif=1
+    return 0
+}
+dependency_error_libxml2()
+{
+    print_check_msg "$dep_checked_libxml2" "checking for libxml2... "
+    # dependency libxml2 platform="windows"
+    while true
+    do
+        if notisplatform "windows"; then
+            break
+        fi
+        if tmp_flags=`xml2-config --cflags` ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+        else
+            break
+        fi
+        if tmp_flags=`xml2-config --libs` ; then
+            TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_libxml2" "yes\n"
+        dep_checked_libxml2=1
+        return 1
+    done
+
+    # dependency libxml2 platform="macos"
+    while true
+    do
+        if notisplatform "macos"; then
+            break
+        fi
+        if tmp_flags=`xml2-config --cflags` ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+        else
+            break
+        fi
+        if tmp_flags=`xml2-config --libs` ; then
+            TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_libxml2" "yes\n"
+        dep_checked_libxml2=1
+        return 1
+    done
+
+    # dependency libxml2
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "libxml-2.0" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libxml-2.0`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libxml-2.0`"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_libxml2" "yes\n"
+        dep_checked_libxml2=1
+        return 1
+    done
+
+    # dependency libxml2
+    while true
+    do
+        if tmp_flags=`xml2-config --cflags` ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+        else
+            break
+        fi
+        if tmp_flags=`xml2-config --libs` ; then
+            TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+        else
+            break
+        fi
+        print_check_msg "$dep_checked_libxml2" "yes\n"
+        dep_checked_libxml2=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_libxml2" "no\n"
+    dep_checked_libxml2=1
+    return 0
+}
+dependency_error_cocoa()
+{
+    print_check_msg "$dep_checked_cocoa" "checking for cocoa... "
+    # dependency cocoa platform="macos"
+    while true
+    do
+        if notisplatform "macos"; then
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_COCOA"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lobjc -framework Cocoa"
+        print_check_msg "$dep_checked_cocoa" "yes\n"
+        dep_checked_cocoa=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_cocoa" "no\n"
+    dep_checked_cocoa=1
+    return 0
+}
+dependency_error_winui()
+{
+    print_check_msg "$dep_checked_winui" "checking for winui... "
+    # dependency winui platform="windows"
+    while true
+    do
+        if notisplatform "windows"; then
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WINUI"
+        print_check_msg "$dep_checked_winui" "yes\n"
+        dep_checked_winui=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_winui" "no\n"
+    dep_checked_winui=1
+    return 0
+}
+
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+while true
+do
+    while true
+    do
+        if [ -z "$lang_c" ] ; then
+            ERROR=1
+            break
+        fi
+
+        break
+    done
+    break
+done
+while true
+do
+    if notisplatform "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 notisplatform "unix"; then
+        break
+    fi
+    if isplatform "macos" || istoolchain "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 notisplatform "bsd"; then
+        break
+    fi
+    while true
+    do
+
+        TEMP_CFLAGS="$TEMP_CFLAGS -I/usr/local/include"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -L/usr/local/lib"
+        break
+    done
+    break
+done
+
+# add general dependency flags to flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+#
+# OPTION VALUES
+#
+checkopt_toolkit_libadwaita()
+{
+    VERR=0
+    if dependency_error_libadwaita ; 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_gtk4()
+{
+    VERR=0
+    if dependency_error_gtk4 ; 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
+    if dependency_error_gtk3 ; 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
+    if dependency_error_gtk2 ; 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
+    if dependency_error_gtk2legacy ; 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
+    if dependency_error_qt5 ; 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
+    if dependency_error_qt4 ; 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
+    if dependency_error_motif ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = motif
+__EOF__
+    return 0
+}
+
+#
+# TARGETS
+#
+
+echo >> "$TEMP_DIR/flags.mk"
+echo "configuring target: dav"
+echo "# flags for target dav" >> "$TEMP_DIR/flags.mk"
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+if dependency_error_curl; then
+    DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED curl "
+    ERROR=1
+fi
+if dependency_error_libxml2; then
+    DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libxml2 "
+    ERROR=1
+fi
+if dependency_error_openssl; then
+    DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED openssl "
+    ERROR=1
+fi
+
+# Features
+
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "DAV_CFLAGS  += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "DAV_CXXFLAGS  += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'DAV_CFLAGS += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'DAV_CXXFLAGS += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'DAV_CFLAGS += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'DAV_CXXFLAGS += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "DAV_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+echo >> "$TEMP_DIR/flags.mk"
+echo "configuring target: tk"
+echo "# flags for target tk" >> "$TEMP_DIR/flags.mk"
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+
+# Features
+
+# Option: --toolkit
+if [ -z "$OPT_TOOLKIT" ]; then
+    echo "auto-detecting option 'toolkit'"
+    SAVED_ERROR="$ERROR"
+    SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+    ERROR=1
+    while true
+    do
+        if isplatform "windows"; then
+        if checkopt_toolkit_winui ; then
+            echo "  toolkit: winui" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        fi
+        if isplatform "macos"; then
+        if checkopt_toolkit_cocoa ; then
+            echo "  toolkit: cocoa" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        fi
+        if checkopt_toolkit_gtk4 ; then
+            echo "  toolkit: gtk4" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_gtk3 ; then
+            echo "  toolkit: gtk3" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_qt5 ; then
+            echo "  toolkit: qt5" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_gtk2 ; then
+            echo "  toolkit: gtk2" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_qt4 ; then
+            echo "  toolkit: qt4" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        if checkopt_toolkit_motif ; then
+            echo "  toolkit: motif" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
+        break
+    done
+    if [ $ERROR -ne 0 ]; then
+        SAVED_ERROR=1
+        SAVED_DEPENDENCIES_FAILED="option 'toolkit' $SAVED_DEPENDENCIES_FAILED"
+    fi
+    ERROR="$SAVED_ERROR"
+    DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
+else
+    echo "checking option toolkit = $OPT_TOOLKIT"
+    if false; then
+        false
+    elif [ "$OPT_TOOLKIT" = "libadwaita" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_libadwaita ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk4" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk4 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk3" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk3 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk2" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk2 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "gtk2legacy" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_gtk2legacy ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "qt5" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_qt5 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "qt4" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_qt4 ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    elif [ "$OPT_TOOLKIT" = "motif" ]; then
+        echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+        if checkopt_toolkit_motif ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+        fi
+    fi
+fi
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "TK_CFLAGS  += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "TK_CXXFLAGS  += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'TK_CFLAGS += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'TK_CXXFLAGS += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+    if [ -n "$lang_c" ]; then
+        echo 'TK_CFLAGS += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo 'TK_CXXFLAGS += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "TK_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+
+# final result
+if [ $ERROR -ne 0 ]; then
+    echo
+    echo "Error: Unresolved dependencies"
+    echo "$DEPENDENCIES_FAILED"
+    abort_configure
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:      $prefix"
+echo "  TOOLCHAIN:   $TOOLCHAIN_NAME"
+echo "Options:"
+cat "$TEMP_DIR/options"
+echo
+
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
+
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
diff --git a/libidav/Makefile b/libidav/Makefile
new file mode 100644 (file)
index 0000000..83cf2d6
--- /dev/null
@@ -0,0 +1,60 @@
+#
+# 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 ../config.mk
+
+# list of source files
+SRC  = webdav.c
+SRC += session.c
+SRC += resource.c
+SRC += methods.c
+SRC += utils.c
+SRC += davqlparser.c
+SRC += davqlexec.c
+SRC += crypto.c
+SRC += xml.c
+SRC += versioning.c
+SRC += config.c
+SRC += pwdstore.c
+
+OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
+
+all: ../build/ucx ../build/lib/libidav$(LIB_EXT)
+
+../build/lib/libidav$(LIB_EXT): $(OBJ)
+       $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ)
+
+../build/libidav/%$(OBJ_EXT): %.c
+       $(CC) -I../ucx $(CFLAGS) $(DAV_CFLAGS) -c -o $@ $<
+
+../build/ucx:
+       test -d '$@'
+
+cppcheck: $(SRC)
+       $(CPPCHECK) $(CPPCHECK_CONFIG) -I../ucx $(CPPCHECK_FLAGS) $+ 2>> ../$(CPPCHECK_LOG)
+
diff --git a/libidav/config.c b/libidav/config.c
new file mode 100644 (file)
index 0000000..9cbb902
--- /dev/null
@@ -0,0 +1,1033 @@
+/*
+ * 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 "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <cx/hash_map.h>
+#include <errno.h>
+#include <libxml/tree.h>
+#include "utils.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+#define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
+
+#define print_error(lineno, ...) \
+    do {\
+        fprintf(stderr, "Error (config.xml line %u): ", lineno); \
+        fprintf(stderr, __VA_ARGS__); \
+        fprintf(stderr, "Abort.\n"); \
+    } while(0);
+#define print_warning(lineno, ...) \
+    do {\
+        fprintf(stderr, "Warning (config.xml line %u): ", lineno); \
+        fprintf(stderr, __VA_ARGS__); \
+    } while(0);
+
+#ifdef _WIN32
+#define ENV_HOME getenv("USERPROFILE")
+#else
+#define ENV_HOME getenv("HOME")
+#endif /* _WIN32 */
+
+
+static int load_repository(
+        DavConfig *config,
+        DavCfgRepository **list_begin,
+        DavCfgRepository **list_end,
+        xmlNode *reponode);
+static int load_key(
+        DavConfig *config,
+        DavCfgKey **list_begin,
+        DavCfgKey **list_end,
+        xmlNode *keynode);
+static int load_proxy(
+        DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type);
+static int load_namespace(
+        DavConfig *config,
+        DavCfgNamespace **list_begin,
+        DavCfgNamespace **list_end,
+        xmlNode *node);
+static int load_secretstore(DavConfig *config, xmlNode *node);
+
+
+int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node) {
+    str->node = node;
+    char *value = util_xml_get_text(node);
+    if(value) {
+        str->value = cx_strdup_a(config->mp->allocator, cx_str(value));
+        return 0;
+    } else {
+        str->value = (cxmutstr){NULL, 0};
+        return 1;
+    }
+}
+
+void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node) {
+    cbool->node = node;
+    char *value = util_xml_get_text(node);
+    cbool->value = util_getboolean(value);
+}
+
+static void set_xml_content(xmlNode *node, const char *content) {
+    xmlNode *child = node->children;
+    while(child) {
+        xmlNode *next = child->next;
+        xmlUnlinkNode(child);
+        xmlFreeNode(child);
+        child = next;
+    }
+    
+    if(content) {
+        xmlChar *encoded = xmlEncodeSpecialChars(node->doc, (const xmlChar*)content);
+        if(encoded) {
+            xmlNodeSetContent(node, encoded);
+            xmlFree(encoded);
+        }
+    }
+}
+
+void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) {
+    if(str->value.ptr) {
+        cxFree(config->mp->allocator, str->value.ptr);
+    }
+    if(new_value.ptr) {
+        str->value = cx_strdup_a(config->mp->allocator, new_value);
+    } else {
+        str->value = cx_mutstrn(NULL, 0);
+    }
+    
+    if(!str->node) {
+        str->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+        xmlAddChild(parent, str->node);
+    }
+    set_xml_content(str->node, new_value.ptr);
+}
+
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename) {
+    const char *content = new_value ? "true" : "false";
+    cbool->value = new_value;
+    if(!cbool->node) {
+        cbool->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+        xmlAddChild(parent, cbool->node);
+    }
+    set_xml_content(cbool->node, content);
+}
+
+void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename) {
+    char content[32];
+    snprintf(content, 32, "%" PRId64, new_value);
+    cint->value = new_value;
+    if(!cint->node) {
+        cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+        xmlAddChild(parent, cint->node);
+    }
+    set_xml_content(cint->node, content);
+}
+
+void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename) {
+    char content[32];
+    snprintf(content, 32, "%" PRIu64, new_value);
+    cint->value = new_value;
+    if(!cint->node) {
+        cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+        xmlAddChild(parent, cint->node);
+    }
+    set_xml_content(cint->node, content);
+}
+
+void dav_cfg_string_remove(CfgString *str) {
+    if(str->node) {
+        xmlUnlinkNode(str->node);
+        xmlFreeNode(str->node);
+        str->node = NULL;
+    }
+}
+
+void dav_cfg_bool_remove(CfgBool *cbool) {
+    if(cbool->node) {
+        xmlUnlinkNode(cbool->node);
+        xmlFreeNode(cbool->node);
+        cbool->node = NULL;
+    }
+}
+
+void dav_cfg_int_remove(CfgInt *cint) {
+    if(cint->node) {
+        xmlUnlinkNode(cint->node);
+        xmlFreeNode(cint->node);
+        cint->node = NULL;
+    }
+}
+
+void dav_cfg_uint_remove(CfgUInt *cint) {
+    if(cint->node) {
+        xmlUnlinkNode(cint->node);
+        xmlFreeNode(cint->node);
+        cint->node = NULL;
+    }
+}
+
+
+DavConfig* dav_config_new(xmlDoc *doc) {
+    CxMempool *cfg_mp = cxMempoolCreate(128, NULL);
+    DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig));
+    memset(config, 0, sizeof(DavConfig));
+    config->mp = cfg_mp;
+    
+    if(!doc) {
+        doc = xmlNewDoc(BAD_CAST "1.0");
+        xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration");
+        xmlDocSetRootElement(doc, root);
+    }
+    config->doc = doc;
+    
+    return config;
+}
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) {
+    xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0);
+    if(!doc) {
+        if(error) {
+            *error = DAV_CONFIG_ERROR_XML;
+        }
+        return NULL;
+    }
+    
+    DavConfig *config = dav_config_new(doc);
+    CxMempool *cfg_mp = config->mp;
+    cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc);
+    
+    DavCfgRepository *repos_begin = NULL;
+    DavCfgRepository *repos_end = NULL;
+    DavCfgKey *keys_begin = NULL;
+    DavCfgKey *keys_end = NULL;
+    DavCfgNamespace *namespaces_begin = NULL;
+    DavCfgNamespace *namespaces_end = NULL;
+    
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    xmlNode *node = xml_root->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "repository")) {
+                ret = load_repository(config, &repos_begin, &repos_end, node);
+            } else if(xstreq(node->name, "key")) {
+                ret = load_key(config, &keys_begin, &keys_end, node);
+            } else if (xstreq(node->name, "http-proxy")) {
+                config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+                ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY);
+            } else if (xstreq(node->name, "https-proxy")) {
+                config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+                ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY);
+            } else if (xstreq(node->name, "namespace")) {
+                ret = load_namespace(config, &namespaces_begin, &namespaces_end, node);
+            } else if (xstreq(node->name, "secretstore")) {
+                ret = load_secretstore(config, node);
+            } else {
+                fprintf(stderr, "Unknown config element: %s\n", node->name);
+                ret = 1;
+            }
+        }
+        node = node->next;
+    }
+    
+    config->repositories = repos_begin;
+    config->keys = keys_begin;
+    config->namespaces = namespaces_begin;
+    
+    if(ret != 0 && error) {
+        *error = ret;
+        cxMempoolDestroy(cfg_mp);
+    } 
+    
+    return config;
+}
+
+void dav_config_free(DavConfig *config) {
+    cxMempoolDestroy(config->mp);
+}
+
+CxBuffer* dav_config2buf(DavConfig *config) {
+    xmlChar* xmlText = NULL;
+    int textLen = 0;
+    xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen, 1);
+    
+    if(!xmlText) {
+        return NULL;
+    }
+    
+    CxBuffer *buf = cxBufferCreate(NULL, textLen, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+    cxBufferWrite(xmlText, 1, textLen, buf);
+    xmlFree(xmlText);
+    return buf;
+}
+
+
+static int repo_add_config(
+        DavConfig *config,
+        DavCfgRepository *repo,
+        xmlNode* node)
+{
+    unsigned short lineno = node->line;
+    char *key = (char*)node->name;
+    char *value = util_xml_get_text(node);
+
+    /* every key needs a value */
+    if(!value) {
+        /* TODO: maybe this should only be reported, if the key is valid
+         * But this makes the code very ugly.
+         */
+        print_error(lineno, "missing value for config element: %s\n", key);
+        return 1;
+    }
+    
+    if(xstreq(key, "name")) {
+        dav_cfg_string_set_node_value(config, &repo->name, node);
+    } else if(xstreq(key, "url")) {
+        dav_cfg_string_set_node_value(config, &repo->url, node);
+    } else if(xstreq(key, "user")) {
+        dav_cfg_string_set_node_value(config, &repo->user, node);
+    } else if(xstreq(key, "password")) {
+        dav_cfg_string_set_node_value(config, &repo->password, node);
+    } else if(xstreq(key, "stored-user")) {
+        dav_cfg_string_set_node_value(config, &repo->stored_user, node);
+    } else if(xstreq(key, "default-key")) {
+        dav_cfg_string_set_node_value(config, &repo->default_key, node);
+    } else if(xstreq(key, "full-encryption")) {
+        dav_cfg_bool_set_node_value(config, &repo->full_encryption, node);
+    } else if(xstreq(key, "content-encryption")) {
+        dav_cfg_bool_set_node_value(config, &repo->content_encryption, node);
+    } else if(xstreq(key, "decrypt-content")) {
+        dav_cfg_bool_set_node_value(config, &repo->decrypt_content, node);
+    } else if(xstreq(key, "decrypt-name")) {
+        dav_cfg_bool_set_node_value(config, &repo->decrypt_name, node);
+    } else if(xstreq(key, "cert")) {
+        dav_cfg_string_set_node_value(config, &repo->cert, node);
+    } else if(xstreq(key, "verification")) {
+        dav_cfg_bool_set_node_value(config, &repo->verification, node);
+    } else if(xstreq(key, "ssl-version")) {
+        repo->ssl_version.node = node;
+        int ssl_version = dav_str2ssl_version((const char*)value);
+        if(ssl_version == -1) {
+            print_warning(lineno, "unknown ssl version: %s\n", value);
+            repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+        } else {
+            repo->ssl_version.value = ssl_version;
+        }
+    } else if(xstreq(key, "authmethods")) {
+        repo->authmethods.node = node;
+        repo->authmethods.value = CURLAUTH_NONE;
+        const char *delims = " \t\r\n";
+        char *meths = strdup(value);
+        char *meth = strtok(meths, delims);
+        while (meth) {
+            if(xstrEQ(meth, "basic")) {
+                repo->authmethods.value |= CURLAUTH_BASIC;
+            } else if(xstrEQ(meth, "digest")) {
+                repo->authmethods.value |= CURLAUTH_DIGEST;
+            } else if(xstrEQ(meth, "negotiate")) {
+                repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE;
+            } else if(xstrEQ(meth, "ntlm")) {
+                repo->authmethods.value |= CURLAUTH_NTLM;
+            } else if(xstrEQ(meth, "any")) {
+                repo->authmethods.value = CURLAUTH_ANY;
+            } else if(xstrEQ(meth, "none")) {
+                /* skip */
+            } else {
+                print_warning(lineno,
+                        "unknown authentication method: %s\n", meth);
+            }
+            meth = strtok(NULL, delims);
+        }
+        free(meths);
+    } else {
+        print_error(lineno, "unkown repository config element: %s\n", key);
+        return 1;
+    }
+    return 0;
+}
+
+int dav_str2ssl_version(const char *value) {
+    if(xstrEQ(value, "TLSv1")) {
+        return CURL_SSLVERSION_TLSv1;
+    } else if(xstrEQ(value, "SSLv2")) {
+        return CURL_SSLVERSION_SSLv2;
+    } else if(xstrEQ(value, "SSLv3")) {
+        return CURL_SSLVERSION_SSLv3;
+    }
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
+    else if(xstrEQ(value, "TLSv1.0")) {
+        return CURL_SSLVERSION_TLSv1_0;
+    } else if(xstrEQ(value, "TLSv1.1")) {
+        return CURL_SSLVERSION_TLSv1_1;
+    } else if(xstrEQ(value, "TLSv1.2")) {
+        return CURL_SSLVERSION_TLSv1_2;
+    }
+#endif
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
+    else if(xstrEQ(value, "TLSv1.3")) {
+        return CURL_SSLVERSION_TLSv1_3;
+    }
+#endif
+    return -1;
+}
+
+static int load_repository(
+        DavConfig *config,
+        DavCfgRepository **list_begin,
+        DavCfgRepository **list_end,
+        xmlNode *reponode)
+{
+    DavCfgRepository *repo = dav_repository_new(config);
+    repo->node = reponode;
+    
+    // add repo config from child nodes
+    xmlNode *node = reponode->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            ret = repo_add_config(config, repo, node);
+        }
+        node = node->next;
+    }
+    
+    // success: add repo to the configuration, error: free repo
+    if(ret) {
+        return 1;
+    } else {
+        cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgRepository, prev),
+                offsetof(DavCfgRepository, next),
+                repo);
+    }
+    
+    return 0;
+}
+
+static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) {
+    xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t");
+    xmlAddChild(node, text1);
+    
+    cxmutstr ctn = cx_strdup(cx_strcast(content));
+    xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr);
+    free(ctn.ptr);
+    
+    xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n");
+    xmlAddChild(node, text2);
+    
+    return newNode;
+}
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) {
+    if(repo->node) {
+        fprintf(stderr, "Error: dav_config_add_repository: node already exists\n");
+        return;
+    }
+    
+    xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository");
+    xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(repoNode, rtext1);
+    repo->node = repoNode;
+    if(repo->name.value.ptr) {
+        repo->name.node = addXmlNode(repoNode, "name", repo->name.value);
+    }
+    if(repo->url.value.ptr) {
+        repo->url.node = addXmlNode(repoNode, "url", repo->url.value);
+    }
+    if(repo->user.value.ptr) {
+        repo->user.node = addXmlNode(repoNode, "user", repo->user.value);
+    }
+    if(repo->password.value.ptr) {
+        repo->password.node = addXmlNode(repoNode, "password", repo->password.value);
+    }
+    
+    if(repo->stored_user.value.ptr) {
+        repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value);
+    }
+    if(repo->default_key.value.ptr) {
+        repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value);
+    }
+    if(repo->cert.value.ptr) {
+        repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value);
+    }
+    
+    // TODO: implement booleans
+    
+    // indent closing tag
+    xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+    xmlAddChild(repoNode, rtext2);
+    
+    // add repository to internal list
+    DavCfgRepository **list_begin = &config->repositories;
+    cx_linked_list_add(
+                (void**)list_begin,
+                NULL,
+                offsetof(DavCfgRepository, prev),
+                offsetof(DavCfgRepository, next),
+                repo);
+    
+    // add repository element to the xml document
+    xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+    
+    xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+    xmlAddChild(xml_root, text1);
+    
+    xmlAddChild(xml_root, repoNode);
+    
+    xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(xml_root, text2);
+}
+
+DavCfgRepository* dav_repository_new(DavConfig *config) {
+    DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository));
+    memset(repo, 0, sizeof(DavCfgRepository));
+    repo->decrypt_name.value = false;
+    repo->decrypt_content.value = true;
+    repo->decrypt_properties.value = false;
+    repo->verification.value = true;
+    repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+    repo->authmethods.value = CURLAUTH_BASIC;
+    return repo;
+}
+
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo) {
+    // TODO
+}
+
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) {
+    if(repo->prev) {
+        repo->prev->next = repo->next;
+    }
+    if(repo->next) {
+        repo->next->prev = repo->prev;
+    }
+    
+    if(repo->node) {
+        // TODO: remove newline after repo node
+        
+        xmlUnlinkNode(repo->node);
+        xmlFreeNode(repo->node);
+    }
+}
+
+int dav_repository_get_flags(DavCfgRepository *repo) {
+    int flags = 0;
+    
+    DavBool encrypt_content = FALSE;
+    DavBool encrypt_name = FALSE;
+    DavBool encrypt_properties = FALSE;
+    DavBool decrypt_content = FALSE;
+    DavBool decrypt_name = FALSE;
+    DavBool decrypt_properties = FALSE;
+    if(repo->full_encryption.value) {
+        encrypt_content = TRUE;
+        encrypt_name = TRUE;
+        encrypt_properties = TRUE;
+        decrypt_content = TRUE;
+        decrypt_name = TRUE;
+        decrypt_properties = TRUE;
+    } else if(repo->content_encryption.value) {
+        encrypt_content = TRUE;
+        decrypt_content = TRUE;
+    }
+    
+    if(decrypt_content) {
+        flags |= DAV_SESSION_DECRYPT_CONTENT;
+    }
+    if(decrypt_name) {
+        flags |= DAV_SESSION_DECRYPT_NAME;
+    }
+    if(decrypt_properties) {
+        flags |= DAV_SESSION_DECRYPT_PROPERTIES;
+    }
+    if(encrypt_content) {
+        flags |= DAV_SESSION_ENCRYPT_CONTENT;
+    }
+    if(encrypt_name) {
+        flags |= DAV_SESSION_ENCRYPT_NAME;
+    }
+    if(encrypt_properties) {
+        flags |= DAV_SESSION_ENCRYPT_PROPERTIES;
+    }
+    return flags;
+}
+
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) {
+    if(repo->url.value.ptr) {
+        cxFree(config->mp->allocator, repo->url.value.ptr);
+    }
+    repo->url.value = cx_strdup_a(config->mp->allocator, newurl);
+}
+
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) {
+    const CxAllocator *a = config->mp->allocator;
+    if(user.length > 0) {
+        repo->user.value = cx_strdup_a(a, user);
+    }
+    if(password.length > 0) {
+        char *pwenc = util_base64encode(password.ptr, password.length);
+        repo->password.value = cx_strdup_a(a, cx_str(pwenc));
+        free(pwenc);
+    }
+}
+
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) {
+    cxmutstr pw = { NULL, 0 };
+    if(repo->password.value.ptr) {
+        pw = cx_mutstr(util_base64decode(repo->password.value.ptr));
+    }
+    return pw;
+}
+
+
+void dav_config_add_key(DavConfig *config, DavCfgKey *key) {
+    cx_linked_list_add(
+            (void**)&config->keys,
+            NULL,
+            offsetof(DavCfgKey, prev),
+            offsetof(DavCfgKey, next),
+            key);
+    
+    if(key->node) {
+        fprintf(stderr, "Error: dav_config_add_key: node already exists\n");
+        return;
+    }
+    
+    xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key");
+    xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(keyNode, rtext1);
+    key->node = keyNode;
+    
+    if(key->name.value.ptr) {
+        key->name.node = addXmlNode(keyNode, "name", key->name.value);
+    }
+    const char *type = dav_config_keytype_str(key->type);
+    if(type) {
+        key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type));
+    }
+    if(key->file.value.ptr) {
+        key->file.node = addXmlNode(keyNode, "file", key->file.value);
+    }
+    
+     // indent closing tag
+    xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+    xmlAddChild(keyNode, rtext2);
+    
+    // add key element to the xml document
+    xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+    
+    xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+    xmlAddChild(xml_root, text1);
+    
+    xmlAddChild(xml_root, keyNode);
+    
+    xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(xml_root, text2);
+}
+
+DavCfgKey* dav_key_new(DavConfig *config) {
+    DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+    memset(key, 0, sizeof(DavCfgKey));
+    key->type = DAV_KEY_TYPE_AES256;
+    return key;
+}
+
+void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) {
+    cx_linked_list_remove(
+            (void**)&config->keys,
+            NULL,
+            offsetof(DavCfgKey, prev),
+            offsetof(DavCfgKey, next),
+            key);
+    if(key->node) {
+        // TODO: remove newline after key node
+        
+        xmlUnlinkNode(key->node);
+        xmlFreeNode(key->node);
+    }
+}
+
+
+static int load_key(
+        DavConfig *config,
+        DavCfgKey **list_begin,
+        DavCfgKey **list_end,
+        xmlNode *keynode)
+{
+    xmlNode *node = keynode->children;
+    DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+    memset(key, 0, sizeof(DavCfgKey));
+    key->type = DAV_KEY_TYPE_AES256;
+    key->node = keynode;
+    
+    int error = 0;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "name")) {
+                dav_cfg_string_set_node_value(config, &key->name, node);
+            } else if(xstreq(node->name, "file")) {
+                dav_cfg_string_set_node_value(config, &key->file, node);
+            } else if(xstreq(node->name, "type")) {
+                const char *value = util_xml_get_text(node);
+                key->type_node = node;
+                if(!strcmp(value, "aes128")) {
+                    key->type = DAV_KEY_TYPE_AES128;
+                } else if(!strcmp(value, "aes256")) {
+                    key->type = DAV_KEY_TYPE_AES256;
+                } else {
+                    print_error(node->line, "unknown key type %s\n", value);
+                    error = 1;
+                }
+            } else {
+                key->unknown_elements++;
+            }
+                
+        }
+        node = node->next;
+    }
+    
+    if(!key->name.value.ptr) {
+        error = 1;
+    }
+    
+    if(!error) {
+        error = 0;
+        size_t expected_length = 0;
+        if(key->type == DAV_KEY_TYPE_AES128) {
+            expected_length = 16;
+        }
+        if(key->type == DAV_KEY_TYPE_AES256) {
+            expected_length = 32;
+        }
+        /*
+        if(key->length < expected_length) {
+            print_error(keynode->line, "key %s is too small (%zu < %zu)\n",
+                    key->name,
+                    key->length,
+                    expected_length);
+            error = 1;
+        }
+        
+        // add key to context
+        if(!error) {
+            cxMapPut(keys, cx_hash_key_str(key->name), key);
+            dav_context_add_key(context, key);
+        }
+        */
+    }
+    
+    // cleanup
+    if(error) {
+        return 1;
+    } else {
+        // add key to the configuration
+        cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgKey, prev),
+                offsetof(DavCfgKey, next),
+                key);
+        
+        return 0;
+    }
+}
+
+static int load_proxy(
+        DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type)
+{
+    const char *stype;
+    if(type == DAV_HTTPS_PROXY) {
+        stype = "https";
+    } else if(type == DAV_HTTP_PROXY) {
+        stype = "http";
+    } else {
+        fprintf(stderr, "unknown proxy type\n");
+        return 1;
+    }
+    
+    if(!proxy) {
+        // no xml error - so report this directly via fprintf
+        fprintf(stderr, "no memory reserved for %s proxy.\n", stype);
+        return 1;
+    }
+    
+    xmlNode *node = proxynode->children;
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            int reportmissingvalue = 0;
+            if(xstreq(node->name, "url")) {
+                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->url, node);
+            } else if(xstreq(node->name, "user")) {
+                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->user, node);
+            } else if(xstreq(node->name, "password")) {
+                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->password, node);
+            } else if(xstreq(node->name, "no")) {
+                reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->noproxy, node);
+            } else {
+                proxy->unknown_elements++;
+            }
+            
+            if (reportmissingvalue) {
+                print_error(node->line,
+                        "missing value for proxy configuration element: %s\n",
+                        node->name);
+                ret = 1;
+                break;
+            }
+        }
+        node = node->next;
+    }
+    
+    if(!ret && !proxy->url.value.ptr) {
+        print_error(proxynode->line, "missing url for %s proxy.\n", stype);
+        return 1;
+    }
+    
+    return ret;
+}
+
+static char* get_attr_content(xmlNode *node) {
+    // TODO: remove code duplication (util_xml_get_text)
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+static int load_namespace(
+        DavConfig *config,
+        DavCfgNamespace **list_begin,
+        DavCfgNamespace **list_end,
+        xmlNode *node)
+{
+    const char *prefix = NULL;
+    const char *uri = NULL;
+    xmlAttr *attr = node->properties;
+    while(attr) {
+        if(attr->type == XML_ATTRIBUTE_NODE) {
+            char *value = get_attr_content(attr->children);
+            if(!value) {
+                print_error(
+                        node->line,
+                        "missing value for attribute %s\n", (char*)attr->name);
+                return 1;
+            }
+            if(xstreq(attr->name, "prefix")) {
+                prefix = value;
+            } else if(xstreq(attr->name, "uri")) {
+                uri = value;
+            } else {
+                print_error(
+                        node->line,
+                        "unexpected attribute %s\n", (char*)attr->name);
+                return 1;
+            }
+        }
+        attr = attr->next;
+    }
+    
+    if(!prefix) {
+        print_error(node->line, "missing prefix attribute\n");
+        return 1;
+    }
+    if(!uri) {
+        print_error(node->line, "missing uri attribute\n");
+        return 1;
+    }
+    
+    DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace));
+    memset(ns, 0, sizeof(DavCfgNamespace));
+    ns->node = node;
+    ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix));
+    ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri));
+    cx_linked_list_add(
+                (void**)list_begin,
+                (void**)list_end,
+                offsetof(DavCfgNamespace, prev),
+                offsetof(DavCfgNamespace, next),
+                ns);
+    
+    return 0;
+}
+
+static int load_secretstore(DavConfig *config, xmlNode *node) {
+    // currently only one secretstore is supported
+    
+    if(config->secretstore) {
+        return 1;
+    }
+    
+    config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore));
+    
+    node = node->children;
+    int error = 0;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "unlock-command")) {
+                dav_cfg_string_set_node_value(config, &config->secretstore->unlock_cmd, node);
+            } else if(xstreq(node->name, "lock-command")) {
+                dav_cfg_string_set_node_value(config, &config->secretstore->lock_cmd, node);
+            }
+        }
+        node = node->next;
+    }
+    
+    return error;
+}
+
+
+
+
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) {
+    if(!config) {
+        return NULL;
+    }
+    DavCfgRepository *repo = config->repositories;
+    while(repo) {
+        if(!cx_strcmp(cx_strcast(repo->name.value), name)) {
+            return repo;
+        }
+        repo = repo->next;
+    }
+    return NULL;
+}
+
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) {
+    cxmutstr p;
+    DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p);
+    *path = p.ptr;
+    return repo;
+}
+
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) {
+    path->ptr = NULL;
+    path->length = 0;
+    
+    int s;
+    if(cx_strprefix(url, CX_STR("http://"))) {
+        s = 7;
+    } else if(cx_strprefix(url, CX_STR("https://"))) {
+        s = 8;
+    } else {
+        s = 1;
+    }
+
+    // split URL into repository and path
+    cxstring r = cx_strsubs(url, s);
+    cxstring p = cx_strchr(r, '/');
+    r = cx_strsubsl(url, 0, url.length-p.length);
+    if(p.length == 0) {
+        p = cx_strn("/", 1);
+    }
+    
+    DavCfgRepository *repo = dav_config_get_repository(config, r);
+    if(repo) {
+        *path = cx_strdup(p);
+    } else {
+        // TODO: who is responsible for freeing this repository?
+        // how can the callee know, if he has to call free()?
+        repo = dav_repository_new(config);
+        repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR(""));
+        if(url.ptr[url.length-1] == '/') {
+            repo->url.value = cx_strdup_a(config->mp->allocator, url);
+            *path = cx_strdup(CX_STR("/"));
+        } else if (cx_strchr(url, '/').length > 0) {
+            // TODO: fix the following workaround after
+            //       fixing the inconsistent behavior of util_url_*()
+            cxstring repo_url = util_url_base_s(url);
+            repo->url.value = cx_strdup_a(config->mp->allocator, repo_url);
+            *path = cx_strdup(util_url_path_s(url));
+        } else {
+            repo->url.value = cx_strdup(url);
+            *path = cx_strdup(CX_STR("/"));
+        }
+    }
+    
+    return repo;
+}
+
+int dav_config_keytype(DavCfgKeyType type) {
+    switch(type) {
+        default: break;
+        case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256;
+        case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128;
+    }
+    return 0;
+}
+
+const char* dav_config_keytype_str(DavCfgKeyType type) {
+    switch(type) {
+        default: break;
+        case DAV_KEY_TYPE_AES256: return "aes256";
+        case DAV_KEY_TYPE_AES128: return "aes128";
+    }
+    return NULL;
+}
+
+int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) {
+    for(DavCfgKey *key=config->keys;key;key=key->next) {
+        char *file = cx_strdup_m(key->file.value).ptr;
+        cxmutstr keycontent = loadkey(file);
+        free(file);
+        
+        // TODO: check key length
+        
+        if(!keycontent.ptr) {
+            return 1;
+        }
+        
+        DavKey *davkey = calloc(1, sizeof(DavKey));
+        davkey->name = cx_strdup_m(key->name.value).ptr;
+        davkey->type = dav_config_keytype(key->type);
+        davkey->data = keycontent.ptr;
+        davkey->length = keycontent.length;
+        
+        dav_context_add_key(ctx, davkey);
+    }
+    return 0;
+}
+
+int dav_config_register_namespaces(DavConfig *config, DavContext *ctx) {
+    DavCfgNamespace *ns = config->namespaces;
+    while(ns) {
+        dav_add_namespace(ctx, ns->prefix.ptr, ns->uri.ptr);
+        ns = ns->next;
+    }
+    return 0;
+}
diff --git a/libidav/config.h b/libidav/config.h
new file mode 100644 (file)
index 0000000..f85c097
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * 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 LIBIDAV_CONFIG_H
+#define LIBIDAV_CONFIG_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavConfig         DavConfig;
+typedef struct DavCfgRepository  DavCfgRepository;
+typedef struct DavCfgProxy       DavCfgProxy;
+typedef struct DavCfgKey         DavCfgKey;
+typedef struct DavCfgNamespace   DavCfgNamespace;
+typedef struct DavCfgSecretStore DavCfgSecretStore;
+
+typedef struct CfgString  CfgString;
+typedef struct CfgInt     CfgInt;
+typedef struct CfgUInt    CfgUInt;
+typedef struct CfgBool    CfgBool;
+
+typedef enum dav_cfg_key_type DavCfgKeyType;
+
+typedef cxmutstr (*dav_loadkeyfile_func)(const char *filename);
+
+#define DAV_HTTP_PROXY 1
+#define DAV_HTTPS_PROXY 2
+    
+enum dav_cfg_key_type {
+    DAV_KEY_TYPE_AES256 = 0,
+    DAV_KEY_TYPE_AES128,
+    DAV_KEY_TYPE_UNKNOWN
+};
+
+struct DavConfig {
+    CxMempool         *mp;
+    
+    DavCfgRepository  *repositories;
+    DavCfgKey         *keys;
+    DavCfgNamespace   *namespaces;
+    DavCfgProxy       *http_proxy;
+    DavCfgProxy       *https_proxy;
+    DavCfgSecretStore *secretstore;
+    
+    xmlDoc *doc;
+};
+
+struct CfgString {
+    cxmutstr value;
+    xmlNode *node;
+};
+
+struct CfgInt {
+    int64_t value;
+    xmlNode *node;
+};
+
+struct CfgUInt {
+    uint64_t value;
+    xmlNode *node;
+};
+
+struct CfgBool {
+    bool value;
+    xmlNode *node;
+};
+
+
+struct DavCfgRepository {
+    xmlNode *node;
+    
+    CfgString     name;
+    CfgString     url;
+    CfgString     user;
+    CfgString     password;
+    CfgString     stored_user;
+    CfgString     default_key;
+    CfgString     cert;
+    CfgBool       verification;
+    
+    CfgBool       full_encryption;
+    CfgBool       content_encryption;
+    CfgBool       decrypt_content;
+    CfgBool       decrypt_name;
+    CfgBool       decrypt_properties;
+    
+    CfgInt        ssl_version;
+    CfgUInt       authmethods;
+    
+    int           unknown_elements;
+    
+    DavCfgRepository    *prev;
+    DavCfgRepository    *next;
+};
+
+struct DavCfgProxy {
+    CfgString  url;
+    CfgString  user;
+    CfgString  password;
+    CfgString  noproxy;
+    
+    int     unknown_elements;
+};
+
+struct DavCfgKey {
+    xmlNode *node;
+    
+    CfgString  name;
+    CfgString  file;
+    DavCfgKeyType type;
+    xmlNode *type_node;
+    
+    DavCfgKey *prev;
+    DavCfgKey *next;
+    
+    int       unknown_elements;
+};
+
+struct DavCfgNamespace {
+    xmlNode *node;
+    cxmutstr prefix;
+    cxmutstr uri;
+    
+    DavCfgNamespace *prev;
+    DavCfgNamespace *next;
+};
+
+struct DavCfgSecretStore {
+    CfgString unlock_cmd;
+    CfgString lock_cmd;
+};
+
+enum DavConfigError {
+    DAV_CONFIG_ERROR_XML = 0
+};
+
+DavConfig* dav_config_new(xmlDoc *doc);
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error);
+
+void dav_config_free(DavConfig *config);
+
+CxBuffer* dav_config2buf(DavConfig *config);
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo);
+
+DavCfgRepository* dav_repository_new(DavConfig *config);
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo);
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo);
+int dav_repository_get_flags(DavCfgRepository *repo);
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl);
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password);
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo);
+
+void dav_config_add_key(DavConfig *config, DavCfgKey *key);
+
+DavCfgKey* dav_key_new(DavConfig *config);
+void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key);
+
+int dav_str2ssl_version(const char *str);
+
+int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node);
+void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node);
+
+void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename);
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename);
+void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename);
+void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename);
+
+void dav_cfg_string_remove(CfgString *str);
+void dav_cfg_bool_remove(CfgBool *cbool);
+void dav_cfg_int_remove(CfgInt *cint);
+void dav_cfg_uint_remove(CfgUInt *cint);
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name);
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path);
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path);
+
+int dav_config_keytype(DavCfgKeyType type);
+const char* dav_config_keytype_str(DavCfgKeyType type);
+int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey);
+
+int dav_config_register_namespaces(DavConfig *config, DavContext *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_CONFIG_H */
+
diff --git a/libidav/crypto.c b/libidav/crypto.c
new file mode 100644 (file)
index 0000000..d52e845
--- /dev/null
@@ -0,0 +1,1542 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include "utils.h"
+
+#include "crypto.h"
+
+/* -------------------- OpenSSL Crypto Functions -------------------- */
+#ifdef DAV_USE_OPENSSL
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+
+static EVP_CIPHER_CTX* create_evp_cipher_ctx() {
+    EVP_CIPHER_CTX *ctx = malloc(sizeof(EVP_CIPHER_CTX));
+    EVP_CIPHER_CTX_init(ctx);
+    return ctx;
+}
+
+static void free_evp_cipher_ctx(EVP_CIPHER_CTX *ctx) {
+    EVP_CIPHER_CTX_cleanup(ctx);
+    free(ctx);
+}
+
+#define EVP_CIPHER_CTX_new() create_evp_cipher_ctx()
+#define EVP_CIPHER_CTX_free(ctx) free_evp_cipher_ctx(ctx)
+
+#endif
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    return !RAND_bytes(buf, len);
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    SHA256_Init(&dec->sha256);
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+    
+    return dec;
+}
+
+void aes_decrypter_init(AESDecrypter *dec) {
+    //EVP_CIPHER_CTX_init(&dec->ctx);
+    dec->ctx = EVP_CIPHER_CTX_new();
+    dec->init = 1;
+    if(dec->key->type == DAV_KEY_AES128) {
+        EVP_DecryptInit_ex(
+                dec->ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                dec->key->data,
+                dec->ivtmp);
+    } else if(dec->key->type == DAV_KEY_AES256) {
+        EVP_DecryptInit_ex(
+                dec->ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                dec->key->data,
+                dec->ivtmp);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        size_t m = 16 - dec->ivpos;
+        size_t cp = m > len ? len : m;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    int outlen = len + 16;
+    unsigned char *out = malloc(outlen);
+    EVP_DecryptUpdate(dec->ctx, out, &outlen, buf, len);
+    ssize_t wlen = dec->write(out, 1, outlen, dec->stream);
+    SHA256_Update(&dec->sha256, out, wlen);
+    free(out);
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init) {
+        void *out = malloc(128);
+        int len = 0;
+        EVP_DecryptFinal_ex(dec->ctx, out, &len);
+        dec->write(out, 1, len, dec->stream);
+        SHA256_Update(&dec->sha256, out, len);
+        free(out);
+        //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+        EVP_CIPHER_CTX_free(dec->ctx);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    free(dec);
+}
+
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(!RAND_bytes(iv, 16)) {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+    SHA256_Init(&enc->sha256);
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 16;
+    
+    //EVP_CIPHER_CTX_init(&enc->ctx);
+    enc->ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_EncryptInit_ex(enc->ctx, EVP_aes_128_cbc(), NULL, key->data, enc->iv);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_EncryptInit_ex(enc->ctx, EVP_aes_256_cbc(), NULL, key->data, enc->iv);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    if(enc->tmp) {
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    void *in = malloc(len);
+    size_t in_len = enc->read(in, 1, len, enc->stream);
+    
+    SHA256_Update(&enc->sha256, in, in_len);
+    
+    unsigned char *out = NULL;
+    int outlen = 0;
+    size_t ivl = enc->ivlen;
+    if(in_len != 0) {
+        outlen = len + 32;
+        out = malloc(outlen + ivl);
+        if(ivl > 0) {
+            memcpy(out, enc->iv, ivl);
+        }
+        EVP_EncryptUpdate(enc->ctx, out + ivl, &outlen, in, in_len);
+        // I think we don't need this
+        /*
+        if(in_len != len) {
+            int newoutlen = 16;
+            EVP_EncryptFinal_ex(enc->ctx, out + ivl + outlen, &newoutlen);
+            outlen += newoutlen;
+            enc->end = 1;
+        }
+        */
+    } else {
+        out = malloc(16);
+        EVP_EncryptFinal_ex(enc->ctx, out, &outlen);
+        enc->end = 1;
+    }
+    enc->tmp = (char*)out;
+    enc->tmplen = outlen + ivl;
+    enc->tmpoff = 0;
+    
+    if(enc->ivlen > 0) {
+        enc->ivlen = 0;
+    }
+    
+    free(in);
+    
+    return aes_read(buf, s, n, enc);
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    if(enc->tmp) {
+        free(enc->tmp);
+    }
+    if(enc->iv) {
+        free(enc->iv);
+    }
+    //EVP_CIPHER_CTX_cleanup(&enc->ctx);
+    EVP_CIPHER_CTX_free(enc->ctx);
+    free(enc);
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 16;
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    unsigned char iv[16];
+    if(!RAND_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    //EVP_CIPHER_CTX ctx;
+    //EVP_CIPHER_CTX_init(&ctx);
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_EncryptInit_ex(
+                ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                (unsigned char*)key->data,
+                iv);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_EncryptInit_ex(
+                ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                (unsigned char*)key->data,
+                iv);
+    } else {
+        //EVP_CIPHER_CTX_cleanup(&ctx);
+        EVP_CIPHER_CTX_free(ctx);
+        return NULL;
+    }
+    
+    //int len = strlen(in);
+    int buflen = len + 64;
+    unsigned char *buf = calloc(1, buflen);
+    memcpy(buf, iv, 16);
+    
+    int l = buflen - 16;
+    EVP_EncryptUpdate(ctx, buf + 16, &l, (unsigned char*)in, len);
+    
+    int f = 0;
+    EVP_EncryptFinal_ex(ctx, buf + 16 + l, &f);
+    char *out = util_base64encode((char*)buf, 16 + l + f);
+    free(buf);
+    EVP_CIPHER_CTX_free(ctx);
+    //EVP_CIPHER_CTX_cleanup(&ctx);
+    
+    return out;
+}
+
+char* aes_decrypt(const char *in, size_t *length, DavKey *key) {
+    int len;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &len);
+    
+    //EVP_CIPHER_CTX ctx;
+    //EVP_CIPHER_CTX_init(&ctx);
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if(key->type == DAV_KEY_AES128) {
+        EVP_DecryptInit_ex(
+                ctx,
+                EVP_aes_128_cbc(),
+                NULL,
+                key->data,
+                buf);
+    } else if(key->type == DAV_KEY_AES256) {
+        EVP_DecryptInit_ex(
+                ctx,
+                EVP_aes_256_cbc(),
+                NULL,
+                key->data,
+                buf);
+    } else {
+        //EVP_CIPHER_CTX_cleanup(&ctx);
+        EVP_CIPHER_CTX_free(ctx);
+        return NULL;
+    }
+    
+    unsigned char *out = malloc(len + 1);
+    int outlen = len;
+    unsigned char *in_buf = buf + 16;
+    int inlen = len - 16;
+    int f = 0; 
+    
+    EVP_DecryptUpdate(ctx, out, &outlen, in_buf, inlen);
+    EVP_DecryptFinal_ex(ctx, out + outlen, &f);
+    out[outlen + f] = '\0';
+    free(buf);
+    //EVP_CIPHER_CTX_cleanup(&ctx);
+    EVP_CIPHER_CTX_free(ctx);
+    
+    *length = outlen + f;
+    return (char*)out;
+}
+
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf){
+    SHA256_Final((unsigned char*)buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    DAV_SHA_CTX ctx;
+    SHA256_Init(&ctx);
+    SHA256_Update(&ctx, data, len);
+    SHA256_Final(hash, &ctx);
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    SHA256_Init(ctx);
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    SHA256_Final(buf, ctx);
+    free(ctx);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int crypto_pw2key_error = 0;
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!crypto_pw2key_error) {
+        fprintf(stderr, "Error: password key derivation not supported on this platform: openssl to old\n");
+        crypto_pw2key_error = 1;
+    }
+    return 0;
+}
+
+#else
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[32];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    // generate key
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: {
+            PKCS5_PBKDF2_HMAC(
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    EVP_sha256(),
+                    keylen,
+                    keydata);
+            break;
+        }
+        case DAV_PWFUNC_PBKDF2_SHA512: {
+            PKCS5_PBKDF2_HMAC(
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    EVP_sha512(),
+                    keylen,
+                    keydata);
+            break;
+        }
+        default: return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+#endif
+
+#endif
+
+
+/* -------------------- Apple Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_COMMON_CRYPTO
+
+#define RANDOM_BUFFER_LENGTH 256
+static char randbuf[RANDOM_BUFFER_LENGTH];
+static int rbufpos = RANDOM_BUFFER_LENGTH;
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    if(len + rbufpos > RANDOM_BUFFER_LENGTH) {
+        int devr = open("/dev/urandom", O_RDONLY);
+        if(devr == -1) {
+            return 1;
+        }
+        
+        if(read(devr, randbuf, RANDOM_BUFFER_LENGTH) < RANDOM_BUFFER_LENGTH) {
+            close(devr);
+            return 1;
+        }
+        
+        rbufpos = 0;
+        if(len > RANDOM_BUFFER_LENGTH) {
+            int err = 0;
+            if(read(devr, buf, len) < len) {
+                err = 1;
+            }
+            close(devr);
+            return err;
+        }
+        
+        close(devr);
+    }
+    
+    char *r = randbuf;
+    memcpy(buf, r + rbufpos, len);
+    rbufpos += len;
+    
+    return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    CC_SHA256_Init(&dec->sha256);
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+    
+    return dec;
+}
+
+
+void aes_decrypter_init(AESDecrypter *dec) {
+    //EVP_CIPHER_CTX_init(&dec->ctx);
+    dec->init = 1;
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(dec->key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+    } else if(dec->key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+    } else {
+        fprintf(stderr, "unknown key type\n");
+        exit(-1);
+    }
+    dec->ctx = cryptor;
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        size_t n = 16 - dec->ivpos;
+        size_t cp = n > len ? len : n;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    int outlen = len + 16;
+    unsigned char *out = malloc(outlen);
+    
+    CCCryptorStatus status;
+    size_t avail = outlen;
+    size_t moved = 0;
+    status = CCCryptorUpdate(dec->ctx, buf, len, out, avail, &moved);
+    
+    ssize_t wlen = dec->write(out, 1, moved, dec->stream);
+    CC_SHA256_Update(&dec->sha256, out, wlen);
+    free(out);
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init) {
+        void *out = malloc(128);
+        size_t len = 0;
+        //EVP_DecryptFinal_ex(dec->ctx, out, &len);
+        CCCryptorFinal(dec->ctx, out, 128, &len);
+        
+        
+        dec->write(out, 1, len, dec->stream);
+        CC_SHA256_Update(&dec->sha256, out, len);
+        free(out);
+        //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+        //EVP_CIPHER_CTX_free(dec->ctx);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+    enc->ctx = cryptor;
+    CC_SHA256_Init(&enc->sha256);
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 16;
+    
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    if(enc->tmp) {
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    void *in = malloc(len);
+    size_t in_len = enc->read(in, 1, len, enc->stream);
+    
+    CC_SHA256_Update(&enc->sha256, in, in_len);
+    
+    unsigned char *out = NULL;
+    size_t outlen = 0;
+    size_t ivl = enc->ivlen;
+    if(in_len != 0) {
+        outlen = len + 32;
+        out = malloc(outlen + ivl);
+        if(ivl > 0) {
+            memcpy(out, enc->iv, ivl);
+        }
+        
+        CCCryptorStatus status;
+        size_t avail = outlen;
+        status = CCCryptorUpdate(enc->ctx, in, in_len, out + ivl, avail, &outlen);
+        // TODO: check if this still works
+        /*
+        if(in_len != len) {
+            size_t newoutlen = 16;
+            status = CCCryptorFinal(enc->ctx, out + ivl + outlen, 16, &newoutlen);
+            outlen += newoutlen;
+            enc->end = 1;
+        }
+        */
+    } else {
+        out = malloc(32);
+        CCCryptorStatus status;
+        size_t avail = outlen;
+        status = CCCryptorFinal(enc->ctx, out, 32, &outlen);
+        enc->end = 1;
+    }
+    enc->tmp = (char*)out;
+    enc->tmplen = outlen + ivl;
+    enc->tmpoff = 0;
+    
+    if(enc->ivlen > 0) {
+        enc->ivlen = 0;
+    }
+    
+    free(in);
+    
+    return aes_read(buf, s, n, enc);
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 16;
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    if(enc->tmp) {
+        free(enc->tmp);
+    }
+    if(enc->iv) {
+        free(enc->iv);
+    }
+    // TODO: cleanup cryptor
+    free(enc);
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    unsigned char iv[16];
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+    } else {
+        return NULL;
+    }
+    
+    if(status != kCCSuccess) {
+        return NULL;
+    }
+    
+    int buflen = len + 64;
+    char *buf = calloc(1, buflen);
+    memcpy(buf, iv, 16);
+    
+    int pos = 16;
+    size_t avail = buflen - 16;
+    size_t moved;
+    char *out = buf + 16;
+    
+    status = CCCryptorUpdate(cryptor, in,
+         len, out, avail,
+         &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    pos += moved;
+    avail -= moved;
+    out += moved;
+    
+    status = CCCryptorFinal(cryptor, out, avail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    pos += moved;
+    
+    char *b64enc = util_base64encode(buf, pos);
+    free(buf);
+    
+    return b64enc;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+    int inlen;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+    
+    CCCryptorRef cryptor;
+    CCCryptorStatus status;
+    if(key->type == DAV_KEY_AES128) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+    } else if(key->type == DAV_KEY_AES256) {
+        status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+    } else {
+        free(buf);
+        return NULL;
+    }
+    
+    if(status != kCCSuccess) {
+        free(buf);
+        return NULL;
+    }
+    
+    char *out = malloc(inlen + 1);
+    size_t outavail = inlen;
+    size_t outlen = 0;
+    
+    unsigned char *inbuf = buf + 16;
+    inlen -= 16;
+    
+    size_t moved = 0;
+    status = CCCryptorUpdate(cryptor, inbuf, inlen, out, outavail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        free(out);
+        // TODO cryptor
+        return NULL;
+    }
+    
+    outlen += moved;
+    outavail -= moved;
+    
+    status = CCCryptorFinal(cryptor, out + outlen, outavail, &moved);
+    if(status != kCCSuccess) {
+        free(buf);
+        free(out);
+        // TODO cryptor
+        return NULL;
+    }
+    
+    outlen += moved;
+    out[outlen] = 0;
+    
+    *len = outlen;
+    return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+    CC_SHA256_Final(buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    CC_SHA256((const unsigned char*)data, len, hash);
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    CC_SHA256_Init(ctx);
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    CC_SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    CC_SHA256_Final(buf, ctx);
+    free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[32];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    // generate key
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: {
+            int result = CCKeyDerivationPBKDF(
+                    kCCPBKDF2,
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    kCCPRFHmacAlgSHA256,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    keydata,
+                    keylen);
+            if(result) {
+                return NULL;
+            }
+            break;
+        }
+        case DAV_PWFUNC_PBKDF2_SHA512: {
+            int result = CCKeyDerivationPBKDF(
+                    kCCPBKDF2,
+                    password,
+                    len,
+                    salt,
+                    saltlen,
+                    kCCPRFHmacAlgSHA512,
+                    DAV_CRYPTO_ITERATION_COUNT,
+                    keydata,
+                    keylen);
+            if(result) {
+                return NULL;
+            }
+            break;
+        }
+        default: return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+
+#endif
+
+/* -------------------- Windows Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_CNG
+
+static void cng_cleanup(BCRYPT_ALG_HANDLE hAesAlg, BCRYPT_KEY_HANDLE hKey, BCRYPT_HASH_HANDLE hHash, void *pbObject) {
+    if(hAesAlg) {
+        BCryptCloseAlgorithmProvider(hAesAlg,0);
+    }
+    if(hKey) {
+        BCryptDestroyKey(hKey);
+    }
+    if(hHash) {
+        BCryptDestroyHash(hHash);
+    }
+    if(pbObject) {
+        free(pbObject);
+    }
+}
+
+static int cng_init_key(BCRYPT_ALG_HANDLE *alg, BCRYPT_KEY_HANDLE *key, void **keyobj, DavKey *aesKey) {
+    BCRYPT_ALG_HANDLE hAesAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey    = NULL;
+    
+    void *pbKeyObject     = NULL;
+    ULONG keyObjectLength = 0;
+    
+    ULONG result = 0;
+    
+    // check DavKey and get AES key length
+    if(!aesKey) {
+        return 1;
+    }
+    
+    ULONG aesKeyLength = 0;
+    if(aesKey->type == DAV_KEY_AES128) {
+        aesKeyLength = 16;
+    } else if(aesKey->type == DAV_KEY_AES256) {
+        aesKeyLength = 32;
+    }
+    if(aesKeyLength > aesKey->length || !aesKey->data) {
+        // invalid DavKey
+        return 1;
+    }
+    
+    // initialize BCrypt stuff
+    if(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0)) {
+        fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+        return 1;
+    }
+    
+    if(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjectLength, sizeof(DWORD), &result, 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot get BCRYPT_OBJECT_LENGTH\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    if(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot set CBC mode\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    pbKeyObject = calloc(1, keyObjectLength);
+    if(!pbKeyObject) {
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    // init key
+    if(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, keyObjectLength, aesKey->data, aesKeyLength, 0)) {
+        fprintf(stderr, "Error: BCrypt: Cannot set key\n");
+        cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+        return 1;
+    }
+    
+    *alg = hAesAlg;
+    *key = hKey;
+    *keyobj = pbKeyObject;
+    
+    return 0;
+}
+
+static int cng_hash_init(WinBCryptSHACTX *ctx) {
+    if(BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)) {
+        fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+        return 1;
+    }
+    
+    ULONG hashObjectLen;
+    ULONG result;
+    if(BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjectLen, sizeof(DWORD), &result, 0)) {
+        cng_cleanup(ctx->hAlg, NULL, NULL, NULL);
+        return 1;
+    }
+    
+    ctx->pbHashObject = calloc(1, hashObjectLen);
+    
+    if(BCryptCreateHash(ctx->hAlg, &ctx->hHash, ctx->pbHashObject, hashObjectLen, NULL, 0, 0)) {
+        cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+        return 1;
+    }
+    
+    return 0;
+}
+
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+    if(BCryptGenRandom(NULL, (unsigned char*)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
+        return 1;
+    }
+    return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+    AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+    if(!dec) {
+        return NULL;
+    }
+    if(cng_hash_init(&dec->sha256)) {
+        free(dec);
+        return NULL;
+    }
+    
+    dec->stream = stream;
+    dec->write = write_func;
+    dec->key = key;
+    dec->init = 0;
+    dec->ivpos = 0;
+      
+    return dec;
+}
+
+static void aes_decrypter_init(AESDecrypter *dec) {
+    if(cng_init_key(&dec->ctx.hAlg, &dec->ctx.hKey, &dec->ctx.pbKeyObject, dec->key)) {
+        fprintf(stderr, "Error: cng_init_key failed\n");
+        exit(-1);
+    }
+    // copy iv
+    memcpy(dec->ctx.pbIV, dec->ivtmp, 16);
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+    int len = s*n;
+    if(!dec->init) {
+        dec->init = 1;
+        
+        size_t n = 16 - dec->ivpos;
+        size_t cp = n > len ? len : n;
+        memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+        dec->ivpos += cp;
+        if(dec->ivpos >= 16) {
+            aes_decrypter_init(dec);
+        }
+        if(len == cp) {
+            return len;
+        } else {
+            buf = (char*)buf + cp;
+            len -= cp;
+        }
+    }
+    
+    // the cipher text must be a multiply of 16
+    // remaining bytes are stored in ctx.buf and must be added to cibuf
+    // the next time
+    size_t cbufalloc = len + 64;
+    ULONG clen = 0;
+    char *cbuf = malloc(cbufalloc);
+    
+    // add previous remaining bytes
+    if(dec->ctx.buflen > 0) {
+        memcpy(cbuf, dec->ctx.buf, dec->ctx.buflen);
+        clen = dec->ctx.buflen;
+    }
+    // add current bytes
+    memcpy(cbuf + clen, buf, len);
+    clen += len;
+    
+    // check if the message fits the blocksize
+    int remaining = clen % 16;
+    if(remaining == 0) {
+        // decrypt last block next time, or in aes_decrypter_shutdown
+        // this makes sure, that shutdown always decrypts the last block
+        // with BCRYPT_BLOCK_PADDING flag
+        remaining = 16;
+    }
+    
+    // add remaining bytes to ctx.buf for the next aes_write run
+    clen -= remaining;
+    memcpy(dec->ctx.buf, cbuf + clen, remaining);
+    dec->ctx.buflen = remaining;
+    
+    // ready to decrypt the message
+    ULONG outlen = clen + 32;
+       
+    // decrypt
+    if(clen > 0) {
+        unsigned char* out = malloc(outlen);
+
+        ULONG enc_len = 0;
+        ULONG status = BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &enc_len, 0);
+        if(status > 0) {
+            fprintf(stderr, "Error: BCryptDecrypt failed: 0x%X\n", status);
+            free(out);
+            free(cbuf);
+            return 0;
+        }      
+        outlen = enc_len;
+
+        // write decrypted data to the output stream and update the hash
+        dec->write(out, 1, outlen, dec->stream);
+        BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+
+        free(out);
+    }
+    
+    free(cbuf);
+    
+    return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+    if(dec->init && dec->ctx.buflen > 0) { 
+        ULONG outlen = 64;
+        char out[64];
+        if(BCryptDecrypt(dec->ctx.hKey, dec->ctx.buf, dec->ctx.buflen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+            fprintf(stderr, "Error: BCryptDecrypt failed\n");
+            return;
+        }
+        dec->write(out, 1, outlen, dec->stream);
+        BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+    }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+    cng_cleanup(dec->ctx.hAlg, dec->ctx.hKey, NULL, dec->ctx.pbKeyObject);
+    cng_cleanup(dec->sha256.hAlg, NULL, dec->sha256.hHash, dec->sha256.pbHashObject);
+    free(dec);
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    unsigned char *iv = malloc(16);
+    if(dav_rand_bytes(iv, 16)) {
+        free(iv);
+        return NULL;
+    }
+    
+    AESEncrypter *enc = calloc(1, sizeof(AESEncrypter));
+    if(cng_hash_init(&enc->sha256)) {
+        free(iv);
+        free(enc);
+        return NULL;
+    }
+    
+    enc->stream = stream;
+    enc->read = read_func;
+    enc->seek = seek_func;
+    enc->tmp = NULL;
+    enc->tmplen = 0;
+    enc->tmpoff = 0;
+    enc->end = 0;
+    enc->iv = iv;
+    enc->ivlen = 0;
+    
+    if(cng_init_key(&enc->ctx.hAlg, &enc->ctx.hKey, &enc->ctx.pbKeyObject, key)) {
+        fprintf(stderr, "Error: cng_init_key failed\n");
+        exit(-1);
+    }
+    
+    enc->ctx.buflen = 0;
+    memcpy(enc->ctx.pbIV, iv, 16);
+    
+    return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+    size_t len = s*n;
+    size_t nread = 0; 
+    
+    if(enc->tmp) {
+        // the temp buffer contains bytes that are already encrypted, but
+        // the last aes_read had not enough read buffer space
+        
+        // in case we have a tmp buf, we just return this 
+        size_t tmp_diff = enc->tmplen - enc->tmpoff;
+        size_t cp_len = tmp_diff > len ? len : tmp_diff;
+        memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+        enc->tmpoff += cp_len;
+        if(enc->tmpoff >= enc->tmplen) {
+            free(enc->tmp);
+            enc->tmp = NULL;
+            enc->tmplen = 0;
+            enc->tmpoff = 0;
+        }
+        return cp_len / s;
+    }
+    
+    if(enc->ivlen < 16) {
+        size_t copy_iv_len = 16 - enc->ivlen;
+        copy_iv_len = len > copy_iv_len ? copy_iv_len : len;
+        
+        memcpy(buf, enc->iv, copy_iv_len);
+        (char*)buf += copy_iv_len;
+        len -= copy_iv_len;
+        nread = copy_iv_len;
+        
+        enc->ivlen += copy_iv_len;
+        
+        if(len == 0) {
+            return copy_iv_len / s;
+        }
+    }
+    
+    if(enc->end) {
+        return 0;
+    }
+    
+    size_t remaining = len % 16;
+    len -= remaining;
+    
+    if(len > 256) {
+        len -= 16; // optimization for avoiding tmp buffer usage
+    }
+    
+    size_t inalloc = len;
+    ULONG  inlen = 0;
+    unsigned char *in = malloc(inalloc);
+    
+    // fill the input buffer
+    while(inlen < inalloc) {
+        size_t r = enc->read(in + inlen, 1, inalloc - inlen, enc->stream);
+        if(r == 0) {
+            enc->end = 1;
+            break;
+        }
+        inlen += r;
+    }
+    
+    if(inlen == 0) {
+        return nread / s;
+    }
+    
+    // hash read data
+    BCryptHashData(enc->sha256.hHash, in, inlen, 0);
+    
+    // create output buffer
+    ULONG outalloc = inlen + 16;
+    ULONG outlen = 0;
+    char *out = malloc(outalloc);
+    
+    // encrypt
+    int flags = 0;
+    if(inlen % 16 != 0) {
+        enc->end = 1;
+    }
+    if(enc->end) {
+        flags = BCRYPT_BLOCK_PADDING;
+    }
+    if(BCryptEncrypt(enc->ctx.hKey, in, inlen, NULL, enc->ctx.pbIV, 16, out, outalloc, &outlen, flags)) {
+        fprintf(stderr, "Error: BCryptEncrypt failed\n");
+    }
+    
+    // check if the output fits in buf, if not, save the remaining bytes in tmp
+    if(outlen > len) {
+        size_t tmplen = outlen - len;
+        char *tmp = malloc(tmplen);
+        memcpy(tmp, out+len, tmplen);
+        
+        enc->tmp = tmp;
+        enc->tmplen = tmplen;
+        enc->tmpoff = 0;
+        
+        outlen = len;
+    }
+    
+    // fill read buffer and return
+    memcpy(buf, out, outlen);
+    nread += outlen;
+    
+    free(in);
+    free(out);
+    
+    return nread / s;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+    enc->end = 1;
+}
+
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin) {
+    if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+        return CURL_SEEKFUNC_CANTSEEK;
+    }
+    
+    enc->ivlen = 0;
+    memcpy(enc->ctx.pbIV, enc->iv, 16);
+    if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+        return CURL_SEEKFUNC_FAIL;
+    }
+    return CURL_SEEKFUNC_OK;
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+    // create random IV
+    char iv[16];
+    if(dav_rand_bytes(iv, 16)) {
+        return NULL;
+    }
+    
+    // initialize bcrypt stuff
+    BCRYPT_ALG_HANDLE hAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey = NULL;
+    void *pbKeyObject = NULL;
+    if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+        return NULL;
+    }
+    
+    // create output buffer
+    ULONG outlen = len + 128;
+    char *out = malloc(outlen);
+    
+    // the output must start with the IV
+    memcpy(out, iv, 16);
+    char *encbuf = out + 16;
+    ULONG enclen = outlen - 16;
+    ULONG encoutlen = 0;
+    
+    // encrypt
+    if(BCryptEncrypt(hKey, (PUCHAR)in, len, NULL, (PUCHAR)iv, 16, encbuf, enclen, &encoutlen, BCRYPT_BLOCK_PADDING)) {
+        fprintf(stderr, "Error: BCryptEncrypt failed\n");
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        free(out);
+        return NULL;
+    }
+    
+    outlen = encoutlen + 16; // length of encrypted data + 16 bytes IV
+    
+    // base64 encode
+    char *outstr = util_base64encode(out, outlen);
+    
+    cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+    free(out);
+    
+    return outstr;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+    BCRYPT_ALG_HANDLE hAlg = NULL;
+    BCRYPT_KEY_HANDLE hKey = NULL;
+    void *pbKeyObject = NULL;
+    if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+        return NULL;
+    }
+    
+    int inlen;
+    unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+    if(inlen < 16 || !buf) {
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        if(buf) {
+            free(buf);
+        }
+        return NULL;
+    }
+    
+    // encrypted data starts with IV
+    char iv[16];
+    memcpy(iv, buf, 16);
+    
+    // decrypt data
+    char *data = buf + 16; // encrypted data starts after IV
+    size_t datalen = inlen - 16;
+    
+    // create output buffer
+    ULONG outlen = inlen;
+    char *out = malloc(outlen + 1);
+    
+    // decrypt
+    if(BCryptDecrypt(hKey, data, datalen, NULL, iv, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+        cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+        free(out);
+        free(buf);
+        return NULL;
+    }
+    
+    // decrypt finished, return
+    out[outlen] = 0;
+    *len = (size_t)outlen;
+    return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+    BCryptFinishHash(sha256->hHash, buf, DAV_SHA256_DIGEST_LENGTH, 0);
+}
+
+
+char* dav_create_hash(const char *data, size_t len) {
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    DAV_SHA_CTX *ctx = dav_hash_init();
+    if(ctx) {
+        dav_hash_update(ctx, data, len);
+        dav_hash_final(ctx, hash);
+    }
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+    DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+    if(!ctx) {
+        return NULL;
+    }
+    if(cng_hash_init(ctx)) {
+        free(ctx);
+        return NULL;
+    }
+    return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+    BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0);
+    
+    // cleanup
+    cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+    free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+    if(!password) {
+        return NULL;
+    }
+    size_t len = strlen(password);
+    if(len == 0) {
+        return NULL;
+    }
+    
+    // setup key data and length
+    unsigned char keydata[128];
+    int keylen = 32;
+    switch(enc) {
+        case DAV_KEY_AES128: keylen = 16; break;
+        case DAV_KEY_AES256: keylen = 32; break;
+        default: return NULL;
+    }
+    
+    LPCWSTR algid;
+    switch(pwfunc) {
+        case DAV_PWFUNC_PBKDF2_SHA256: algid = BCRYPT_SHA256_ALGORITHM; break;
+        case DAV_PWFUNC_PBKDF2_SHA512: algid = BCRYPT_SHA512_ALGORITHM; break;
+        default: return NULL;
+    }
+    
+    // open algorithm provider
+    BCRYPT_ALG_HANDLE hAlg;
+    ULONG status = BCryptOpenAlgorithmProvider(&hAlg, algid, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
+    if(status > 0) {
+        fprintf(stderr, "Error: dav_pw2key: BCryptOpenAlgorithmProvider failed: 0x%X\n", (unsigned int)status);
+        return NULL;
+    }
+    
+    // derive key
+    status =  BCryptDeriveKeyPBKDF2(
+            hAlg,
+            (PUCHAR)password,
+            len,
+            (PUCHAR)salt,
+            saltlen,
+            DAV_CRYPTO_ITERATION_COUNT,
+            keydata,
+            128,
+            0);
+    
+    BCryptCloseAlgorithmProvider(hAlg,0);
+    
+    if(status) {
+        fprintf(stderr, "Error: dav_pw2key: BCryptDeriveKeyPBKDF2 failed: 0x%X\n", (unsigned int)status);
+        return NULL;
+    }
+    
+    // create DavKey with generated data
+    DavKey *key = malloc(sizeof(DavKey));
+    key->data = malloc(keylen);
+    key->length = keylen;
+    key->name = NULL;
+    key->type = enc;
+    memcpy(key->data, keydata, keylen);
+    return key;
+}
+#endif
+
+
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key) {
+    CxBuffer *encbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!encbuf) {
+        return NULL;
+    }
+    
+    AESEncrypter *enc = aes_encrypter_new(
+            key,
+            in,
+            (dav_read_func)cxBufferRead,
+            NULL);
+    if(!enc) {
+        cxBufferFree(encbuf);
+        return NULL;
+    }
+    
+    char buf[1024];
+    size_t r;
+    while((r = aes_read(buf, 1, 1024, enc)) > 0) {
+        cxBufferWrite(buf, 1, r, encbuf);
+    }
+    aes_encrypter_close(enc);
+    
+    encbuf->pos = 0;
+    return encbuf;
+}
+
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key) {
+    CxBuffer *decbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!decbuf) {
+        return NULL;
+    }
+    AESDecrypter *dec = aes_decrypter_new(
+            key,
+            decbuf,
+            (dav_write_func)cxBufferWrite);
+    if(!dec) {
+        cxBufferFree(decbuf);
+        return NULL;
+    }
+    
+    aes_write(in->space, 1, in->size, dec);
+    aes_decrypter_shutdown(dec);
+    aes_decrypter_close(dec);
+    decbuf->pos = 0;
+    return decbuf;
+}
diff --git a/libidav/crypto.h b/libidav/crypto.h
new file mode 100644 (file)
index 0000000..b058488
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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 DAV_CRYPTO_H
+#define        DAV_CRYPTO_H
+
+#include "webdav.h"
+#include <cx/string.h>
+
+#ifdef __APPLE__
+/* macos */
+
+#define DAV_CRYPTO_COMMON_CRYPTO
+
+#define DAV_AES_CTX              CCCryptorRef
+#define DAV_SHA_CTX              CC_SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <CommonCrypto/CommonCrypto.h>
+#include <CommonCrypto/CommonDigest.h>
+
+#elif defined(_WIN32)
+
+#define DAV_CRYPTO_CNG
+
+#include <windows.h>
+#include <bcrypt.h>
+
+typedef struct WinBCryptCTX {
+    BCRYPT_ALG_HANDLE hAlg;
+    BCRYPT_KEY_HANDLE hKey;
+    void              *pbKeyObject;
+    unsigned char     pbIV[16];
+    
+    unsigned char     buf[16];
+    ULONG             buflen;
+} WinBCryptCTX;
+
+typedef struct WinBCryptSHACTX {
+    BCRYPT_ALG_HANDLE  hAlg;
+    BCRYPT_HASH_HANDLE hHash;    
+    void               *pbHashObject;
+} WinBCryptSHACTX;
+
+#define DAV_AES_CTX              WinBCryptCTX
+#define DAV_SHA_CTX              WinBCryptSHACTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#else
+/* unix/linux */
+
+#define DAV_USE_OPENSSL
+
+#define DAV_AES_CTX              EVP_CIPHER_CTX*
+#define DAV_SHA_CTX              SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#if defined(__sun) && defined(__SunOS_5_10)
+#include <sha2.h>
+#define SHA256_Init     SHA256Init
+#define SHA256_Update   SHA256Update
+#define SHA256_Final    SHA256Final
+#else
+#include <openssl/sha.h>
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DAV_PWFUNC_PBKDF2_SHA256 0
+#define DAV_PWFUNC_PBKDF2_SHA512 1
+    
+#define DAV_CRYPTO_ITERATION_COUNT 4000
+    
+typedef struct {
+    DAV_AES_CTX    ctx;
+    DAV_SHA_CTX    sha256;
+    void           *stream;
+    dav_write_func write;
+    DavKey         *key;
+    int            init;
+    unsigned char  ivtmp[16];
+    size_t         ivpos;
+} AESDecrypter;
+
+typedef struct {
+    DAV_AES_CTX    ctx;
+    DAV_SHA_CTX    sha256;
+    void           *iv;
+    size_t         ivlen;
+    void           *stream;
+    dav_read_func  read;
+    dav_seek_func  seek;
+    char           *tmp;
+    size_t         tmplen;
+    size_t         tmpoff;
+    int            end;
+} AESEncrypter;
+
+typedef struct DavHashContext DavHashContext;
+
+int dav_rand_bytes(unsigned char *buf, size_t len);
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func);
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec);
+void aes_decrypter_shutdown(AESDecrypter *dec);
+void aes_decrypter_close(AESDecrypter *dec);
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc);
+void aes_encrypter_close(AESEncrypter *enc);
+int aes_encrypter_reset(AESEncrypter  *enc, curl_off_t offset, int origin);
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key);
+char* aes_decrypt(const char *in, size_t *len, DavKey *key);
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf);
+
+char* dav_create_hash(const char *data, size_t len);
+
+DAV_SHA_CTX* dav_hash_init(void);
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf);
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc);
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key);
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_CRYPTO_H */
+
diff --git a/libidav/davqlexec.c b/libidav/davqlexec.c
new file mode 100644 (file)
index 0000000..6811ded
--- /dev/null
@@ -0,0 +1,1485 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <cx/utils.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.h>
+
+#include "davqlexec.h"
+#include "utils.h"
+#include "methods.h"
+#include "session.h"
+#include "resource.h"
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap) {
+    DavQLArgList *args = malloc(sizeof(DavQLArgList));
+    if(!args) {
+        return NULL;
+    }
+    args->first = NULL;
+    
+    if(!st->args) {
+        args->first = NULL;
+        args->current = NULL;
+        return args;
+    }
+    
+    DavQLArg *cur = NULL;
+    CxIterator i = cxListIterator(st->args);
+    cx_foreach(void*, data, i) {
+        intptr_t type = (intptr_t)data;
+        DavQLArg *arg = calloc(1, sizeof(DavQLArg));
+        if(!arg) {
+            dav_ql_free_arglist(args);
+            return NULL;
+        }
+        arg->type = type;
+        switch(type) {
+            case 'd': {
+                arg->value.d = va_arg(ap, int);
+                break;
+            }
+            case 'u': {
+                arg->value.u = va_arg(ap, unsigned int);
+                break;
+            }
+            case 's': {
+                arg->value.s = va_arg(ap, char*);
+                break;
+            }
+            case 't': {
+                arg->value.t = va_arg(ap, time_t);
+                break;
+            }
+            default: {
+                free(arg);
+                dav_ql_free_arglist(args);
+                return NULL;
+            }
+        }
+        if(cur) {
+            cur->next = arg;
+        } else {
+            args->first = arg;
+        }
+        cur = arg;
+    }
+    args->current = args->first;
+    return args;
+}
+
+void dav_ql_free_arglist(DavQLArgList *args) {
+    DavQLArg *arg = args->first;
+    while(arg) {
+        DavQLArg *next = arg->next;
+        free(arg);
+        arg = next;
+    }
+    free(args);
+}
+
+static DavQLArg* arglist_get(DavQLArgList *args) {
+    DavQLArg *a = args->current;
+    if(a) {
+        args->current = a->next;
+    }
+    return a;
+}
+
+int dav_ql_getarg_int(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 'd') {
+        return a->value.d;
+    }
+    return 0;
+}
+
+unsigned int dav_ql_getarg_uint(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 'u') {
+        return a->value.u;
+    }
+    return 0;
+}
+
+char* dav_ql_getarg_str(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 's') {
+        return a->value.s;
+    }
+    return "";
+}
+
+time_t dav_ql_getarg_time(DavQLArgList *args) {
+    DavQLArg *a = arglist_get(args);
+    if(a && a->type == 't') {
+        return a->value.t;
+    }
+    return 0;
+}
+
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...) {
+    va_list ap;
+    va_start(ap, st);
+    DavResult result = dav_statement_execv(sn, st, ap);
+    va_end(ap);
+    return result;
+}
+
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap) {
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    // make sure the statement was successfully parsed
+    if(st->type == DAVQL_ERROR) {
+        return result;
+    }
+    
+    if(st->type == DAVQL_SELECT) {
+        return dav_exec_select(sn, st, ap);
+    } else {
+        // TODO
+    }
+    
+    return result;
+}
+
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, a, CX_BUFFER_AUTO_EXTEND);
+    
+    int placeholder = 0;
+    for(int i=0;i<fstr.length;i++) {
+        char c = fstr.ptr[i];
+        if(placeholder) {
+            if(c == '%') {
+                // no placeholder, %% transposes to %
+                cxBufferPut(&buf, c);
+            } else {
+                // detect placeholder type and insert arg
+                int err = 0;
+                switch(c) {
+                    case 's': {
+                        char *arg = dav_ql_getarg_str(ap);
+                        cxBufferPutString(&buf, arg);
+                        break;
+                    }
+                    case 'd': {
+                        int arg = dav_ql_getarg_int(ap);
+                        cx_bprintf(&buf, "%d", arg);
+                        break;
+                    }
+                    case 'u': {
+                        unsigned int arg = dav_ql_getarg_uint(ap);
+                        cx_bprintf(&buf, "%u", arg);
+                        break;
+                    }
+                    case 't': {
+                        // time arguments not supported for strings
+                        err = 1;
+                        break;
+                    }
+                    default: {
+                        *error = DAVQL_UNKNOWN_FORMATCHAR;
+                        err = 1;
+                    }
+                }
+                if(err) {
+                    cxBufferDestroy(&buf);
+                    return (cxmutstr){NULL,0};
+                }
+            }
+            placeholder = 0;
+        } else {
+            if(c == '%') {
+                placeholder = 1;
+            } else {
+                cxBufferPut(&buf, c);
+            }
+        }
+    }
+    if(cxBufferPut(&buf, '\0')) {
+        *error = DAVQL_OOM;
+        cxBufferDestroy(&buf);
+        return (cxmutstr){NULL, 0};
+    }
+    *error = DAVQL_OK;
+    
+    return cx_mutstrn(buf.space, buf.size-1);
+}
+
+static int fl_add_properties(DavSession *sn, const CxAllocator *a, CxMap *map, DavQLExpression *expression) {
+    if(!expression) {
+        return 0;
+    }
+    
+    if(expression->type == DAVQL_IDENTIFIER) {
+        DavProperty *property = cxMalloc(a, sizeof(DavProperty));
+
+        char *name;
+        DavNamespace *ns = dav_get_property_namespace(
+                sn->context,
+                cx_strdup_a(a, expression->srctext).ptr,
+                &name);
+        if(!ns) {
+            return -1;
+        }
+        
+        property->ns = ns;
+        property->name = name;
+        property->value = NULL;
+        
+        cxMapPut(map, cx_hash_key(expression->srctext.ptr, expression->srctext.length), property);
+    }
+    
+    if(expression->left) {
+        if(fl_add_properties(sn, a, map, expression->left)) {
+            return -1;
+        }
+    }
+    if(expression->right) {
+        if(fl_add_properties(sn, a, map, expression->right)) {
+            return -1;
+        }
+    }
+    
+    return 0;
+}
+
+static CxBuffer* fieldlist2propfindrequest(DavSession *sn, const CxAllocator *a, CxList *fields, int *isallprop) {
+    CxMap *properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    *isallprop = 0;
+    
+    CxIterator i = cxListIterator(fields);
+    cx_foreach(DavQLField*, field, i) {
+        if(!cx_strcmp(field->name, CX_STR("*"))) {
+            cxMapDestroy(properties);
+            *isallprop = 1;
+            return create_allprop_propfind_request();
+        } else if(!cx_strcmp(field->name, CX_STR("-"))) {
+            cxMapDestroy(properties);
+            return create_propfind_request(sn, NULL, "propfind", 0);
+        } else {
+            if(fl_add_properties(sn, a, properties, field->expr)) {
+                // TODO: set error
+                cxMapDestroy(properties);
+                return NULL;
+            }
+        }
+    }
+    
+    i = cxMapIteratorValues(properties);
+    CxList *list = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    cx_foreach(DavProperty*, value, i) {
+        cxListAdd(list, value);
+    }
+    
+    CxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0);
+    cxListDestroy(list);
+    cxMapDestroy(properties);
+    return reqbuf;
+}
+
+static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, CxList *fields) {
+    CxMap *new_properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
+    DavResourceData *data = (DavResourceData*)res->data;
+    
+    // add basic properties
+    void *value;
+    
+    cxmutstr cl_keystr = dav_property_key("DAV:", "getcontentlength");
+    CxHashKey cl_key = cx_hash_key(cl_keystr.ptr, cl_keystr.length);
+    value = cxMapGet(data->properties, cl_key);
+    if(value) {
+        cxMapPut(new_properties, cl_key, value);
+    }
+    
+    cxmutstr cd_keystr = dav_property_key("DAV:", "creationdate");
+    CxHashKey cd_key = cx_hash_key(cd_keystr.ptr, cd_keystr.length);
+    value = cxMapGet(data->properties, cd_key);
+    if(value) {
+        cxMapPut(new_properties, cd_key, value);
+    }
+    
+    cxmutstr lm_keystr = dav_property_key("DAV:", "getlastmodified");
+    CxHashKey lm_key = cx_hash_key(lm_keystr.ptr, lm_keystr.length);
+    value = cxMapGet(data->properties, lm_key);
+    if(value) {
+        cxMapPut(new_properties, lm_key, value);
+    }
+    
+    cxmutstr ct_keystr = dav_property_key("DAV:", "getcontenttype");
+    CxHashKey ct_key = cx_hash_key(ct_keystr.ptr, ct_keystr.length);
+    value = cxMapGet(data->properties, ct_key);
+    if(value) {
+        cxMapPut(new_properties, ct_key, value);
+    }
+    
+    cxmutstr rt_keystr = dav_property_key("DAV:", "resourcetype");
+    CxHashKey rt_key = cx_hash_key(rt_keystr.ptr, rt_keystr.length);
+    value = cxMapGet(data->properties, rt_key);
+    if(value) {
+        cxMapPut(new_properties, rt_key, value);
+    }
+    
+    cxmutstr cn_keystr = dav_property_key(DAV_NS, "crypto-name");
+    CxHashKey cn_key = cx_hash_key(cn_keystr.ptr, cn_keystr.length);
+    value = cxMapGet(data->properties, cn_key);
+    if(value) {
+        cxMapPut(new_properties, cn_key, value);
+    }
+    
+    cxmutstr ck_keystr = dav_property_key(DAV_NS, "crypto-key");
+    CxHashKey ck_key = cx_hash_key(ck_keystr.ptr, ck_keystr.length);
+    value = cxMapGet(data->properties, ck_key);
+    if(value) {
+        cxMapPut(new_properties, ck_key, value);
+    }
+    
+    cxmutstr ch_keystr = dav_property_key(DAV_NS, "crypto-hash");
+    CxHashKey ch_key = cx_hash_key(ch_keystr.ptr, ch_keystr.length);
+    value = cxMapGet(data->properties, ch_key);
+    if(value) {
+        cxMapPut(new_properties, ch_key, value);
+    }
+    
+    // add properties from field list
+    if(fields) {
+        CxIterator i = cxListIterator(fields);
+        cx_foreach(DavCompiledField*, field, i) {
+            DavQLStackObj field_result;
+            if(!dav_exec_expr(field->code, res, &field_result)) {
+                cxmutstr str;
+                str.ptr = NULL;
+                str.length = 0;
+                DavXmlNode *node = NULL;
+                if(field_result.type == 0) {
+                    str = cx_asprintf_a(
+                            sn->mp->allocator,
+                            "%" PRId64,
+                            field_result.data.integer);
+                } else if(field_result.type == 1) {
+                    if(field_result.data.string) {
+                        str = cx_strdup_a(sn->mp->allocator, cx_strn(
+                                field_result.data.string,
+                                field_result.length));
+                    }
+                } else if(field_result.type == 2) {
+                    node = dav_copy_node(field_result.data.node);
+                } else {
+                    // unknown type
+                    // TODO: error
+                    resource_free_properties(sn, new_properties);
+                    return -1;
+                }
+                if(str.ptr) {
+                    node = dav_session_malloc(sn, sizeof(DavXmlNode));
+                    memset(node, 0, sizeof(DavXmlNode));
+                    node->type = DAV_XML_TEXT;
+                    node->content = str.ptr;
+                    node->contentlength = str.length;
+                }
+                if(node) {
+                    cxmutstr key = dav_property_key(field->ns, field->name);
+
+                    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+                    namespace->prefix = NULL;
+                    namespace->name = dav_session_strdup(sn, field->ns);
+
+                    DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+                    prop->name = dav_session_strdup(sn, field->name);
+                    prop->ns = namespace;
+                    prop->value = node;
+
+                    cxMapPut(new_properties, cx_hash_key(key.ptr, key.length), prop);
+                    free(key.ptr);
+                }
+            } else {
+                // TODO: error
+                resource_free_properties(sn, new_properties);
+                return -1;
+            }
+        }
+    }
+    
+    cxMapRemove(data->properties, cl_key);
+    cxMapRemove(data->properties, cd_key);
+    cxMapRemove(data->properties, lm_key);
+    cxMapRemove(data->properties, ct_key);
+    cxMapRemove(data->properties, rt_key);
+    cxMapRemove(data->properties, cn_key);
+    cxMapRemove(data->properties, ck_key);
+    cxMapRemove(data->properties, ch_key);
+    
+    resource_free_properties(sn, data->properties);
+    data->properties = new_properties;
+    
+    free(cl_keystr.ptr);
+    free(cd_keystr.ptr);
+    free(lm_keystr.ptr);
+    free(ct_keystr.ptr);
+    free(rt_keystr.ptr);
+    free(cn_keystr.ptr);
+    free(ck_keystr.ptr);
+    free(ch_keystr.ptr);
+    
+    return 0;
+}
+
+/*
+ * execute a davql select statement
+ */
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) {
+    CxMempool *mp = cxBasicMempoolCreate(128);
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    DavQLArgList *args = dav_ql_get_args(st, ap);
+    if(!args) {
+        return result;
+    }
+    cxMempoolRegister(mp, args, (cx_destructor_func)dav_ql_free_arglist);
+    
+    int isallprop;
+    CxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp->allocator, st->fields, &isallprop);
+    if(!rqbuf) {
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    cxMempoolRegister(mp, rqbuf, (cx_destructor_func)cxBufferFree);
+    
+    // compile field list
+    CxList *cfieldlist = cxLinkedListCreate(mp->allocator, NULL, CX_STORE_POINTERS);
+    if(st->fields) {
+        CxIterator i = cxListIterator(st->fields);
+        cx_foreach(DavQLField*, field, i) {
+            if(cx_strcmp(field->name, CX_STR("*")) && cx_strcmp(field->name, CX_STR("-"))) {
+                // compile field expression
+                CxBuffer *code = dav_compile_expr(
+                        sn->context,
+                        mp->allocator,
+                        field->expr,
+                        args);
+                if(!code) {
+                    // TODO: set error string
+                    return result;
+                }
+                DavCompiledField *cfield = cxMalloc(
+                        mp->allocator,
+                        sizeof(DavCompiledField));
+
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                    sn->context,
+                    cx_strdup_a(mp->allocator, field->name).ptr,
+                    &ns,
+                    &name);
+                if(!ns || !name) {
+                    // TODO: set error string
+                    return result;
+                }
+                cfield->ns = ns;
+                cfield->name = name;
+                cfield->code = code;
+                cxListAdd(cfieldlist, cfield);
+            } 
+        }
+    }
+    
+    // get path string
+    davqlerror_t error;
+    cxmutstr path = dav_format_string(mp->allocator, st->path, args, &error);
+    if(error) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    int depth = st->depth == DAV_DEPTH_PLACEHOLDER ?
+            dav_ql_getarg_int(args) : st->depth;
+    
+    CxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args);
+    if(st->where && !where) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    // compile order criterion
+    CxList *ordercr = NULL;
+    if(st->orderby) {
+        ordercr = cxLinkedListCreate(mp->allocator, NULL, sizeof(DavOrderCriterion));
+        CxIterator i = cxListIterator(st->orderby);
+        cx_foreach(DavQLOrderCriterion*, oc, i) {
+            DavQLExpression *column = oc->column;
+            //printf("%.*s %s\n", column->srctext.length, column->srctext.ptr, oc->descending ? "desc" : "asc");
+            if(column->type == DAVQL_IDENTIFIER) {
+                // TODO: remove code duplication (add_cmd)
+                davqlresprop_t resprop;
+                cxstring propertyname = cx_strchr(column->srctext, ':');
+                if(propertyname.length > 0) {
+                    char *ns;
+                    char *name;
+                    dav_get_property_namespace_str(
+                            sn->context,
+                            cx_strdup_a(mp->allocator, column->srctext).ptr,
+                            &ns,
+                            &name);
+                    if(ns && name) {
+                        DavOrderCriterion cr;
+                        cr.type = 1;
+                        cxmutstr keystr = dav_property_key_a(mp->allocator, ns, name);
+                        cr.column.property = cx_hash_key(keystr.ptr, keystr.length);
+                        cr.descending = oc->descending;
+                        cxListAdd(ordercr, &cr);
+                    } else {
+                        // error
+                        // TODO: cleanup
+                        cxMempoolDestroy(mp);
+                        return result;
+                    }
+                } else if(dav_identifier2resprop(column->srctext, &resprop)) {
+                    DavOrderCriterion cr;
+                    cr.type = 0;
+                    cr.column.resprop = resprop;
+                    cr.descending = oc->descending;
+                    cxListAdd(ordercr, &cr);
+                } else {
+                    // error
+                    // TODO: cleanup
+                    cxMempoolDestroy(mp);
+                    return result;
+                }
+
+            } else if(column->type == DAVQL_NUMBER) {
+                // TODO: implement
+                fprintf(stderr, "order by number not supported\n");
+                return result;
+            } else {
+                // something is broken
+                // TODO: cleanup
+                cxMempoolDestroy(mp);
+                return result;
+            }
+        }
+    }
+    
+    DavResource *selroot = dav_resource_new(sn, path.ptr);
+    
+    CxList *stack = cxLinkedListCreateSimple(sizeof(DavQLRes));
+    // initialize the stack with the requested resource
+    DavQLRes res;
+    res.resource = selroot;
+    res.depth = 0;
+    cxListInsert(stack, 0, &res);
+    
+    // reuseable response buffer
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, mp->allocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!rpbuf) {
+        // TODO: cleanup
+        cxMempoolDestroy(mp);
+        return result;
+    }
+    
+    result.result = selroot;
+    result.status = 0;
+    
+    // do a propfind request for each resource on the stack
+    while(cxListSize(stack) > 0) {
+        DavQLRes *sr_ptr = cxListAt(stack, 0); // get first element from the stack
+        DavResource *root = sr_ptr->resource;
+        int res_depth = sr_ptr->depth;
+        cxListRemove(stack, 0); // remove first element
+        
+        util_set_url(sn, dav_resource_get_href(root));
+        CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+        long http_status = 0;
+        curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+        //printf("rpbuf: %s\n%.*s\n\n", root->href, (int)rpbuf->size, rpbuf->space);
+        //fflush(stdout);
+        
+        if(ret == CURLE_OK && http_status == 207) {
+            // in case of an redirect we have to adjust resource->href
+            dav_set_effective_href(sn, root);
+            
+            // propfind request successful, now parse the response
+            char *url = "http://url/";          
+            PropfindParser *parser = create_propfind_parser(rpbuf, url);
+            if(!parser) {
+                result.status = -1;
+                break;
+            }
+            
+            ResponseTag response;
+            int r;
+            while((r = get_propfind_response(parser, &response)) != 0) {
+                if(r == -1) {
+                    // error
+                    result.status = -1;
+                    // TODO: free resources
+                    cleanup_response(&response);
+                    break;
+                }
+                
+                // the propfind multistatus response contains responses
+                // for the requested resource and all childs
+                // determine if the response is a child or not
+                if(hrefeq(sn, root->href, response.href)) {
+                    // response is the currently requested resource
+                    // and not a child
+                    
+                    // add properties
+                    add_properties(root, &response);
+                    cleanup_response(&response);
+                    
+                    if(root == selroot) {                     
+                        // The current root is the root of the select query.
+                        // In this case we have to check the where clause.
+                        // If root is not selroot, the where clause was
+                        // already checked for the resource before it was
+                        // added to the stack.
+                        DavQLStackObj where_result;
+                        if(!dav_exec_expr(where, root, &where_result)) {                           
+                            if(where_result.data.integer != 0) { 
+                                if(!reset_properties(sn, &result, root, cfieldlist)) {
+                                    continue;
+                                }
+                                result.status = -1;
+                            }
+                        }
+                        result.result = NULL;
+                        result.status = -1;
+                        dav_resource_free_all(selroot);
+                        cxListDestroy(stack);
+                        break;
+                    }
+                } else {
+                    DavResource *child = response2resource(
+                            sn,
+                            &response,
+                            root->path);
+                    cleanup_response(&response);
+                    // check where clause
+                    DavQLStackObj where_result;
+                    if(!dav_exec_expr(where, child, &where_result)) {
+                        if(where_result.data.integer != 0) {
+                            if(!reset_properties(sn, &result, child, cfieldlist)) {
+                                //resource_add_child(root, child);
+                                resource_add_ordered_child(root, child, ordercr);
+                                if(child->iscollection &&
+                                    (depth < 0 || depth > res_depth+1))
+                                {
+                                    DavQLRes rs;
+                                    rs.resource = child;
+                                    rs.depth = res_depth + 1;
+                                    cxListInsert(stack, 0, &rs);
+                                }
+                            } else {
+                                dav_resource_free(child);
+                            }
+                        } else {
+                            dav_resource_free(child);
+                        }
+                    }
+                }    
+            }
+            destroy_propfind_parser(parser);
+        } else  {
+            dav_session_set_error(sn, ret, http_status);
+            result.result = NULL;
+            result.status = -1;
+            dav_resource_free_all(selroot);
+            break;
+        }
+        
+        // reset response buffer
+        cxBufferSeek(rpbuf, SEEK_SET, 0);
+    }
+    
+    cxMempoolDestroy(mp);
+    return result;
+}
+
+static int count_func_args(DavQLExpression *expr) {
+    int count = 0;
+    DavQLExpression *arg = expr->right;
+    while(arg) {
+        count++;
+        if(arg->op == DAVQL_ARGLIST) {
+            arg = arg->right;
+        } else {
+            break;
+        }
+    }
+    return count;
+}
+
+int dav_identifier2resprop(cxstring src, davqlresprop_t *prop) {
+    if(!cx_strcmp(src, CX_STR("name"))) {
+        *prop = DAVQL_RES_NAME;
+    } else if(!cx_strcmp(src, CX_STR("path"))) {
+        *prop = DAVQL_RES_PATH;
+    } else if(!cx_strcmp(src, CX_STR("href"))) {
+        *prop = DAVQL_RES_HREF;
+    } else if(!cx_strcmp(src, CX_STR("contentlength"))) {
+        *prop = DAVQL_RES_CONTENTLENGTH;
+    } else if(!cx_strcmp(src, CX_STR("contenttype"))) {
+        *prop = DAVQL_RES_CONTENTTYPE;
+    } else if(!cx_strcmp(src, CX_STR("creationdate"))) {
+        *prop = DAVQL_RES_CREATIONDATE;
+    } else if(!cx_strcmp(src, CX_STR("lastmodified"))) {
+        *prop = DAVQL_RES_LASTMODIFIED;
+    } else if(!cx_strcmp(src, CX_STR("iscollection"))) {
+        *prop = DAVQL_RES_ISCOLLECTION;
+    } else {
+        return 0;
+    }
+    return 1;
+}
+
+static int add_cmd(DavContext *ctx, const CxAllocator *a, CxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) {
+    if(!expr) {
+        return 0;
+    }
+     
+    int numcmd = 1;
+    DavQLCmd cmd;
+    memset(&cmd, 0, sizeof(DavQLCmd));
+    davqlerror_t error;
+    
+    cxstring src = expr->srctext;
+    switch(expr->type) {
+        default: break;
+        case DAVQL_NUMBER: {   
+            cmd.type = DAVQL_CMD_INT;
+            if(src.ptr[0] == '%') {
+                cmd.data.integer = dav_ql_getarg_int(ap);
+            } else if(util_strtoint(src.ptr, &cmd.data.integer)) {
+                cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            
+            break;
+        }
+        case DAVQL_STRING: {
+            cmd.type = DAVQL_CMD_STRING;
+            cmd.data.string = dav_format_string(a, src, ap, &error);
+            cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_TIMESTAMP: {
+            if(src.ptr[0] == '%') {
+                cmd.type = DAVQL_CMD_TIMESTAMP;
+                cmd.data.timestamp = dav_ql_getarg_time(ap);
+                cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            break;
+        }
+        case DAVQL_IDENTIFIER: {
+            cxstring propertyname = cx_strchr(src, ':');
+            cmd.type = DAVQL_CMD_RES_IDENTIFIER;
+            if(propertyname.length > 0) {
+                cmd.type = DAVQL_CMD_PROP_IDENTIFIER;
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                        ctx,
+                        cx_strdup_a(a, src).ptr,
+                        &ns,
+                        &name);
+                if(ns && name) {
+                    cmd.data.property.ns = ns;
+                    cmd.data.property.name = name;
+                } else {
+                    // error
+                    return -1;
+                }
+            } else if(!dav_identifier2resprop(src, &cmd.data.resprop)) {
+                if(!cx_strcmp(src, CX_STR("true"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 1;
+                } else if(!cx_strcmp(src, CX_STR("false"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 0;
+                } else {
+                    // error, unknown identifier
+                    return -1;
+                }
+            }
+            cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_UNARY: {
+            numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+            switch(expr->op) {
+                case DAVQL_ADD: {
+                    // noop
+                    numcmd = 0;
+                    break;
+                }
+                case DAVQL_SUB: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_SUB;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEG: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_NEG;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+        case DAVQL_BINARY: {
+            numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+            numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+            switch(expr->op) {
+                case DAVQL_ADD: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_ADD;
+                    break;
+                }
+                case DAVQL_SUB: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_SUB;
+                    break;
+                }
+                case DAVQL_MUL: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_MUL;
+                    break;
+                }
+                case DAVQL_DIV: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_DIV;
+                    break;
+                }
+                case DAVQL_AND: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_AND;
+                    break;
+                }
+                case DAVQL_OR: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_OR;
+                    break;
+                }
+                case DAVQL_XOR: {
+                    cmd.type = DAVQL_CMD_OP_BINARY_XOR;
+                    break;
+                }
+                default: break;
+            }
+            cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+            break;
+        }
+        case DAVQL_LOGICAL: {
+            if(expr->left && expr->right && expr->op != DAVQL_LOR) {
+                numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+            }
+            
+            switch(expr->op) {
+                case DAVQL_NOT: {
+                    numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_NOT;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LAND: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_AND;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LOR: {
+                    int nleft = add_cmd(ctx, a, bcode, expr->left, ap);
+                    
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_OR_L;
+                    DavQLCmd *or_l = (DavQLCmd*)(bcode->space + bcode->pos);
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+                    or_l->data.integer = nright + 1;
+                    
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_OR;
+                    cmd.data.integer = 0;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd += nleft + nright;
+                    break;
+                }
+                case DAVQL_LXOR: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_XOR;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_EQ: {
+                    cmd.type = DAVQL_CMD_OP_EQ;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEQ: {
+                    cmd.type = DAVQL_CMD_OP_NEQ;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LT: {
+                    cmd.type = DAVQL_CMD_OP_LT;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GT: {
+                    cmd.type = DAVQL_CMD_OP_GT;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LE: {
+                    cmd.type = DAVQL_CMD_OP_LE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GE: {
+                    cmd.type = DAVQL_CMD_OP_GE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LIKE: {
+                    cmd.type = DAVQL_CMD_OP_LIKE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_UNLIKE: {
+                    cmd.type = DAVQL_CMD_OP_UNLIKE;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+        case DAVQL_FUNCCALL: {
+            switch(expr->op) {
+                case DAVQL_CALL: {
+                    int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+                    // TODO: count args
+                    DavQLExpression *funcid = expr->left;
+                    if(!funcid && funcid->type != DAVQL_IDENTIFIER) {
+                        // fail
+                        return -1;
+                    }
+                    
+                    // numargs
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = count_func_args(expr);
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    // TODO: resolve function name
+                    cmd.type = DAVQL_CMD_CALL;
+                    cmd.data.func = NULL;
+                    cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd = 2;
+                    numcmd += nright;
+                    break;
+                }
+                case DAVQL_ARGLIST: {
+                    numcmd = 0;
+                    numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+                    numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+                    break;
+                }
+                default: break;
+            }
+            break;
+        }
+    }
+    return numcmd;
+}
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList  *ap) {
+    CxBuffer *bcode = cxBufferCreate(NULL, 512, a, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    if(!bcode) {
+        return NULL;
+    }
+    
+    if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) {
+        cxBufferFree(bcode);
+        return NULL;
+    }
+    
+    return bcode;
+}
+
+static int cmd_str_cmp(DavQLStackObj obj1, DavQLStackObj obj2, davqlcmdtype_t cmd) {
+    cxmutstr s1m = obj1.type == 1 ?
+        cx_mutstrn(obj1.data.string, obj1.length) :
+        cx_asprintf("%" PRId64, obj1.data.integer);
+    cxmutstr s2m = obj1.type == 1 ?
+        cx_mutstrn(obj2.data.string, obj2.length) :
+        cx_asprintf("%" PRId64, obj2.data.integer);
+    
+    cxstring s1 = cx_strcast(s1m);
+    cxstring s2 = cx_strcast(s2m);
+    
+    int res = 0;
+    switch(cmd) {
+        case DAVQL_CMD_OP_EQ: {
+            res = cx_strcmp(s1, s2) == 0;
+            break;
+        }
+        case DAVQL_CMD_OP_NEQ: {
+            res = cx_strcmp(s1, s2) != 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LT: {
+            res = cx_strcmp(s1, s2) < 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GT: {
+            res = cx_strcmp(s1, s2) > 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LE: {
+            res  = cx_strcmp(s1, s2) <= 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GE: {
+            res = cx_strcmp(s1, s2) >= 0;
+            break;
+        }
+        default: break;
+    }
+    
+    if(obj1.type == 0) {
+        free(s1m.ptr);
+    }
+    if(obj2.type == 0) {
+        free(s2m.ptr);
+    }
+    
+    return res;
+}
+
+int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result) {
+    if(!bcode) {
+        result->type = 0;
+        result->length = 0;
+        result->data.integer = 1;
+        return 0;
+    }
+    
+    size_t count = bcode->pos / sizeof(DavQLCmd);
+    DavQLCmd *cmds = (DavQLCmd*)bcode->space;
+    
+    // create execution stack
+    size_t stsize = 64;
+    size_t stpos = 0;
+    DavQLStackObj *stack = calloc(stsize, sizeof(DavQLStackObj));
+#define DAVQL_PUSH(obj) \
+        if(stpos == stsize) { \
+            stsize += 64; \
+            DavQLStackObj *stack_newptr; \
+            stack_newptr = realloc(stack, stsize * sizeof(DavQLStackObj)); \
+            if(stack_newptr) { \
+                stack = stack_newptr; \
+            } else { \
+                free(stack); \
+                return -1; \
+            }\
+        } \
+        stack[stpos++] = obj;
+#define DAVQL_PUSH_INT(intval) \
+        { \
+            DavQLStackObj intobj; \
+            intobj.type = 0; \
+            intobj.length = 0; \
+            intobj.data.integer = intval; \
+            DAVQL_PUSH(intobj); \
+        }
+#define DAVQL_POP() stack[--stpos]
+    
+    DavQLStackObj obj;
+    int ret = 0;
+    for(size_t i=0;i<count;i++) {
+        DavQLCmd cmd = cmds[i];
+        switch(cmd.type) {
+            case DAVQL_CMD_INT: {
+                //printf("int %lld\n", cmd.data.integer);
+                obj.type = 0;
+                obj.length = 0;
+                obj.data.integer = cmd.data.integer;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_STRING: {
+                //printf("string \"%.*s\"\n", cmd.data.string.length, cmd.data.string.ptr);
+                obj.type = 1;
+                obj.length = cmd.data.string.length;
+                obj.data.string = cmd.data.string.ptr;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_TIMESTAMP: {
+                //printf("timestamp %d\n", cmd.data.timestamp);
+                obj.type = 0;
+                obj.length = 0;
+                obj.data.integer = (int64_t)cmd.data.timestamp;
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_RES_IDENTIFIER: {
+                //char *rid[8] = {"name", "path", "href", "contentlength", "contenttype", "creationdate", "lastmodified", "iscollection"};
+                //printf("resprop %s\n", rid[cmd.data.resprop]);
+                switch(cmd.data.resprop) {
+                    case DAVQL_RES_NAME: {
+                        obj.type = 1;
+                        obj.length = strlen(res->name);
+                        obj.data.string = res->name;
+                        break;
+                    }
+                    case DAVQL_RES_PATH: {
+                        obj.type = 1;
+                        obj.length = strlen(res->path);
+                        obj.data.string = res->path;
+                        break;
+                    }
+                    case DAVQL_RES_HREF: {
+                        obj.type = 1;
+                        obj.length = strlen(res->href);
+                        obj.data.string = res->href;
+                        break;
+                    }
+                    case DAVQL_RES_CONTENTLENGTH: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->contentlength;
+                        break;
+                    }
+                    case DAVQL_RES_CONTENTTYPE: {
+                        obj.type = 1;
+                        obj.length = strlen(res->contenttype);
+                        obj.data.string = res->contenttype;
+                        break;
+                    }
+                    case DAVQL_RES_CREATIONDATE: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->creationdate;
+                        break;
+                    }
+                    case DAVQL_RES_LASTMODIFIED: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->lastmodified;
+                        break;
+                    }
+                    case DAVQL_RES_ISCOLLECTION: {
+                        obj.type = 0;
+                        obj.length = 0;
+                        obj.data.integer = res->iscollection;
+                        break;
+                    }
+                }
+                DAVQL_PUSH(obj);
+                break;
+            }
+            case DAVQL_CMD_PROP_IDENTIFIER: {
+                //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name);
+                //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                if(dav_xml_isstring(value)) {
+                    obj.type = 1;
+                    obj.length = (uint32_t)value->contentlength;
+                    obj.data.string = value->content;
+                } else {
+                    obj.type = 2;
+                    obj.length = 0;
+                    obj.data.node = value;
+                }
+                DAVQL_PUSH(obj);
+                break;
+            }
+            //case DAVQL_CMD_OP_UNARY_ADD: {
+            //    printf("uadd\n");
+            //    break;
+            //}
+            case DAVQL_CMD_OP_UNARY_SUB: {
+                //printf("usub\n");
+                obj = DAVQL_POP();
+                if(obj.type == 0) {
+                    obj.data.integer = -obj.data.integer;
+                    DAVQL_PUSH(obj);
+                } else {
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_UNARY_NEG: {
+                //printf("uneg\n");
+                obj = DAVQL_POP();
+                if(obj.type == 0) {
+                    obj.data.integer = obj.data.integer == 0 ? 1 : 0;
+                    DAVQL_PUSH(obj);
+                } else {
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_ADD: {
+                //printf("add\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer + obj2.data.integer);
+                } else {
+                    // TODO: string concat
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_SUB: {
+                //printf("sub\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer - obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_MUL: {
+                //printf("mul\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer * obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_DIV: {
+                //printf("div\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer / obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_AND: {
+                //printf("and\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer & obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_OR: {
+                //printf("or\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer | obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_BINARY_XOR: {
+                //printf("xor\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer ^ obj2.data.integer);
+                } else {
+                    // error
+                    ret = -1;
+                    i = count; // end loop
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_NOT: {
+                //printf("not\n");
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_AND: {
+                //printf("land\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(v1 && v2);
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_OR_L: {
+                //printf("or_l %d\n", cmd.data.integer);
+                DavQLStackObj obj1 = stack[stpos];
+                if((obj1.type == 0 && obj1.data.integer) || (obj1.type == 1 && obj1.data.string)) {
+                    stpos--;
+                    DAVQL_PUSH_INT(1);
+                    i += cmd.data.integer; // jump, skip right subtree of 'or'
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_OR: {
+                //printf("or\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(v1 || v2);
+                break;
+            }
+            case DAVQL_CMD_OP_LOGICAL_XOR: {
+                //printf("lxor\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+                int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+                DAVQL_PUSH_INT(!v1 != !v2);
+                break;
+            }
+            case DAVQL_CMD_OP_EQ: {
+                //printf("eq\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer == obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_NEQ: {
+                //printf("neq\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer != obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LT: {
+                //printf("lt\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer < obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_GT: {
+                //printf("gt\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer > obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LE: {
+                //printf("le\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer <= obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_GE: {
+                //printf("ge\n");
+                DavQLStackObj obj2 = DAVQL_POP();
+                DavQLStackObj obj1 = DAVQL_POP();
+                if(obj1.type == 0 && obj2.type == 0) {
+                    DAVQL_PUSH_INT(obj1.data.integer >= obj2.data.integer);
+                } else {
+                    DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+                }
+                break;
+            }
+            case DAVQL_CMD_OP_LIKE: {
+                //printf("like\n");
+                break;
+            }
+            case DAVQL_CMD_OP_UNLIKE: {
+                //printf("unlike\n");
+                break;
+            }
+            case DAVQL_CMD_CALL: {
+                //printf("call %x\n", cmd.data.func);
+                break;
+            }
+        }
+    }
+    
+    if(stpos == 1) {
+        *result = stack[0];
+    } else {
+        ret = -1;
+    }
+    free(stack);
+    
+    return ret;
+}
diff --git a/libidav/davqlexec.h b/libidav/davqlexec.h
new file mode 100644 (file)
index 0000000..48511fc
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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 DAVQLEXEC_H
+#define        DAVQLEXEC_H
+
+#include <stdarg.h>
+#include "davqlparser.h"
+#include "webdav.h"
+
+#include <cx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+typedef struct DavQLCmd      DavQLCmd;
+typedef struct DavQLStackObj DavQLStackObj;
+typedef struct DavQLRes      DavQLRes;
+
+typedef struct DavQLArg      DavQLArg;
+typedef struct DavQLArgList  DavQLArgList;
+
+typedef void*(*davql_func)(); // TODO: interface?
+
+struct DavQLArg {
+    int type;
+    union DavQLArgValue{
+        int          d;
+        unsigned int u;
+        char         *s;
+        time_t       t;
+    } value;
+    DavQLArg *next;
+};
+
+struct DavQLArgList {
+    DavQLArg *first;
+    DavQLArg *current;
+};
+
+typedef enum {
+    DAVQL_OK = 0,
+    DAVQL_UNSUPPORTED_FORMATCHAR,
+    DAVQL_UNKNOWN_FORMATCHAR,
+    DAVQL_OOM
+} davqlerror_t;
+
+typedef enum {
+    DAVQL_CMD_INT = 0,
+    DAVQL_CMD_STRING,
+    DAVQL_CMD_TIMESTAMP,
+    DAVQL_CMD_RES_IDENTIFIER,
+    DAVQL_CMD_PROP_IDENTIFIER,
+    //DAVQL_CMD_OP_UNARY_ADD,
+    DAVQL_CMD_OP_UNARY_SUB,
+    DAVQL_CMD_OP_UNARY_NEG,
+    DAVQL_CMD_OP_BINARY_ADD,
+    DAVQL_CMD_OP_BINARY_SUB,
+    DAVQL_CMD_OP_BINARY_MUL,
+    DAVQL_CMD_OP_BINARY_DIV,
+    DAVQL_CMD_OP_BINARY_AND,
+    DAVQL_CMD_OP_BINARY_OR,
+    DAVQL_CMD_OP_BINARY_XOR,
+    DAVQL_CMD_OP_LOGICAL_NOT,
+    DAVQL_CMD_OP_LOGICAL_AND,
+    DAVQL_CMD_OP_LOGICAL_OR_L,
+    DAVQL_CMD_OP_LOGICAL_OR,
+    DAVQL_CMD_OP_LOGICAL_XOR,
+    DAVQL_CMD_OP_EQ,
+    DAVQL_CMD_OP_NEQ,
+    DAVQL_CMD_OP_LT,
+    DAVQL_CMD_OP_GT,
+    DAVQL_CMD_OP_LE,
+    DAVQL_CMD_OP_GE,
+    DAVQL_CMD_OP_LIKE,
+    DAVQL_CMD_OP_UNLIKE,
+    DAVQL_CMD_CALL
+} davqlcmdtype_t;
+
+typedef enum {
+    DAVQL_RES_NAME = 0,
+    DAVQL_RES_PATH,
+    DAVQL_RES_HREF,
+    DAVQL_RES_CONTENTLENGTH,
+    DAVQL_RES_CONTENTTYPE,
+    DAVQL_RES_CREATIONDATE,
+    DAVQL_RES_LASTMODIFIED,
+    DAVQL_RES_ISCOLLECTION
+} davqlresprop_t;
+
+struct DavQLCmd {
+    davqlcmdtype_t type;
+    union DavQLCmdData {
+        int64_t        integer;
+        cxmutstr       string;
+        time_t         timestamp;
+        davqlresprop_t resprop;
+        DavPropName    property;
+        davql_func     func;
+    } data;
+};
+
+struct DavQLStackObj {
+    int32_t  type; // 0: int, 1: string, 2: xml
+    uint32_t length;
+    union DavQLStackData {
+        int64_t    integer;
+        char       *string;
+        DavXmlNode *node;
+    } data;
+};
+
+struct DavQLRes {
+    DavResource *resource;
+    int depth;
+};
+
+typedef struct DavCompiledField {
+    char *ns;
+    char *name;
+    CxBuffer *code;
+} DavCompiledField;
+
+typedef struct DavOrderCriterion {
+    int type; // 0: resprop, 1: property
+    union DavQLColumn {
+        davqlresprop_t resprop;
+        CxHashKey property;
+    } column;
+    _Bool descending;
+} DavOrderCriterion;
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap);
+void dav_ql_free_arglist(DavQLArgList *args);
+
+int dav_ql_getarg_int(DavQLArgList *args);
+unsigned int dav_ql_getarg_uint(DavQLArgList *args);
+char* dav_ql_getarg_str(DavQLArgList *args);
+time_t dav_ql_getarg_time(DavQLArgList *args);
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...);
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap);
+
+CxBuffer* dav_path_string(cxmutstr src, DavQLArgList *args, davqlerror_t *error);
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error);
+
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap);
+
+int dav_identifier2resprop(cxstring src, davqlresprop_t *prop);
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap);
+
+int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLEXEC_H */
+
diff --git a/libidav/davqlparser.c b/libidav/davqlparser.c
new file mode 100644 (file)
index 0000000..c18ab97
--- /dev/null
@@ -0,0 +1,1860 @@
+/*
+ * 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 "davqlparser.h"
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/printf.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#define sfmtarg(s) ((int)(s).length), (s).ptr
+
+// ------------------------------------------------------------------------
+//                        D E B U G E R
+// ------------------------------------------------------------------------
+
+static const char* _map_querytype(davqltype_t type) {
+    switch(type) {
+    case DAVQL_ERROR: return "ERROR";
+    case DAVQL_SELECT: return "SELECT";
+    case DAVQL_SET: return "SET";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_exprtype(davqlexprtype_t type) {
+    switch(type) {
+    case DAVQL_UNDEFINED_TYPE: return "undefined";
+    case DAVQL_NUMBER: return "NUMBER";
+    case DAVQL_STRING: return "STRING";
+    case DAVQL_TIMESTAMP: return "TIMESTAMP";
+    case DAVQL_IDENTIFIER: return "IDENTIFIER";
+    case DAVQL_UNARY: return "UNARY";
+    case DAVQL_BINARY: return "BINARY";
+    case DAVQL_LOGICAL: return "LOGICAL";
+    case DAVQL_FUNCCALL: return "FUNCCALL";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_specialfield(int info) {
+    switch(info) {
+    case 0: return "";
+    case 1: return "with wildcard";
+    case 2: return "(resource data only)";
+    default: return "with mysterious identifier";
+    }
+}
+
+static const char* _map_operator(davqloperator_t op) {
+    // don't use string array, because enum values may change
+    switch(op) {
+    case DAVQL_NOOP: return "no operator";
+    case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ",";
+    case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-";
+    case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/";
+    case DAVQL_AND: return "&"; case DAVQL_OR: return "|";
+    case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~";
+    case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND";
+    case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR";
+    case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!=";
+    case DAVQL_LT: return "<"; case DAVQL_GT: return ">";
+    case DAVQL_LE: return "<="; case DAVQL_GE: return ">=";
+    case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE";
+    default: return "unknown";
+    }
+}
+
+static void dav_debug_ql_fnames_print(DavQLStatement *stmt) {
+    if (stmt->fields) {
+        printf("Field names: ");
+        CxIterator i = cxListIterator(stmt->fields);
+        cx_foreach(DavQLField *, f, i) {
+            printf("%.*s, ", (int)f->name.length, f->name.ptr);
+        }
+        printf("\b\b  \b\b\n");
+    }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+    // Basic information
+    size_t fieldcount = stmt->fields ? cxListSize(stmt->fields) : 0;
+    int specialfield = 0;
+    if (fieldcount > 0) {
+        DavQLField* firstfield = (DavQLField*)cxListAt(stmt->fields, 0);
+        if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+            switch (firstfield->expr->srctext.ptr[0]) {
+            case '*': specialfield = 1; break;
+            case '-': specialfield = 2; break;
+            }
+        }
+    }
+    if (specialfield) {
+        fieldcount--;
+    }
+    printf("Statement: %.*s\nType: %s\nField count: %zu %s\n",
+        (int)stmt->srctext.length, stmt->srctext.ptr,
+        _map_querytype(stmt->type),
+        fieldcount,
+        _map_specialfield(specialfield));
+    
+    dav_debug_ql_fnames_print(stmt);
+    printf("Path: %.*s\nHas where clause: %s\n",
+        (int)stmt->path.length, stmt->path.ptr,
+        stmt->where ? "yes" : "no");
+    
+    // WITH attributes
+    if (stmt->depth == DAV_DEPTH_INFINITY) {
+        printf("Depth: infinity\n");
+    } else if (stmt->depth == DAV_DEPTH_PLACEHOLDER) {
+        printf("Depth: placeholder\n");
+    } else {
+        printf("Depth: %d\n", stmt->depth);
+    }
+    
+    // order by clause
+    printf("Order by: ");
+    if (stmt->orderby) {
+        CxIterator i = cxListIterator(stmt->orderby);
+        cx_foreach(DavQLOrderCriterion*, critdata, i) {
+            printf("%.*s %s%s", (int)critdata->column->srctext.length, critdata->column->srctext.ptr,
+                critdata->descending ? "desc" : "asc",
+                i.index+1 < cxListSize(stmt->orderby) ? ", " : "\n");
+        }
+    } else {
+        printf("nothing\n");
+    }
+    
+    // error messages
+    if (stmt->errorcode) {
+        printf("\nError code: %d\nError: %s\n",
+            stmt->errorcode, stmt->errormessage);
+    }
+}
+
+static int dav_debug_ql_expr_selected(DavQLExpression *expr) {
+    if (!expr) {
+        printf("Currently no expression selected.\n");
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static void dav_debug_ql_expr_print(DavQLExpression *expr) {
+    if (dav_debug_ql_expr_selected(expr)) {
+        cxstring empty = CX_STR("(empty)");
+        printf(
+            "Text: %.*s\nType: %s\nOperator: %s\n",
+            sfmtarg(expr->srctext),
+            _map_exprtype(expr->type),
+            _map_operator(expr->op));
+        if (expr->left || expr->right) {
+            printf("Left hand: %.*s\nRight hand: %.*s\n",
+                sfmtarg(expr->left?expr->left->srctext:empty),
+                sfmtarg(expr->right?expr->right->srctext:empty));
+        }
+    }
+}
+
+static void dav_debug_ql_field_print(DavQLField *field) {
+    if (field) {
+        printf("Name: %.*s\n", sfmtarg(field->name));
+        if (field->expr) {
+            dav_debug_ql_expr_print(field->expr);
+        } else {
+            printf("No expression.\n");
+        }
+    } else {
+        printf("No field selected.\n");
+    }
+}
+
+static void dav_debug_ql_tree_print(DavQLExpression *expr, int depth) {
+    if (expr) {
+        if (expr->left) {
+            printf("%*c%s\n", depth, ' ', _map_operator(expr->op));
+            dav_debug_ql_tree_print(expr->left, depth+1);
+            dav_debug_ql_tree_print(expr->right, depth+1);
+        } else if (expr->type == DAVQL_UNARY) {
+            printf("%*c%s %.*s\n", depth, ' ', _map_operator(expr->op),
+                sfmtarg(expr->srctext));
+        } else {
+            printf("%*c%.*s\n", depth, ' ', sfmtarg(expr->srctext));
+        }
+    }
+}
+
+#define DQLD_CMD_Q     0
+#define DQLD_CMD_PS    1
+#define DQLD_CMD_PE    2
+#define DQLD_CMD_PF    3
+#define DQLD_CMD_PT    4
+#define DQLD_CMD_F    10
+#define DQLD_CMD_W    11
+#define DQLD_CMD_O    12
+#define DQLD_CMD_L    21
+#define DQLD_CMD_R    22
+#define DQLD_CMD_N    23
+#define DQLD_CMD_P    24
+#define DQLD_CMD_H   100
+
+static int dav_debug_ql_command() {
+    printf("> ");
+    
+    char buffer[8];
+    fgets(buffer, 8, stdin);
+     // discard remaining chars
+    if (!strchr(buffer, '\n')) {
+        int chr;
+        while ((chr = fgetc(stdin) != '\n') && chr != EOF);
+    }
+    
+    if (!strcmp(buffer, "q\n")) {
+        return DQLD_CMD_Q;
+    } else if (!strcmp(buffer, "ps\n")) {
+        return DQLD_CMD_PS;
+    } else if (!strcmp(buffer, "pe\n")) {
+        return DQLD_CMD_PE;
+    } else if (!strcmp(buffer, "pf\n")) {
+        return DQLD_CMD_PF;
+    } else if (!strcmp(buffer, "pt\n")) {
+        return DQLD_CMD_PT;
+    } else if (!strcmp(buffer, "l\n")) {
+        return DQLD_CMD_L;
+    } else if (!strcmp(buffer, "r\n")) {
+        return DQLD_CMD_R;
+    } else if (!strcmp(buffer, "h\n")) {
+        return DQLD_CMD_H;
+    } else if (!strcmp(buffer, "f\n")) {
+        return DQLD_CMD_F;
+    } else if (!strcmp(buffer, "w\n")) {
+        return DQLD_CMD_W;
+    } else if (!strcmp(buffer, "o\n")) {
+        return DQLD_CMD_O;
+    } else if (!strcmp(buffer, "n\n")) {
+        return DQLD_CMD_N;
+    } else if (!strcmp(buffer, "p\n")) {
+        return DQLD_CMD_P;
+    } else {
+        return -1;
+    }
+}
+
+void dav_debug_statement(DavQLStatement *stmt) {
+    if (!stmt) {
+        fprintf(stderr, "Debug DavQLStatement failed: null pointer");
+        return;
+    }
+
+    printf("Starting DavQL debugger (type 'h' for help)...\n\n");
+    dav_debug_ql_stmt_print(stmt);
+    
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    DavQLExpression *examineexpr = NULL;
+    CxList *examineelem = NULL;
+    int examineclause = 0;
+    
+    while(1) {
+        int cmd = dav_debug_ql_command();
+        switch (cmd) {
+        case DQLD_CMD_Q: return;
+        case DQLD_CMD_PS: dav_debug_ql_stmt_print(stmt); break;
+        case DQLD_CMD_PE: dav_debug_ql_expr_print(examineexpr); break;
+        case DQLD_CMD_PT: dav_debug_ql_tree_print(examineexpr, 1); break;
+        case DQLD_CMD_PF: dav_debug_ql_fnames_print(stmt); break;
+        case DQLD_CMD_F:
+            examineclause = DQLD_CMD_F;
+            examineelem = stmt->fields;
+            if (stmt->fields && cxListSize(stmt->fields) > 0) {
+                DavQLField* field = cxListAt(stmt->fields, 0);
+                examineexpr = field->expr;
+                dav_debug_ql_field_print(field);
+            } else {
+                examineexpr = NULL;
+            }
+            break;
+        case DQLD_CMD_W:
+            examineclause = 0; examineelem = NULL;
+            examineexpr = stmt->where;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_O:
+            examineclause = DQLD_CMD_O;
+            examineelem = stmt->orderby;
+            examineexpr = stmt->orderby && cxListSize(stmt->orderby) > 0 ?
+                ((DavQLOrderCriterion*)cxListAt(stmt->orderby, 0))->column : NULL;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            printf("TODO: port code to ucx 3\n");
+            /*
+            if (examineelem) {
+                CxList *newelem = (cmd == DQLD_CMD_N ?
+                    examineelem->next : examineelem->prev);
+                if (newelem) {
+                    examineelem = newelem;
+                    if (examineclause == DQLD_CMD_O) {
+                        examineexpr = ((DavQLOrderCriterion*)
+                            examineelem->data)->column;
+                        dav_debug_ql_expr_print(examineexpr);
+                    } else if (examineclause == DQLD_CMD_F) {
+                        DavQLField* field = (DavQLField*)examineelem->data;
+                        examineexpr = field->expr;
+                        dav_debug_ql_field_print(field);
+                    } else {
+                        printf("Examining unknown clause type.");
+                    }
+                } else {
+                    printf("Reached end of list.\n");
+                }
+            } else {
+                printf("Currently not examining an expression list.\n");
+            }
+            */
+            break;
+        case DQLD_CMD_L:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->left) {
+                    examineexpr = examineexpr->left;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no left subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_R:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->right) {
+                    examineexpr = examineexpr->right;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no right subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_H:
+            printf(
+                "\nCommands:\n"
+                "ps:  print statement information\n"
+                "o:   examine order by clause\n"
+                "f:   examine field list\n"
+                "pf:  print field names\n"
+                "w:   examine where clause\n"
+                "n:   examine next expression "
+                    "(in order by clause or field list)\n"
+                "p:   examine previous expression "
+                    "(in order by clause or field list)\n"
+                "q:   quit\n\n"
+                "\nExpression examination:\n"
+                "pe:  print expression information\n"
+                "pt:  print full syntax tree of current (sub-)expression\n"
+                "l:   enter left subtree\n"
+                "r:   enter right subtree\n");
+            break;
+        default: printf("unknown command\n");
+        }
+    }
+}
+
+// ------------------------------------------------------------------------
+//                         P A R S E R
+// ------------------------------------------------------------------------
+
+#define _error_context "(%.*s[->]%.*s%.*s)"
+#define _error_invalid "invalid statement"
+#define _error_out_of_memory "out of memory"
+#define _error_unexpected_token "unexpected token " _error_context
+#define _error_invalid_token "invalid token " _error_context
+#define _error_missing_path "expected path " _error_context
+#define _error_missing_from "expecting FROM keyword " _error_context
+#define _error_missing_at "expecting AT keyword " _error_context
+#define _error_missing_by "expecting BY keyword " _error_context
+#define _error_missing_as "expecting alias ('as <identifier>') " _error_context
+#define _error_missing_identifier "expecting identifier " _error_context
+#define _error_missing_par "missing closed parenthesis " _error_context
+#define _error_missing_assign "expecting assignment ('=') " _error_context
+#define _error_missing_where "SET statements must have a WHERE clause or " \
+                        "explicitly use ANYWHERE " _error_context
+#define _error_invalid_depth "invalid depth " _error_context
+#define _error_missing_expr "missing expression " _error_context
+#define _error_invalid_expr "invalid expression " _error_context
+#define _error_invalid_unary_op "invalid unary operator "  _error_context
+#define _error_invalid_logical_op "invalid logical operator "  _error_context
+#define _error_invalid_fmtspec "invalid format specifier " _error_context
+#define _error_invalid_string "string expected " _error_context
+#define _error_invalid_order_criterion "invalid order criterion " _error_context
+
+#define token_sstr(token) ((token)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, DavQLToken *token) {
+    
+    // we try to achieve two things: get as many information as possible
+    // and recover the concrete source string (and not the token strings)
+    cxstring emptystring = CX_STR("");
+    cxstring prev = token->prev ? (token->prev->prev ?
+        token_sstr(token->prev->prev) : token_sstr(token->prev))
+        : emptystring;
+    cxstring tokenstr = token_sstr(token);
+    cxstring next = token->next ? (token->next->next ?
+        token_sstr(token->next->next) : token_sstr(token->next))
+        : emptystring;
+    
+    int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr;
+    const char *pn = tokenstr.ptr + tokenstr.length;
+    int ln = next.ptr+next.length - pn;
+    
+    stmt->errorcode = errorcode;
+    stmt->errormessage = cx_asprintf(errormsg,
+        lp, prev.ptr,
+        sfmtarg(tokenstr),
+        ln, pn).ptr;
+}
+
+#define dqlsec_alloc_failed(ptr, stmt)                                  \
+                    if (!(ptr)) do {                                    \
+                        (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;  \
+                        return 0;                                       \
+                    } while(0)
+#define dqlsec_malloc(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = malloc(sizeof(type)), stmt)
+#define dqlsec_mallocz(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = calloc(1, sizeof(type)), stmt)
+
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+    cxstring keywords[] ={CX_STR("select"), CX_STR("set"), CX_STR("from"), CX_STR("at"), CX_STR("as"),
+        CX_STR("where"), CX_STR("anywhere"), CX_STR("like"), CX_STR("unlike"), CX_STR("and"),
+        CX_STR("or"), CX_STR("not"), CX_STR("xor"), CX_STR("with"), CX_STR("infinity"),
+        CX_STR("order"), CX_STR("by"), CX_STR("asc"), CX_STR("desc")
+    };
+    for (int i = 0 ; i < sizeof(keywords)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, keywords[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+    cxstring operators[] = {CX_STR("and"), CX_STR("or"), CX_STR("not"), CX_STR("xor"),
+        CX_STR("like"), CX_STR("unlike")
+    };
+    for (int i = 0 ; i < sizeof(operators)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, operators[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int dav_stmt_add_field(DavQLStatement *stmt, DavQLField *field) {
+    if(!stmt->fields) {
+        stmt->fields = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        if(!stmt->fields) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+        }
+    }
+    
+    if(cxListAdd(stmt->fields, field)) {
+        stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+    }
+    
+    return 0;
+}
+    
+
+static void tokenlist_free(DavQLToken *tokenlist) {
+    DavQLToken *token = tokenlist;
+    while(token) {
+        DavQLToken *next = token->next;
+        free(token);
+        token = next;
+    }
+}
+
+static int dav_parse_add_token(DavQLToken **begin, DavQLToken **end, DavQLToken *token) {
+    
+    // determine token class (order of if-statements is very important!)
+    char firstchar = token->value.ptr[0];
+
+    if (isdigit(firstchar)) {
+        token->tokenclass = DAVQL_TOKEN_NUMBER;
+        // check, if all characters are digits
+        for (size_t i = 1 ; i < token->value.length ; i++) {
+            if (!isdigit(token->value.ptr[i])) {
+                token->tokenclass = DAVQL_TOKEN_INVALID;
+                break;
+            }
+        }
+    } else if (firstchar == '%') {
+        token->tokenclass = DAVQL_TOKEN_FMTSPEC;
+    } else if (token->value.length == 1) {
+        switch (firstchar) {
+        case '(': token->tokenclass = DAVQL_TOKEN_OPENP; break;
+        case ')': token->tokenclass = DAVQL_TOKEN_CLOSEP; break;
+        case ',': token->tokenclass = DAVQL_TOKEN_COMMA; break;
+        default:
+            token->tokenclass = strchr(special_token_symbols, firstchar) ?
+                DAVQL_TOKEN_OPERATOR : DAVQL_TOKEN_IDENTIFIER;
+        }
+    } else if (islongoperator(token)) {
+        token->tokenclass = DAVQL_TOKEN_OPERATOR;
+    } else if (firstchar == '\'') {
+        token->tokenclass = DAVQL_TOKEN_STRING;
+    } else if (firstchar == '`') {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+    } else if (iskeyword(token)) {
+        token->tokenclass = DAVQL_TOKEN_KEYWORD;
+    } else {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+        // TODO: check for illegal characters
+    }
+    
+    // remove quotes (extreme cool feature)
+    if (token->tokenclass == DAVQL_TOKEN_STRING ||
+        (token->tokenclass == DAVQL_TOKEN_IDENTIFIER && firstchar == '`')) {
+        
+        char lastchar = token->value.ptr[token->value.length-1];
+        if (firstchar == lastchar) {
+            token->value.ptr++;
+            token->value.length -= 2;
+        } else {
+            token->tokenclass = DAVQL_TOKEN_INVALID;
+        }
+    }
+    
+    cx_linked_list_add((void**)begin, (void**)end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return 0;
+}
+
+
+
+static DavQLToken* dav_parse_tokenize(cxstring src) {
+#define alloc_token() do {token = calloc(1, sizeof(DavQLToken));\
+        if(!token) {tokenlist_free(tokens_begin); return NULL;}} while(0)
+#define add_token() if(dav_parse_add_token(&tokens_begin, &tokens_end, token)) return NULL;
+    
+    DavQLToken *tokens_begin = NULL;
+    DavQLToken *tokens_end = NULL;
+    
+    DavQLToken *token = NULL;
+    
+    char insequence = '\0';
+    for (size_t i = 0 ; i < src.length ; i++) {
+        // quoted strings / identifiers are a single token
+        if (src.ptr[i] == '\'' || src.ptr[i] == '`') {
+            if (src.ptr[i] == insequence) {
+                // lookahead for escaped string quotes
+                if (src.ptr[i] == '\'' && i+2 < src.length &&
+                    src.ptr[i+1] == src.ptr[i] && src.ptr[i+2] == src.ptr[i]) {
+                    token->value.length += 3;
+                    i += 2;
+                } else {
+                    // add quoted token to list
+                    token->value.length++;
+                    add_token();
+                    token = NULL;
+                    insequence = '\0';
+                }
+            } else if (insequence == '\0') {
+                insequence = src.ptr[i];
+                // always create new token for quoted strings
+                if (token) {
+                    add_token();
+                }
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 1;
+            } else {
+                // add other kind of quotes to token
+                token->value.length++;
+            }
+        } else if (insequence) {
+            token->value.length++;
+        } else if (isspace(src.ptr[i])) {
+            // add token before spaces to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+        } else if (strchr(special_token_symbols, src.ptr[i])) {
+            // add token before special symbol to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+            // add special symbol as single token to list
+            alloc_token();
+            token->value.ptr = src.ptr + i;
+            token->value.length = 1;
+            add_token();
+            // set tokenizer ready to read more tokens
+            token = NULL;
+        } else {
+            // if this is a new token, create memory for it
+            if (!token) {
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 0;
+            }
+            // extend token length when reading more bytes
+            token->value.length++;
+        }
+    }
+    
+    if (token) {
+        add_token();
+    }
+    
+    alloc_token();
+    token->tokenclass = DAVQL_TOKEN_END;
+    token->value = CX_STR("");
+    
+    cx_linked_list_add((void**)&tokens_begin, (void**)&tokens_end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return tokens_begin;
+#undef alloc_token
+#undef add_token
+}
+
+static void dav_free_expression(DavQLExpression *expr) {
+    if (expr) {
+        if (expr->left) {
+            dav_free_expression(expr->left);
+        }
+        if (expr->right) {
+            dav_free_expression(expr->right);
+        }
+        free(expr);
+    }
+}
+
+static void dav_free_field(DavQLField *field) {
+    dav_free_expression(field->expr);
+    free(field);
+}
+
+static void dav_free_order_criterion(DavQLOrderCriterion *crit) {
+    if (crit->column) { // do it null-safe though column is expected to be set
+        dav_free_expression(crit->column);
+    }
+}
+
+#define token_is(token, expectedclass) (token && \
+    (token->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+    !cx_strcasecmp(token->value, cx_str(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,DavQLToken*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv,
+        exprparser_f parseR) {
+    
+    if (!token) {
+        return 0;
+    }
+    
+    int total_consumed = 0, consumed;
+    
+    // save temporarily on stack (copy to heap later on)
+    DavQLExpression left, right;
+    
+    // RULE:    LEFT, [Operator, RIGHT]
+    memset(&left, 0, sizeof(DavQLExpression));
+    consumed = parseL(stmt, token, &left);
+    if (!consumed || stmt->errorcode) {
+        return 0;
+    }
+    total_consumed += consumed;
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+    char *op;
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) &&
+            (op = strchr(opc, token_sstr(token).ptr[0]))) {
+        expr->op = opv[op-opc];
+        expr->type = DAVQL_BINARY;
+        total_consumed++;
+        token = token->next;
+        memset(&right, 0, sizeof(DavQLExpression));
+        consumed = parseR(stmt, token, &right);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+        total_consumed += consumed;
+    }
+    
+    if (expr->op == DAVQL_NOOP) {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    } else {
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &left, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &right, sizeof(DavQLExpression));
+        
+        expr->srctext.ptr = expr->left->srctext.ptr;
+        expr->srctext.length = 
+            expr->right->srctext.ptr -
+            expr->left->srctext.ptr + expr->right->srctext.length;
+    }
+    
+    return total_consumed;
+}
+
+static void fmt_args_add(DavQLStatement *stmt, void *data) {
+    if(!stmt->args) {
+        stmt->args = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    }
+    cxListAdd(stmt->args, data);
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, cxstring str) {
+    int placeholder = 0;
+    for (size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        if (placeholder) {
+            if (c != '%') {
+                fmt_args_add(stmt, (void*)(intptr_t)c);
+            }
+            placeholder = 0;
+        } else if (c == '%') {
+            placeholder = 1;
+        }
+    }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext = token_sstr(token);
+    if (token_is(token, DAVQL_TOKEN_NUMBER)) {
+        expr->type = DAVQL_NUMBER;
+    } else if (token_is(token, DAVQL_TOKEN_STRING)) {
+        expr->type = DAVQL_STRING;
+        // check for format specifiers and add args
+        dav_add_fmt_args(stmt, expr->srctext);
+    } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) {
+        expr->type = DAVQL_TIMESTAMP;
+    } else if (token_is(token, DAVQL_TOKEN_FMTSPEC)
+            && expr->srctext.length == 2) {
+        switch (expr->srctext.ptr[1]) {
+        case 'd': expr->type = DAVQL_NUMBER; break;
+        case 's': expr->type = DAVQL_STRING; break;
+        case 't': expr->type = DAVQL_TIMESTAMP; break;
+        default:
+            dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+                _error_invalid_fmtspec, stmt, token);
+            return 0;
+        }
+        // add fmtspec type to query arg list
+        fmt_args_add(stmt, (void*)(intptr_t)expr->srctext.ptr[1]);
+    } else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext.ptr = token_sstr(token).ptr;
+    expr->srctext.length = 0;
+    expr->left = expr->right = NULL; // in case we fail, we want them to be sane
+    
+    int total_consumed = 0;
+    
+    // RULE:    Expression, {",", Expression};
+    DavQLExpression *arglist = expr;
+    DavQLExpression arg;
+    const char *lastchar = expr->srctext.ptr;
+    int consumed;
+    do {
+        memset(&arg, 0, sizeof(DavQLExpression));
+        consumed = dav_parse_expression(stmt, token, &arg);
+        if (consumed) {
+            lastchar = arg.srctext.ptr + arg.srctext.length;
+            total_consumed += consumed;
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            // look ahead for a comma
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++;
+                token = token->next;
+                /* we have more arguments, so put the current argument to the
+                 * left subtree and create a new node to the right
+                 */
+                dqlsec_malloc(stmt, arglist->left, DavQLExpression);
+                memcpy(arglist->left, &arg, sizeof(DavQLExpression));
+                arglist->srctext.ptr = arg.srctext.ptr;
+                arglist->op = DAVQL_ARGLIST;
+                arglist->type = DAVQL_FUNCCALL;
+                dqlsec_mallocz(stmt, arglist->right, DavQLExpression);
+                arglist = arglist->right;
+            } else {
+                // this was the last argument, so write it to the current node
+                memcpy(arglist, &arg, sizeof(DavQLExpression));
+                consumed = 0;
+            }
+        }
+    } while (consumed && !stmt->errorcode);
+    
+    // recover source text
+    arglist = expr;
+    while (arglist && arglist->type == DAVQL_FUNCCALL) {
+        arglist->srctext.length = lastchar - arglist->srctext.ptr;
+        arglist = arglist->right;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_funccall(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    // RULE:    Identifier, "(", ArgumentList, ")";
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER) &&
+            token_is(token->next, DAVQL_TOKEN_OPENP)) {
+
+        expr->type = DAVQL_FUNCCALL;
+        expr->op = DAVQL_CALL;
+        
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->left->type = DAVQL_IDENTIFIER;
+        expr->left->srctext = token_sstr(token);
+        expr->right = NULL;
+        
+        token = token->next->next;
+        
+        DavQLExpression arg;
+        int argtokens = dav_parse_arglist(stmt, token, &arg);
+        if (stmt->errorcode) {
+            // if an error occurred while parsing the arglist, return now
+            return 2;
+        }
+        if (argtokens) {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), argtokens);
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &arg, sizeof(DavQLExpression));
+        } else {
+            // arg list may be empty
+            expr->right = NULL;
+        }
+        
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            return 3 + argtokens;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                stmt, token);
+            return 2; // it MUST be a function call, but it is invalid
+        }
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_unary_expr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    DavQLToken *firsttoken = token; // save for srctext recovery
+    
+    DavQLExpression* atom = expr;
+    int total_consumed = 0;
+   
+    // optional unary operator
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        char *op = strchr("+-~", token_sstr(token).ptr[0]);
+        if (op) {
+            expr->type = DAVQL_UNARY;
+            switch (*op) {
+            case '+': expr->op = DAVQL_ADD; break;
+            case '-': expr->op = DAVQL_SUB; break;
+            case '~': expr->op = DAVQL_NEG; break;
+            }
+            dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+            atom = expr->left;
+            total_consumed++;
+            token = token->next;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP,
+                _error_invalid_unary_op, stmt, token);
+            return 0;
+        }
+    }
+    
+    // RULE:    (ParExpression | AtomicExpression)
+    if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        token = token->next; total_consumed++;
+        // RULE:    "(", Expression, ")"
+        int consumed = dav_parse_expression(stmt, token, atom);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+                _error_invalid_expr, stmt, token);
+            return 0;
+        }
+        token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+        total_consumed += consumed;
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            token = token->next; total_consumed++;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR,
+                _error_missing_par, stmt, token);
+            return 0;
+        }
+    } else {
+        // RULE:    FunctionCall
+        int consumed = dav_parse_funccall(stmt, token, atom);
+        if (consumed) {
+            total_consumed += consumed;
+        } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+            // RULE:    Identifier
+            total_consumed++;
+            atom->type = DAVQL_IDENTIFIER;
+            atom->srctext = token_sstr(token);
+        } else {
+            // RULE:    Literal
+            total_consumed += dav_parse_literal(stmt, token, atom);
+        }
+    }
+    
+    // recover source text
+    expr->srctext.ptr = token_sstr(firsttoken).ptr;
+    if (total_consumed > 0) {
+        cxstring lasttoken =
+            token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed-1));
+        expr->srctext.length =
+            lasttoken.ptr - expr->srctext.ptr + lasttoken.length;
+    } else {
+        // the expression should not be used anyway, but we want to be safe
+        expr->srctext.length = 0;
+    }
+    
+    
+    return total_consumed;
+}
+
+static int dav_parse_bitexpr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_unary_expr,
+        "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR},
+        dav_parse_bitexpr);
+}
+
+static int dav_parse_multexpr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_bitexpr,
+        "*/", (int[]){DAVQL_MUL, DAVQL_DIV},
+        dav_parse_multexpr);
+}
+
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_multexpr,
+        "+-", (int[]){DAVQL_ADD, DAVQL_SUB},
+        dav_parse_expression);
+}
+
+static int dav_parse_named_field(DavQLStatement *stmt, DavQLToken *token,
+        DavQLField *field) {
+    int total_consumed = 0, consumed;
+    
+    // RULE:    Expression, " as ", Identifier;
+    DavQLExpression *expr;
+    dqlsec_mallocz(stmt, expr, DavQLExpression);
+    consumed = dav_parse_expression(stmt, token, expr);
+    if (stmt->errorcode) {
+        dav_free_expression(expr);
+        return 0;
+    }
+    if (expr->type == DAVQL_UNDEFINED_TYPE) {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+            _error_invalid_expr, stmt, token);
+        return 0;
+    }
+
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+    total_consumed += consumed;    
+    
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) {
+        token = token->next; total_consumed++;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_as, stmt, token);
+        return 0;
+    }
+
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+        field->name = token_sstr(token);
+        field->expr = expr;
+        return total_consumed + 1;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_identifier, stmt, token);
+        return 0;
+    }
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, DavQLToken *token) {
+    
+    // RULE:    "-"
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        return 1;
+    }
+    
+    // RULE:    "*", {",", NamedExpression}
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        
+        int total_consumed = 0;
+        int consumed = 1;
+        
+        do {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            total_consumed += consumed;
+            
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++; token = token->next;
+                DavQLField localfield;
+                consumed = dav_parse_named_field(stmt, token, &localfield);
+                if (!stmt->errorcode && consumed) {
+                    DavQLField *add_field;
+                    dqlsec_malloc(stmt, add_field, DavQLField);
+                    memcpy(add_field, &localfield, sizeof(DavQLField));
+                    if(dav_stmt_add_field(stmt, add_field)) {
+                        free(add_field);
+                        return 0;
+                    }
+                }                
+            } else {
+                consumed = 0;
+            }
+        } while (consumed > 0);
+        
+        return total_consumed;
+    }
+    
+    // RULE:    FieldExpression, {",", FieldExpression}
+    {
+        int total_consumed = 0, consumed;
+        do {
+            // RULE:    NamedField | Identifier
+            DavQLField localfield;
+            consumed = dav_parse_named_field(stmt, token, &localfield);
+            if (consumed) {
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                memcpy(field, &localfield, sizeof(DavQLField));
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+                token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+                total_consumed += consumed;
+            } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)
+                // look ahead, if the field is JUST the identifier
+                && (token_is(token->next, DAVQL_TOKEN_COMMA) ||
+                    tokenvalue_is(token->next, "from"))) {
+                
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+                field->expr->type = DAVQL_IDENTIFIER;
+                field->expr->srctext = field->name = token_sstr(token);
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+
+                consumed = 1;
+                total_consumed++;
+                token = token->next;
+                
+                // we found a valid solution, so erase any errors
+                stmt->errorcode = 0;
+                if (stmt->errormessage) {
+                    free(stmt->errormessage);
+                    stmt->errormessage = NULL;
+                }
+            } else {
+                // dav_parse_named_field has already thrown a good error
+                consumed = 0;
+            }
+            
+            // field has been parsed, now try to get a comma
+            if (consumed) {
+                consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+                if (consumed) {
+                    token = token->next;
+                    total_consumed++;
+                }
+            }
+        } while (consumed);
+
+        return total_consumed;
+    }
+}
+
+// forward declaration
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    expr->type = DAVQL_LOGICAL;
+    expr->srctext = token_sstr(token);
+    
+    int total_consumed = 0;
+
+    DavQLExpression bexpr;
+    memset(&bexpr, 0, sizeof(DavQLExpression));
+    total_consumed = dav_parse_expression(stmt, token, &bexpr);
+    if (!total_consumed || stmt->errorcode) {
+        return 0;
+    }
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed);
+
+    DavQLToken* optok = token;
+    // RULE:    Expression, (" like " | " unlike "), String
+    if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok,
+            "like") || tokenvalue_is(optok, "unlike"))) {
+
+        total_consumed++;
+        token = token->next;
+        if (token_is(token, DAVQL_TOKEN_STRING)) {
+            expr->op = tokenvalue_is(optok, "like") ?
+                DAVQL_LIKE : DAVQL_UNLIKE;
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+            dqlsec_mallocz(stmt, expr->right, DavQLExpression);
+            expr->right->type = DAVQL_STRING;
+            expr->right->srctext = token_sstr(token);
+            expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+            
+            // fmt args
+            dav_add_fmt_args(stmt, expr->right->srctext);
+
+            return total_consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_STRING,
+                _error_invalid_string, stmt, token);
+            return 0;
+        }        
+    }
+    // RULE:    Expression, Comparison, Expression
+    else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (
+            tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") ||
+            tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) {
+
+        total_consumed++;
+        token = token->next;
+
+        if (tokenvalue_is(optok, "=")) {
+            expr->op = DAVQL_EQ;
+        } else {
+            if (tokenvalue_is(token, "=")) {
+                if (tokenvalue_is(optok, "!")) {
+                    expr->op = DAVQL_NEQ;
+                } else if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LE;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GE;
+                }
+                total_consumed++;
+                token = token->next;
+            } else {
+                if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LT;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GT;
+                }
+            }
+        }
+
+        DavQLExpression rexpr;
+        memset(&rexpr, 0, sizeof(DavQLExpression));
+        int consumed = dav_parse_expression(stmt, token, &rexpr);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(
+                DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+
+        total_consumed += consumed;
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &rexpr, sizeof(DavQLExpression));
+        
+        expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+
+        return total_consumed;
+    }
+    // RULE:    FunctionCall | Identifier;
+    else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) {
+        memcpy(expr, &bexpr, sizeof(DavQLExpression));
+
+        return total_consumed;
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_bool_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    // RULE:    "not ", LogicalExpression
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) {
+        expr->type = DAVQL_LOGICAL;
+        expr->op = DAVQL_NOT;
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->srctext = token_sstr(token);
+
+        token = token->next;
+        int consumed = dav_parse_bool_expr(stmt, token, expr->left);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (consumed) {
+            cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed-1));
+            expr->srctext.length =
+                lasttok.ptr - expr->srctext.ptr + lasttok.length;
+            return consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+    }
+    // RULE:    "(", LogicalExpression, ")"
+    else if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        int consumed = dav_parse_logical_expr(stmt, token->next, expr);
+        if (consumed) {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+            if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+                token = token->next;
+                return consumed + 2;
+            } else {
+                dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                    stmt, token);
+                return 0;
+            }
+        } else {
+            // don't handle errors here, we can also try a boolean primary
+            stmt->errorcode = 0;
+            if (stmt->errormessage) {
+                free(stmt->errormessage);
+            }
+        }
+    }
+    
+    // RULE:    BooleanPrimary
+    return dav_parse_bool_prim(stmt, token, expr);
+}
+
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    DavQLToken *firsttoken = token;
+    int total_consumed = 0;
+    
+    // RULE:    BooleanLiteral, [LogicalOperator, LogicalExpression];
+    DavQLExpression left, right;
+    memset(&left, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_bool_expr(stmt, token, &left);
+    if (stmt->errorcode) {
+        return 0;
+    }
+    if (!consumed) {
+        dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+            _error_missing_expr, stmt, token);
+        return 0;
+    }
+    total_consumed += consumed;
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        expr->type = DAVQL_LOGICAL;
+
+        davqloperator_t op = DAVQL_NOOP;
+        if (tokenvalue_is(token, "and")) {
+            op = DAVQL_LAND;
+        } else if (tokenvalue_is(token, "or")) {
+            op = DAVQL_LOR;
+        } else if (tokenvalue_is(token, "xor")) {
+            op = DAVQL_LXOR;
+        }
+
+        if (op == DAVQL_NOOP) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP,
+                _error_invalid_logical_op, stmt, token);
+            return 0;
+        } else {
+            expr->op = op;
+            total_consumed++;
+            token = token->next;
+
+            memset(&right, 0, sizeof(DavQLExpression));
+            consumed = dav_parse_logical_expr(stmt, token, &right);
+            if (stmt->errorcode) {
+                return 0;
+            }
+            if (!consumed) {
+                dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                    _error_missing_expr, stmt, token);
+                return 0;
+            }
+            total_consumed += consumed;
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &left, sizeof(DavQLExpression));
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &right, sizeof(DavQLExpression));
+        }
+    } else {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    }
+    
+    // set type and recover source text
+    if (total_consumed > 0) {        
+        expr->srctext.ptr = token_sstr(firsttoken).ptr;
+        cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(firsttoken, 0, offsetof(DavQLToken, next), total_consumed-1));
+        expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, DavQLToken *token) {
+    dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+    
+    return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, DavQLToken *token) {
+
+    int total_consumed = 0;
+    
+    // RULE:    "depth", "=", (Number | "infinity")
+    if (tokenvalue_is(token, "depth")) {
+        token = token->next; total_consumed++;
+        if (tokenvalue_is(token, "=")) {
+            token = token->next; total_consumed++;
+            if (tokenvalue_is(token, "infinity")) {
+                stmt->depth = DAV_DEPTH_INFINITY;
+                token = token->next; total_consumed++;
+            } else {
+                DavQLExpression *depthexpr;
+                dqlsec_mallocz(stmt, depthexpr, DavQLExpression);
+                
+                int consumed = dav_parse_expression(stmt, token, depthexpr);
+
+                if (consumed) {
+                    if (depthexpr->type == DAVQL_NUMBER) {
+                        if (depthexpr->srctext.ptr[0] == '%') {
+                            stmt->depth = DAV_DEPTH_PLACEHOLDER;
+                        } else {
+                            cxstring depthstr = depthexpr->srctext;
+                            char *conv = malloc(depthstr.length+1);
+                            if (!conv) {
+                                dav_free_expression(depthexpr);
+                                stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+                                return 0;
+                            }
+                            char *chk;
+                            memcpy(conv, depthstr.ptr, depthstr.length);
+                            conv[depthstr.length] = '\0';
+                            stmt->depth = strtol(conv, &chk, 10);
+                            if (*chk || stmt->depth < -1) {
+                                dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                                    _error_invalid_depth, stmt, token);
+                            }
+                            free(conv);
+                        }
+                        total_consumed += consumed;
+                    } else {
+                        dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                            _error_invalid_depth, stmt, token);
+                    }
+                }
+                
+                dav_free_expression(depthexpr);
+            }
+        }
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_order_crit(DavQLStatement *stmt, DavQLToken *token,
+    DavQLOrderCriterion *crit) {
+    
+    // RULE:    (Identifier | Number), [" asc"|" desc"];
+    DavQLExpression expr;
+    memset(&expr, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_expression(stmt, token, &expr);
+    if (stmt->errorcode || !consumed) {
+        return 0;
+    }
+    
+    if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) {
+        dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION,
+            _error_invalid_order_criterion, stmt, token);
+        return 0;
+    }
+    
+    dqlsec_malloc(stmt, crit->column, DavQLExpression);
+    memcpy(crit->column, &expr, sizeof(DavQLExpression));
+    
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && (
+            tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) {
+        
+        crit->descending = tokenvalue_is(token, "desc");
+        
+        return consumed+1;
+    } else {
+        crit->descending = 0;
+        return consumed;
+    }
+}
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, DavQLToken *token) {
+    
+    int total_consumed = 0, consumed;
+    
+    DavQLOrderCriterion crit;
+    
+    if(!stmt->orderby) {
+        stmt->orderby = cxLinkedListCreateSimple(sizeof(DavQLOrderCriterion));
+        if(!stmt->orderby) {
+            return 0;
+        }
+    }
+    
+    // RULE:    OrderByCriterion, {",", OrderByCriterion};
+    do {
+        consumed = dav_parse_order_crit(stmt, token, &crit);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+        token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+        total_consumed += consumed;
+        
+        if(cxListAdd(stmt->orderby, &crit)) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 0;
+        }
+        
+        if (token_is(token, DAVQL_TOKEN_COMMA)) {
+            total_consumed++;
+            token = token->next;
+        } else {
+            consumed = 0;
+        }
+    } while (consumed);
+    
+    return total_consumed;
+}
+
+
+static int dav_parse_assignments(DavQLStatement *stmt, DavQLToken *token) {
+    
+    // RULE:    Assignment, {",", Assignment}
+    int total_consumed = 0, consumed;
+    do {
+        // RULE:    Identifier, "=", Expression
+        if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+
+            // Identifier
+            DavQLField *field;
+            dqlsec_malloc(stmt, field, DavQLField);
+            field->name = token_sstr(token);
+            total_consumed++;
+            token = token->next;
+            
+            // "="
+            if (!token_is(token, DAVQL_TOKEN_OPERATOR)
+                    || !tokenvalue_is(token, "=")) {
+                dav_free_field(field);
+                
+                dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN,
+                    _error_missing_assign, stmt, token);
+                return total_consumed;
+            }
+            total_consumed++;
+            token = token->next;
+
+            // Expression
+            dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+            consumed = dav_parse_expression(stmt, token, field->expr);
+            if (stmt->errorcode) {
+                dav_free_field(field);
+                return total_consumed;
+            }
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            total_consumed += consumed;
+            
+            // Add assignment to list and check if there's another one
+            if(dav_stmt_add_field(stmt, field)) {
+                free(field);
+                return 0;
+            }
+            consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+            if (consumed) {
+                token = token->next;
+                total_consumed++;
+            }
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                    _error_missing_identifier, stmt, token);
+            return total_consumed;
+        }
+    } while (consumed);
+
+    return total_consumed;
+}
+
+static int dav_parse_path(DavQLStatement *stmt, DavQLToken *tokens) {
+    if (token_is(tokens, DAVQL_TOKEN_STRING)) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        return 1;
+    } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR)
+            && tokenvalue_is(tokens, "/")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        int consumed = 1;
+        while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) &&
+                !token_is(tokens, DAVQL_TOKEN_END)) {
+            cxstring toksstr = token_sstr(tokens);
+            stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length;
+            tokens = tokens->next;
+            consumed++;
+        }
+        return consumed;
+    } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) &&
+            tokenvalue_is(tokens, "%s")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        fmt_args_add(stmt, (void*)(intptr_t)'s');
+        return 1;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_path, stmt, tokens);
+        return 0;
+    }
+}
+
+/**
+ * Parser of a select statement.
+ * @param stmt the statement object that shall contain the syntax tree
+ * @param tokens the token list
+ */
+static void dav_parse_select_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+    stmt->type = DAVQL_SELECT;
+
+    // Consume field list
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_fieldlist(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume FROM keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "from")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_from, stmt, tokens);
+        return;
+    }
+    
+    // Consume path
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    //dav_add_fmt_args(stmt, stmt->path); // add possible path args
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume where clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // useless, but the user may want to explicitly express his intent
+        tokens = tokens->next;
+        stmt->where = NULL;
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume order by clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "order")) {
+        tokens = tokens->next;
+        if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+                && tokenvalue_is(tokens, "by")) {
+            tokens = tokens->next;
+            tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+                dav_parse_orderby_clause(stmt, tokens));
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_by, stmt, tokens);
+            return;
+        }
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    
+    if (tokens) {
+        if (token_is(tokens, DAVQL_TOKEN_INVALID)) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN,
+                _error_invalid_token, stmt, tokens);
+        } else if (!token_is(tokens, DAVQL_TOKEN_END)) {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _error_unexpected_token, stmt, tokens);
+        }
+    }
+}
+
+static void dav_parse_set_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+    stmt->type = DAVQL_SET;
+    
+    // Consume assignments
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_assignments(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume AT keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "at")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_at, stmt, tokens);
+        return;
+    }
+
+    // Consume path
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume mandatory where clause (or anywhere keyword)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // no-op, but we want the user to be explicit about this
+        tokens = tokens->next;
+        stmt->where = NULL;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_where, stmt, tokens);
+        return;
+    }
+}
+
+DavQLStatement* dav_parse_statement(cxstring srctext) {
+    DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement));
+    
+    // if we can't even get enough memory for the statement object or an error
+    // message, we can simply die without returning anything
+    if (!stmt) {
+        return NULL;
+    }
+    char *oommsg = strdup(_error_out_of_memory);
+    if (!oommsg) {
+        free(stmt);
+        return NULL;
+    }
+    
+    // default values
+    stmt->type = -1;
+    stmt->depth = 1;
+    
+    // save trimmed source text
+    stmt->srctext = cx_strtrim(srctext);
+    
+    if (stmt->srctext.length) {   
+        // tokenization
+        DavQLToken* tokens = dav_parse_tokenize(stmt->srctext);
+
+        if (tokens) {
+            // use first token to determine query type
+
+            if (tokenvalue_is(tokens, "select")) {
+                dav_parse_select_statement(stmt, tokens->next);
+            } else if (tokenvalue_is(tokens, "set")) {
+                dav_parse_set_statement(stmt, tokens->next);
+            } else {
+                stmt->type = DAVQL_ERROR;
+                stmt->errorcode = DAVQL_ERROR_INVALID;
+                stmt->errormessage = strdup(_error_invalid);
+            }
+
+            // free token data
+            tokenlist_free(tokens);
+        } else {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+        }
+    } else {
+        stmt->type = DAVQL_ERROR;
+        stmt->errorcode = DAVQL_ERROR_INVALID;
+        stmt->errormessage = strdup(_error_invalid);
+    }
+    
+    if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) {
+        stmt->type = DAVQL_ERROR;
+        stmt->errormessage = oommsg;
+    } else {
+        free(oommsg);
+    }
+    
+    return stmt;
+}
+
+void dav_free_statement(DavQLStatement *stmt) {
+    if(stmt->fields) {
+        cxDefineDestructor(stmt->fields, dav_free_field);
+        cxListDestroy(stmt->fields);
+    }
+    
+    if (stmt->where) {
+        dav_free_expression(stmt->where);
+    }
+    if (stmt->errormessage) {
+        free(stmt->errormessage);
+    }
+    
+    if(stmt->orderby) {
+        cxDefineDestructor(stmt->orderby, dav_free_order_criterion);
+        cxListDestroy(stmt->orderby);
+    }
+    if(stmt->args) {
+        cxListDestroy(stmt->args);
+    }
+    free(stmt);
+}
diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h
new file mode 100644 (file)
index 0000000..7640b26
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * 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 DAVQLPARSER_H
+#define        DAVQLPARSER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <cx/string.h>
+#include <cx/list.h>
+
+/**
+ * Enumeration of possible statement types.
+ */
+typedef enum {DAVQL_ERROR, DAVQL_SELECT, DAVQL_SET} davqltype_t;
+
+/**
+ * Enumeration of possible token classes.
+ */
+typedef enum {
+    DAVQL_TOKEN_INVALID, DAVQL_TOKEN_KEYWORD,
+    DAVQL_TOKEN_IDENTIFIER, DAVQL_TOKEN_FMTSPEC,
+    DAVQL_TOKEN_STRING, DAVQL_TOKEN_NUMBER, DAVQL_TOKEN_TIMESTAMP,
+    DAVQL_TOKEN_COMMA, DAVQL_TOKEN_OPENP, DAVQL_TOKEN_CLOSEP,
+    DAVQL_TOKEN_OPERATOR, DAVQL_TOKEN_END
+} davqltokenclass_t;
+
+/**
+ * Enumeration of possible expression types.
+ */
+typedef enum {
+    DAVQL_UNDEFINED_TYPE,
+    DAVQL_NUMBER, DAVQL_STRING, DAVQL_TIMESTAMP, DAVQL_IDENTIFIER,
+    DAVQL_UNARY, DAVQL_BINARY, DAVQL_LOGICAL, DAVQL_FUNCCALL
+} davqlexprtype_t;
+
+/**
+ * Enumeration of possible expression operators.
+ */
+typedef enum {
+    DAVQL_NOOP, DAVQL_CALL, DAVQL_ARGLIST, // internal representations
+    DAVQL_ADD, DAVQL_SUB, DAVQL_MUL, DAVQL_DIV,
+    DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG,  // airthmetic
+    DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, // logical
+    DAVQL_EQ, DAVQL_NEQ, DAVQL_LT, DAVQL_GT, DAVQL_LE, DAVQL_GE,
+    DAVQL_LIKE, DAVQL_UNLIKE // comparisons
+} davqloperator_t;
+
+typedef struct DavQLToken DavQLToken;    
+struct DavQLToken {
+    davqltokenclass_t tokenclass;
+    cxstring value;
+    DavQLToken *prev;
+    DavQLToken *next;
+};
+
+/**
+ * An expression within a DAVQL query.
+ */
+typedef struct _davqlexpr DavQLExpression;
+
+/**
+ * The structure for type DavQLExpression.
+ */
+struct _davqlexpr {
+    /**
+     * The original expression text.
+     * Contains the literal value, if type is LITERAL.
+     */
+    cxstring srctext;
+    /**
+     * The expression type.
+     */
+    davqlexprtype_t type;
+    /**
+     * Operator.
+     */
+    davqloperator_t op;
+    /**
+     * Left or single operand.
+     * <code>NULL</code> for literals or identifiers.
+     */
+    DavQLExpression *left;
+    /**
+     * Right operand.
+     * <code>NULL</code> for literals, identifiers or unary expressions.
+     */
+    DavQLExpression *right;
+};
+
+/**
+ * A tuple representing an order criterion.
+ */
+typedef struct {
+    /**
+     * The column.
+     */
+    DavQLExpression *column;
+    /**
+     * True, if the result shall be sorted descending, false otherwise.
+     * Default is false (ascending).
+     */
+    _Bool descending;
+} DavQLOrderCriterion;
+
+/**
+ * A tuple representing a field.
+ */
+typedef struct {
+    /**
+     * The field name.
+     * <ul>
+     * <li>SELECT: the identifier or an alias name</li>
+     * <li>SET: the identifier</li>
+     * </ul>
+     */
+    cxstring name;
+    /**
+     * The field expression.
+     * <ul>
+     * <li>SELECT: the queried property (identifier) or an expression</li>
+     * <li>SET: the expression for the value to be set</li>
+     * </ul>
+     */
+    DavQLExpression *expr;
+} DavQLField;
+
+/**
+ * Query statement object.
+ * Contains the binary information about the parsed query.
+ * 
+ * The grammar for a DavQLStatement is:
+ * 
+ * <pre>
+ * Keyword = "select" | "set" | "from" | "at" | "as"
+ *         | "where" | "anywhere" | "like" | "unlike"
+ *         | "and" | "or" | "not" | "xor" | "with" | "infinity"
+ *         | "order" | "by" | "asc" | "desc";
+ * 
+ * Expression        = AddExpression;
+ * AddExpression     = MultExpression, [AddOperator, AddExpression];
+ * MultExpression    = BitwiseExpression, [MultOperator, MultExpression];
+ * BitwiseExpression = UnaryExpression, [BitwiseOperator, BitwiseExpression];
+ * UnaryExpression   = [UnaryOperator], (ParExpression | AtomicExpression);
+ * AtomicExpression  = FunctionCall | Identifier | Literal;
+ * ParExpression     = "(", Expression, ")";
+ * 
+ * BitwiseOperator = "&" | "|" | "^";
+ * MultOperator    = "*" | "/";
+ * AddOperator     = "+" | "-";
+ * UnaryOperator   = "+" | "-" | "~";
+ * 
+ * FunctionCall    = Identifier, "(", [ArgumentList], ")";
+ * ArgumentList    = Expression, {",", Expression};
+ * Identifier      = IdentifierChar - ?Digit?, {IdentifierChar}
+ *                 | "`", ?Character? - "`", {?Character? - "`"}, "`";
+ * IdentifierChar  = ?Character? - (" "|",");
+ * Literal         = Number | String | Timestamp;
+ * Number          = ?Digit?, {?Digit?} | "%d";
+ * String          = "'", {?Character? - "'" | "'''"} , "'" | "%s";
+ * Timestamp       = "%t"; // TODO: maybe introduce a real literal 
+ * 
+ * LogicalExpression = BooleanExpression, [LogicalOperator, LogicalExpression];
+ * BooleanExpression = "not ", BooleanExpression
+ *                   | "(", LogicalExpression, ")"
+ *                   | BooleanPrimary;
+ * BooleanPrimary    = Expression, (" like " | " unlike "), String
+ *                   | Expression, Comparison, Expression
+ *                   | FunctionCall | Identifier;
+ * 
+ * LogicalOperator = " and " | " or " | " xor ";
+ * Comparison      = | "=" | "<" | ">" | "<=" | ">=" | "!=";
+ * 
+ * FieldExpressions = "-"
+ *                  | "*", {",", NamedField}
+ *                  | FieldExpression, {",", FieldExpression};
+ * FieldExpression  = NamedField | Identifier;
+ * NamedField       = Expression, " as ", Identifier;
+ * 
+ * Assignments   = Assignment, {",", Assignment};
+ * Assignment    = Identifier, "=", Expression;
+ * 
+ * Path     = String
+ *          | "/", [PathNode, {"/", PathNode}], ["/"];
+ * PathNode = {{?Character? - "/"} - Keyword};
+ * 
+ * WithClause = "depth", "=", (Number | "infinity");
+ * 
+ * OrderByClause    = OrderByCriterion, {",", OrderByCriterion};
+ * OrderByCriterion = (Identifier | Number), [" asc"|" desc"];
+ * 
+ * </pre>
+ * 
+ * Note: mandatory spaces are part of the grammar. But you may also insert an
+ * arbitrary amount of optional spaces between two symbols if they are not part
+ * of an literal, identifier or the path.
+ * 
+ * <b>SELECT:</b>
+ * <pre>
+ * SelectStatement = "select ", FieldExpressions,
+ * " from ", Path,
+ * [" with ", WithClause],
+ * [(" where ", LogicalExpression) | " anywhere"],
+ * [" order by ", OrderByClause];
+  * </pre>
+ * 
+ * <b>SET:</b>
+ * <pre>
+ * SetStatement = "set ",Assignments,
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
+ * </pre>
+ * 
+ */
+typedef struct {
+    /**
+     * The original query text.
+     */
+    cxstring srctext;
+    /**
+     * The statement type.
+     */
+    davqltype_t type;
+    /**
+     * Error code, if any error occurred. Zero otherwise.
+     */
+    int errorcode;
+    /**
+     * Error message, if any error occurred.
+     */
+    char* errormessage;
+    /**
+     * The list of DavQLFields.
+     */
+    CxList* fields;
+    /**
+     * A string that denotes the queried path.
+     */
+    cxstring path;
+    /**
+     * Logical expression for selection.
+     * <code>NULL</code>, if there is no where clause.
+     */
+    DavQLExpression* where;
+    /**
+     * The list of DavQLOrderCriterions.
+     * This is <code>NULL</code> for SET queries and may be <code>NULL</code>
+     * if the result doesn't need to be sorted.
+     */
+    CxList* orderby;
+    /**
+     * The recursion depth for the statement.
+     * Defaults to 1.
+     * Magic numbers are DAV_DEPTH_INFINITY for infinity and
+     * DAV_DEPTH_PLACEHOLDER for a placeholder.
+     */
+    int depth;
+    /**
+     * A list of all required arguments
+     */
+    CxList* args;
+} DavQLStatement;
+
+/** Infinity recursion depth for a DavQLStatement. */
+#define DAV_DEPTH_INFINITY -1
+
+/** Depth needs to be specified at runtime. */
+#define DAV_DEPTH_PLACEHOLDER -2
+
+/** Unexpected token. */
+#define DAVQL_ERROR_UNEXPECTED_TOKEN 1
+
+/** A token has been found, for which no token class is applicable. */
+#define DAVQL_ERROR_INVALID_TOKEN 2
+
+/** A token that has been expected was not found. */
+#define DAVQL_ERROR_MISSING_TOKEN 11
+
+/** An expression has been expected, but was not found. */
+#define DAVQL_ERROR_MISSING_EXPR 12
+
+/** A closed parenthesis ')' is missing. */
+#define DAVQL_ERROR_MISSING_PAR 13
+
+/** An assignment operator '=' is missing. */
+#define DAVQL_ERROR_MISSING_ASSIGN 14
+
+/** The type of the expression could not be determined. */
+#define DAVQL_ERROR_INVALID_EXPR 21
+
+/** An operator has been found for an unary expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_UNARY_OP 22
+
+/** An operator has been found for a logical expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_LOGICAL_OP 23
+
+/** Invalid format specifier. */
+#define DAVQL_ERROR_INVALID_FMTSPEC 24
+
+/** A string has been expected. */
+#define DAVQL_ERROR_INVALID_STRING 25
+
+/** The order criterion is invalid (must be an identifier or field index). */
+#define DAVQL_ERROR_INVALID_ORDER_CRITERION 26
+
+/** The depth is invalid. */
+#define DAVQL_ERROR_INVALID_DEPTH 101
+
+/** Nothing about the statement seems legit. */
+#define DAVQL_ERROR_INVALID -1
+
+/** A call to malloc or calloc failed. */
+#define DAVQL_ERROR_OUT_OF_MEMORY -2
+
+/**
+ * Starts an interactive debugger for a DavQLStatement.
+ * 
+ * @param stmt the statement to debug
+ */
+void dav_debug_statement(DavQLStatement *stmt);
+
+/**
+ * Parses a statement.
+ * @param stmt the sstr_t containing the statement
+ * @return a DavQLStatement object
+ */
+DavQLStatement* dav_parse_statement(cxstring stmt);
+
+/**
+ * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement.
+ */
+#define dav_parse_cstr_statement(stmt) dav_parse_statement(cx_str(stmt))
+
+/**
+ * Frees a DavQLStatement.
+ * @param stmt the statement object to free
+ */
+void dav_free_statement(DavQLStatement *stmt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLPARSER_H */
+
diff --git a/libidav/methods.c b/libidav/methods.c
new file mode 100644 (file)
index 0000000..0a436c1
--- /dev/null
@@ -0,0 +1,1367 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+#include "methods.h"
+#include "crypto.h"
+#include "session.h"
+#include "xml.h"
+
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <cx/hash_map.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin) {
+    return cxBufferSeek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
+}
+
+/* ----------------------------- PROPFIND ----------------------------- */
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        CxBuffer *request,
+        CxBuffer *response)
+{
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND");
+    
+    // always try to get information about possible children
+    int depth = 1;
+    
+    int maxretry = 2;
+    
+    struct curl_slist *headers = NULL;
+    CURLcode ret = 0;
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    CxMap *respheaders = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    cxDefineDestructor(respheaders, free);
+    util_capture_header(handle, respheaders);
+    
+    for(int i=0;i<maxretry;i++) {
+        if (depth == 1) {
+            headers = curl_slist_append(headers, "Depth: 1");
+        } else if (depth == -1) {
+            headers = curl_slist_append(headers, "Depth: infinity");
+        } else {
+            headers = curl_slist_append(headers, "Depth: 0");
+        }
+        headers = curl_slist_append(headers, "Content-Type: text/xml");
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+        // reset buffers and perform request
+        request->pos = 0;
+        response->size = response->pos = 0;
+        ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+        curl_slist_free_all(headers);
+        headers = NULL;
+        
+        /*
+         * Handle two cases:
+         * 1. We communicate with IIS and get a X-MSDAVEXT_Error: 589831
+         *    => try with depth 0 next time, it's not a collection
+         * 2. Other cases
+         *    => the server handled our request and we can stop requesting
+         */
+        char *msdavexterror;
+        msdavexterror = cxMapGet(respheaders, cx_hash_key_str("x-msdavext_error"));
+        int iishack =  depth == 1 &&
+            msdavexterror && !strncmp(msdavexterror, "589831;", 7);
+        
+        if(iishack) {
+            depth = 0;
+        } else {
+            break;
+        }
+    }
+    
+    // deactivate header capturing and free captured map
+    util_capture_header(handle, NULL);
+    cxMapDestroy(respheaders);
+       
+    return ret;
+}
+
+CxBuffer* create_allprop_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:allprop/></D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+CxBuffer* create_cryptoprop_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    int add_crypto_name = 1;
+    int add_crypto_key = 1;
+    int add_crypto_hash = 1;
+    char *crypto_ns = "idav";
+    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    if(properties) {
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavProperty*, p, i) {
+            if(strcmp(p->ns->name, "DAV:")) {
+                cxMapPut(namespaces, cx_hash_key_str(p->ns->prefix), p->ns);
+            }
+
+            // if the properties list contains the idav properties crypto-name
+            // and crypto-key, mark them as existent 
+            if(!strcmp(p->ns->name, DAV_NS)) {
+                if(!strcmp(p->name, "crypto-name")) {
+                    add_crypto_name = 0;
+                    crypto_ns = p->ns->prefix;
+                } else if(!strcmp(p->name, "crypto-key")) {
+                    add_crypto_key = 0;
+                    crypto_ns = p->ns->prefix;
+                } else if(!strcmp(p->name, "crypto-hash")) {
+                    add_crypto_hash = 0;
+                    crypto_ns = p->ns->prefix;
+                }
+            }
+        }
+    }
+    
+    DavNamespace idav_ns;
+    if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn) && !nocrypt) {
+        idav_ns.prefix = "idav";
+        idav_ns.name = DAV_NS;
+        cxMapPut(namespaces, cx_hash_key_str("idav"), &idav_ns);
+    }
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    cx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
+    
+    CxIterator mapi = cxMapIteratorValues(namespaces);
+    cx_foreach(DavNamespace*, ns, mapi) {
+        s = CX_STR(" xmlns:");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(ns->prefix);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("=\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(ns->name);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    s = CX_STR(">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // default properties
+    s = CX_STR("<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:creationdate />\n<D:getlastmodified />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:getcontentlength />\n<D:getcontenttype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:resourcetype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // crypto properties
+    if(DAV_CRYPTO(sn) && !nocrypt) {
+        if(add_crypto_name) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-name />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_key) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-key />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_hash) {
+            cxBufferPut(buf, '<');
+            cxBufferPutString(buf, crypto_ns);
+            s = CX_STR(":crypto-hash />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // extra properties
+    if(properties) {
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavProperty*, prop, i) {
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prop->ns->prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prop->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(" />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // end
+    cx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
+    
+    cxMapDestroy(namespaces);
+    return buf;
+}
+
+CxBuffer* create_basic_propfind_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
+    cxBufferWrite(s.ptr, 1, s.length, buf);  
+    s = CX_STR(DAV_NS);
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("\" >\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // properties
+    s = CX_STR("<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<D:resourcetype />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-key />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-name />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("<i:crypto-hash />\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    s = CX_STR("</D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // end
+    s = CX_STR("</D:propfind>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+PropfindParser* create_propfind_parser(CxBuffer *response, char *url) {
+    PropfindParser *parser = malloc(sizeof(PropfindParser));
+    if(!parser) {
+        return NULL;
+    }
+    parser->document = xmlReadMemory(response->space, response->pos, url, NULL, 0);
+    parser->current = NULL;
+    if(parser->document) {
+        xmlNode *xml_root = xmlDocGetRootElement(parser->document);
+        if(xml_root) {
+            xmlNode *node = xml_root->children;
+            while(node) {
+                // find first response tag
+                if(node->type == XML_ELEMENT_NODE) {
+                    if(xstreq(node->name, "response")) {
+                        parser->current = node;
+                        break;
+                    }
+                }
+                node = node->next;
+            }
+            return parser;
+        } else {
+            xmlFreeDoc(parser->document);
+        }
+    }
+    free(parser);
+    return NULL;
+}
+
+void destroy_propfind_parser(PropfindParser *parser) {
+    if(parser->document) {
+        xmlFreeDoc(parser->document);
+    }
+    free(parser);
+}
+
+int get_propfind_response(PropfindParser *parser, ResponseTag *result) {
+    if(parser->current == NULL) {
+        return 0;
+    }
+    
+    char *href = NULL;
+    int iscollection = 0;
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    result->properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
+    
+    xmlNode *node = parser->current->children;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "href")) {
+                xmlNode *href_node = node->children;
+                if(href_node->type != XML_TEXT_NODE) {
+                    // error
+                    return -1;
+                }
+                href = (char*)href_node->content;
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int ok = 0;
+                // get the status code
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        if(xstreq(n->name, "prop")) {
+                            prop_node = n;
+                        } else if(xstreq(n->name, "status")) {
+                            xmlNode *status_node = n->children;
+                            if(status_node->type != XML_TEXT_NODE) {
+                                // error
+                                return -1;
+                            }
+                            cxstring status_str = cx_str((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                // error
+                                return -1;
+                            }
+                            status_str = cx_strsubsl(status_str, 9, 3);
+                            if(!cx_strcmp(status_str, CX_STR("200"))) {
+                                ok = 1;
+                            }
+                        }
+                    }    
+                    n = n->next;
+                }
+                // if status is ok, get all properties
+                if(ok) {
+                    n = prop_node->children;
+                    while(n) {
+                        if(n->type == XML_ELEMENT_NODE) {
+                            cxListAdd(result->properties, n);
+                            if(xstreq(n->name, "resourcetype")) {
+                                if(parse_resource_type(n)) {
+                                    iscollection = TRUE;
+                                }
+                            } else if(xstreq(n->ns->href, DAV_NS)) {
+                                if(xstreq(n->name, "crypto-name")) {
+                                    crypto_name = util_xml_get_text(n);
+                                } else if(xstreq(n->name, "crypto-key")) {
+                                    crypto_key = util_xml_get_text(n);
+                                }
+                            }
+                        }
+                        n = n->next;
+                    }
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    result->href = util_url_path(href);
+    result->iscollection = iscollection;
+    result->crypto_name = crypto_name;
+    result->crypto_key = crypto_key;
+    
+    // find next response tag
+    xmlNode *next = parser->current->next;
+    while(next) {
+        if(next->type == XML_ELEMENT_NODE) {
+            if(xstreq(next->name, "response")) {
+                break;
+            }
+        }
+        next = next->next;
+    }
+    parser->current = next;
+    
+    return 1;
+}
+
+void cleanup_response(ResponseTag *result) {
+    if(result) {
+        cxListDestroy(result->properties);
+    }
+}
+
+int hrefeq(DavSession *sn, const char *href1, const char *href2) {
+    cxmutstr href_s = cx_mutstr(util_url_decode(sn, href1));
+    cxmutstr href_r = cx_mutstr(util_url_decode(sn, href2));
+    int ret = 0;
+    if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+        ret = 1;
+    } else if(href_s.length == href_r.length + 1) {
+        if(href_s.ptr[href_s.length-1] == '/') {
+            href_s.length--;
+            if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+                ret = 1;
+            }
+        }
+    } else if(href_r.length == href_s.length + 1) {
+        if(href_r.ptr[href_r.length-1] == '/') {
+            href_r.length--;
+            if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+                ret = 1;
+            }
+        }
+    } 
+
+    free(href_s.ptr);
+    free(href_r.ptr);
+    
+    return ret;
+}
+
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response) {
+    char *url = NULL;
+    curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url);
+    if(!root) {
+        printf("methods.c: TODO: remove\n");
+        root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove
+    }
+    
+    //printf("%.*s\n\n", response->size, response->space);
+    xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0);
+    if(!doc) {
+        // TODO: free stuff
+        sn->error = DAV_ERROR;
+        return NULL;
+    }
+    
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    xmlNode *node = xml_root->children;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "response")) {
+                parse_response_tag(root, node);
+            }
+        }
+        node = node->next;
+    }
+    xmlFreeDoc(doc);
+    
+    return root;
+}
+
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path) {
+    // create resource
+    char *name = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_NAME(sn) && response->crypto_name && (key = dav_context_get_key(sn->context, response->crypto_key))) {
+        if(!response->crypto_key) {
+            sn->error = DAV_ERROR;
+            dav_session_set_errstr(sn, "Missing crypto-key property");
+            return NULL;
+        }
+        name = util_decrypt_str_k(sn, response->crypto_name, key);
+        if(!name) {
+            sn->error = DAV_ERROR;
+            dav_session_set_errstr(sn, "Cannot decrypt resource name");
+            return NULL;
+        }
+    } else {
+        cxstring resname = cx_str(util_resource_name(response->href));
+        int nlen = 0;
+        char *uname = curl_easy_unescape(
+                sn->handle,
+                resname.ptr,
+                resname.length,
+                &nlen);
+        name = dav_session_strdup(sn, uname);
+        curl_free(uname);
+    }
+
+    char *href = dav_session_strdup(sn, response->href);
+    DavResource *res = NULL;
+    if(parent_path) {
+        res = dav_resource_new_full(sn, parent_path, name, href);
+    } else {
+        res = dav_resource_new_href(sn, href);
+    }
+    dav_session_free(sn, name);
+    
+    add_properties(res, response); 
+    return res;
+}
+
+void add_properties(DavResource *res, ResponseTag *response) {
+    res->iscollection = response->iscollection;
+    
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    char *crypto_key = NULL;
+    
+    // add properties
+    if(response->properties) {
+        CxIterator i = cxListIterator(response->properties);
+        cx_foreach(xmlNode*, prop, i) {
+            resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+        
+            if (decrypt_props &&
+                prop->children &&
+                prop->children->type == XML_TEXT_NODE &&
+                xstreq(prop->ns->href, DAV_NS))
+            {
+                if(xstreq(prop->name, "crypto-prop")) {
+                    crypto_prop = prop;
+                } else if(xstreq(prop->name, "crypto-key")) {
+                    crypto_key = util_xml_get_text(prop);
+                }
+            }
+        }
+    }
+    
+    if(crypto_prop && crypto_key) {
+        char *crypto_prop_content = util_xml_get_text(crypto_prop);
+        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+        if(crypto_prop_content) {
+            CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+            resource_set_crypto_properties(res, cprops);
+        }
+    }
+    
+    set_davprops(res);
+}
+
+int parse_response_tag(DavResource *resource, xmlNode *node) {
+    DavSession *sn = resource->session;
+    
+    //DavResource *res = resource;
+    DavResource *res = NULL;
+    const char *href = NULL;
+    CxList *properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    int iscollection = 0; // TODO: remove
+    
+    node = node->children;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "href")) {
+                xmlNode *href_node = node->children;
+                if(href_node->type != XML_TEXT_NODE) {
+                    // error
+                    sn->error = DAV_ERROR;
+                    return 1;
+                }
+                //char *href = (char*)href_node->content;
+                href = util_url_path((const char*)href_node->content);
+                
+                char *href_s = util_url_decode(resource->session, href);
+                char *href_r = util_url_decode(resource->session, resource->href);
+                
+                if(hrefeq(sn, href_s, href_r)) {
+                    res = resource;
+                }   
+                
+                free(href_s);
+                free(href_r);
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int ok = 0;
+                // get the status code
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        if(xstreq(n->name, "prop")) {
+                            prop_node = n;
+                        } else if(xstreq(n->name, "status")) {
+                            xmlNode *status_node = n->children;
+                            if(status_node->type != XML_TEXT_NODE) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            cxstring status_str = cx_str((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            status_str = cx_strsubsl(status_str, 9, 3);
+                            if(!cx_strcmp(status_str, CX_STR("200"))) {
+                                ok = 1;
+                            }
+                        }
+                    }    
+                    n = n->next;
+                }
+                // if status is ok, get all properties
+                if(ok) {
+                    n = prop_node->children;
+                    while(n) {
+                        if(n->type == XML_ELEMENT_NODE) {
+                            cxListAdd(properties, n);
+                            if(xstreq(n->name, "resourcetype")) {
+                                if(parse_resource_type(n)) {
+                                    iscollection = TRUE;
+                                }
+                            } else if(n->ns && xstreq(n->ns->href, DAV_NS)) {
+                                if(xstreq(n->name, "crypto-name")) {
+                                    crypto_name = util_xml_get_text(n);
+                                } else if(xstreq(n->name, "crypto-key")) {
+                                    crypto_key = util_xml_get_text(n);
+                                }
+                            }
+                        }
+                        n = n->next;
+                    }
+                }
+            }
+        }
+        
+        node = node->next;
+    }
+    
+    if(!res) {
+        // create new resource object
+        char *name = NULL;
+        if(DAV_DECRYPT_NAME(sn) && crypto_name) {
+            if(!crypto_key) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Missing crypto-key property");
+                return -1;
+            }
+            name = util_decrypt_str(sn, crypto_name, crypto_key);
+            if(!name) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Cannot decrypt resource name");
+                return -1;
+            }
+        } else {
+            cxstring resname = cx_str(util_resource_name(href));
+            int nlen = 0;
+            char *uname = curl_easy_unescape(
+                    sn->handle,
+                    resname.ptr,
+                    resname.length,
+                    &nlen);
+            name = dav_session_strdup(sn, uname);
+            curl_free(uname);
+        }
+        
+        char *href_cp = dav_session_strdup(sn, href);
+        res = dav_resource_new_full(sn, resource->path, name, href_cp);
+        
+        dav_session_free(sn, name);
+    }
+    res->iscollection = iscollection;
+    
+    // add properties
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    
+    CxIterator i = cxListIterator(properties);
+    cx_foreach(xmlNode*, prop, i) {
+        if(!prop->ns) {
+            continue;
+        }
+        resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+        
+        if (decrypt_props &&
+            prop->children &&
+            prop->children->type == XML_TEXT_NODE &&
+            xstreq(prop->ns->href, DAV_NS))
+        {
+            if(xstreq(prop->name, "crypto-prop")) {
+                crypto_prop = prop;
+            }
+        }
+    }
+    cxListDestroy(properties);
+    
+    if(crypto_prop && crypto_key) {
+        char *crypto_prop_content = util_xml_get_text(crypto_prop);
+        DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+        if(crypto_prop_content && key) {
+            CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+            resource_set_crypto_properties(res, cprops);
+        }
+    }
+    
+    
+    set_davprops(res);
+    if(res != resource) {
+        resource_add_child(resource, res);
+    }
+    
+    return 0;
+}
+
+void set_davprops(DavResource *res) {
+    char *cl = dav_get_string_property_ns(res, "DAV:", "getcontentlength");
+    char *ct = dav_get_string_property_ns(res, "DAV:", "getcontenttype");
+    char *cd = dav_get_string_property_ns(res, "DAV:", "creationdate");
+    char *lm = dav_get_string_property_ns(res, "DAV:", "getlastmodified");
+    
+    res->contenttype = ct;
+    if(cl) {
+        char *end = NULL;
+        res->contentlength = strtoull(cl, &end, 0);
+    }
+    res->creationdate = util_parse_creationdate(cd);
+    res->lastmodified = util_parse_lastmodified(lm);
+}
+
+int parse_resource_type(xmlNode *node) {
+    int collection = FALSE;
+    xmlNode *c = node->children;
+    while(c) {
+        if(c->type == XML_ELEMENT_NODE) {
+            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, "collection")) {
+                collection = TRUE;
+                break;
+            }
+        }
+        c = c->next;
+    }
+    return collection;
+}
+
+
+/* ----------------------------- PROPPATCH ----------------------------- */
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        CxBuffer *request,
+        CxBuffer *response)
+{       
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH");
+    
+    struct curl_slist *headers = NULL;
+    headers = curl_slist_append(headers, "Content-Type: text/xml"); 
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    cxBufferSeek(request, 0, SEEK_SET);
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    curl_slist_free_all(headers);
+    
+    //printf("proppatch: \n%.*s\n", request->size, request->space);
+    
+    return ret;
+}
+
+CxBuffer* create_proppatch_request(DavResourceData *data) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    cxDefineDestructor(namespaces, free);
+
+    {
+        char prefix[8];
+        int pfxnum = 0;
+        if (data->set && cxListSize(data->set) > 0) {
+            CxIterator i = cxListIterator(data->set);
+            cx_foreach(DavProperty*, p, i) {
+                if (strcmp(p->ns->name, "DAV:")) {
+                    snprintf(prefix, 8, "x%d", pfxnum++);
+                    cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+                }
+            }
+        }
+        if (data->remove && cxListSize(data->remove) > 0) {
+            CxIterator i = cxListIterator(data->remove);
+            cx_foreach(DavProperty*, p, i) {
+                if (strcmp(p->ns->name, "DAV:")) {
+                    snprintf(prefix, 8, "x%d", pfxnum++);
+                    cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+                }
+            }
+        }
+    }
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\"");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    CxIterator mapi = cxMapIterator(namespaces);
+    cx_foreach(CxMapEntry*, entry, mapi) {
+        s = CX_STR(" xmlns:");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_str(entry->value);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("=\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = cx_strn(entry->key->data, entry->key->len);
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        s = CX_STR("\"");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    s = CX_STR(">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(data->set) {
+        s = CX_STR("<D:set>\n<D:prop>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        CxIterator i = cxListIterator(data->set);
+        cx_foreach(DavProperty*, property, i) {
+            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+            if(!prefix) {
+                prefix = "D";
+            }
+            
+            // begin tag
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(">");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            
+            // content
+            DavXmlNode *content = property->value;
+            if(content->type == DAV_XML_TEXT && !content->next) {
+                cxBufferWrite(content->content, 1, content->contentlength, buf);
+            } else {
+                dav_print_node(buf, (cx_write_func)cxBufferWrite, namespaces, content);
+            }
+            
+            // end tag
+            s = CX_STR("</");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(">\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        s = CX_STR("</D:prop>\n</D:set>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    if(data->remove) {
+        s = CX_STR("<D:remove>\n<D:prop>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        CxIterator i = cxListIterator(data->remove);
+        cx_foreach(DavProperty*, property, i) {
+            char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+            
+            s = CX_STR("<");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(prefix);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(":");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = cx_str(property->name);
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+            s = CX_STR(" />\n");
+            cxBufferWrite(s.ptr, 1, s.length, buf);
+        }
+        s = CX_STR("</D:prop>\n</D:remove>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("</D:propertyupdate>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    // cleanup namespace map
+    cxMapDestroy(namespaces);
+    
+    return buf;
+}
+
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:set>\n<D:prop>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(DAV_ENCRYPT_NAME(sn)) {
+        s = CX_STR("<idav:crypto-name>");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        char *crname = aes_encrypt(name, strlen(name), key);
+        cxBufferPutString(buf, crname);
+        free(crname);
+        s = CX_STR("</idav:crypto-name>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("<idav:crypto-key>");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    cxBufferPutString(buf, key->name);
+    s = CX_STR("</idav:crypto-key>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    if(hash) {
+        s = CX_STR("<idav:crypto-hash>");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+        cxBufferPutString(buf, hash);
+        s = CX_STR("</idav:crypto-hash>\n");
+        cxBufferWrite(s.ptr, 1, s.length, buf);
+    }
+    
+    s = CX_STR("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+/* ----------------------------- PUT ----------------------------- */
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+    //fwrite(buf, s, n, stdout);
+    return s*n;
+}
+
+CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, dav_read_func read_func, dav_seek_func seek_func, size_t length) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+    
+    // clear headers
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = NULL;
+        if(create) {
+            url = util_parent_path(url);
+            ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+            free(url);
+        } else {
+            ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        }
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CxBuffer *buf = NULL;
+    if(!read_func) {
+        buf = cxBufferCreate(data, length, cxDefaultAllocator, 0);
+        buf->size = length;
+        data = buf;
+        read_func = (dav_read_func)cxBufferRead;
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+    } else if(length == 0) {
+        headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)-1);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, seek_func);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, data);
+    curl_easy_setopt(handle, CURLOPT_READDATA, data);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    if(buf) {
+        cxBufferFree(buf);
+    }
+    
+    return ret;
+}
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response) { 
+    CURL *handle = sn->handle;
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    return ret;
+}
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock) {
+    CURL *handle = sn->handle;
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        url = util_parent_path(url);
+        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        free(url);
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL");
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    return ret;
+}
+
+
+CURLcode do_head_request(DavSession *sn) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "HEAD");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
+    
+    // clear headers
+    struct curl_slist *headers = NULL;
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_easy_setopt(handle, CURLOPT_NOBODY, 0L);
+    return ret;
+}
+
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override) { 
+    CURL *handle = sn->handle;
+    if(copy) {
+        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "COPY");
+    } else {
+        curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MOVE");
+    }
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    struct curl_slist *headers = NULL;
+    if(lock) {
+        char *url = NULL;
+        curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+        char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+    }
+    //cxstring deststr = ucx_sprintf("Destination: %s", dest);
+    cxmutstr deststr = cx_strcat(2, CX_STR("Destination: "), cx_str(dest));
+    headers = curl_slist_append(headers, deststr.ptr);
+    if(override) {
+        headers = curl_slist_append(headers, "Overwrite: T");
+    } else {
+        headers = curl_slist_append(headers, "Overwrite: F");
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    free(deststr.ptr);
+    curl_slist_free_all(headers);
+    headers = NULL;
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    return ret;
+}
+
+
+CxBuffer* create_lock_request(void) {
+    CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxstring s;
+    
+    s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("<D:lockinfo xmlns:D=\"DAV:\">\n"
+          "<D:lockscope><D:exclusive/></D:lockscope>\n"
+          "<D:locktype><D:write/></D:locktype>\n"
+          "<D:owner><D:href>http://davutils.org/libidav/</D:href></D:owner>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    s = CX_STR("</D:lockinfo>\n");
+    cxBufferWrite(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock) {
+    lock->locktoken = NULL;
+    lock->timeout = NULL;
+    
+    xmlDoc *doc = xmlReadMemory(response->space, response->size, NULL, NULL, 0);
+    if(!doc) {
+        sn->error = DAV_ERROR;
+        return -1;
+    }
+    
+    char *timeout = NULL;
+    char *locktoken = NULL;
+    
+    int ret = -1;
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    DavBool lockdiscovery = 0;
+    if(xml_root) {
+        xmlNode *node = xml_root->children;
+        while(node) {
+            if(node->type == XML_ELEMENT_NODE) {
+                if(xstreq(node->name, "lockdiscovery")) {
+                    node = node->children;
+                    lockdiscovery = 1;
+                    continue;
+                }
+                
+                if(xstreq(node->name, "activelock")) {
+                    node = node->children;
+                    continue;
+                }
+                
+                if(lockdiscovery) {
+                    if(xstreq(node->name, "timeout")) {
+                        timeout = util_xml_get_text(node);
+                    } else if(xstreq(node->name, "locktoken")) {
+                        xmlNode *n = node->children;
+                        while(n) {
+                            if(xstreq(n->name, "href")) {
+                                locktoken = util_xml_get_text(n);
+                                break;
+                            }
+                            n = n->next;
+                        }
+                    }
+                }
+            }
+            node = node->next;
+        }
+    }
+    
+    if(timeout && locktoken) {
+        lock->timeout = strdup(timeout);
+        lock->locktoken = strdup(locktoken);
+        ret = 0;
+    }
+    
+    xmlFreeDoc(doc);
+    return ret;
+}
+
+CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout) { 
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK");  
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+    request->pos = 0;
+    
+    // clear headers    
+    struct curl_slist *headers = NULL;
+    
+    if(timeout != 0) {
+        cxmutstr thdr;
+        if(timeout < 0) {
+            thdr = cx_asprintf("%s", "Timeout: Infinite");
+        } else {
+            thdr = cx_asprintf("Timeout: Second-%u", (unsigned int)timeout);
+        }
+        headers = curl_slist_append(headers, thdr.ptr);
+        free(thdr.ptr);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    
+    if(headers) {
+        curl_slist_free_all(headers);
+    }
+    
+    return ret;
+}
+
+CURLcode do_unlock_request(DavSession *sn, char *locktoken) {   
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "UNLOCK");
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    // set lock-token header
+    cxmutstr ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
+    struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr);
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    curl_slist_free_all(headers);
+    free(ltheader.ptr);
+    
+    return ret;
+}
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+    
+    // set lock-token header
+    cxmutstr ltheader;
+    struct curl_slist *headers = NULL;
+    if(locktoken) {
+        ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
+        headers = curl_slist_append(NULL, ltheader.ptr);
+    }
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    CURLcode ret = dav_session_curl_perform(sn, NULL);
+    if(locktoken) {
+        curl_slist_free_all(headers);
+        free(ltheader.ptr);
+    }
+    
+    return ret;
+}
+
+
+CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response) {
+    CURL *handle = sn->handle;
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT");
+    
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); 
+    curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request);
+    curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    struct curl_slist *headers = NULL;
+    headers = curl_slist_append(headers, "Content-Type: text/xml");
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+    
+    request->pos = 0;
+    response->size = response->pos = 0;
+    CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+    
+    curl_slist_free_all(headers);
+    
+    return ret;
+}
diff --git a/libidav/methods.h b/libidav/methods.h
new file mode 100644 (file)
index 0000000..399ce1c
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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 METHODS_H
+#define        METHODS_H
+
+#include "webdav.h"
+#include "resource.h"
+
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct PropfindParser PropfindParser;
+typedef struct ResponseTag    ResponseTag;
+typedef struct LockDiscovery  LockDiscovery;
+
+struct PropfindParser {
+    xmlDoc *document;
+    xmlNode *current;
+};
+
+struct ResponseTag {
+    const char *href;
+    int        iscollection;
+    CxList     *properties;
+    const char *crypto_name;
+    const char *crypto_key;
+};
+
+struct LockDiscovery {
+    char    *timeout;
+    char    *locktoken;
+};
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin);
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        CxBuffer *request,
+        CxBuffer *response);
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        CxBuffer *request,
+        CxBuffer *response);
+
+CURLcode do_put_request(
+        DavSession *sn,
+        char *lock,
+        DavBool create,
+        void *data,
+        dav_read_func read_func,
+        dav_seek_func seek_func,
+        size_t length);
+
+CxBuffer* create_allprop_propfind_request(void);
+CxBuffer* create_cryptoprop_propfind_request(void);
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt);
+CxBuffer* create_basic_propfind_request(void);
+
+PropfindParser* create_propfind_parser(CxBuffer *response, char *url);
+void destroy_propfind_parser(PropfindParser *parser);
+int get_propfind_response(PropfindParser *parser, ResponseTag *result);
+void cleanup_response(ResponseTag *result);
+
+int hrefeq(DavSession *sn, const char *href1, const char *href2);
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path);
+void add_properties(DavResource *res, ResponseTag *response);
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response);
+int parse_response_tag(DavResource *resource, xmlNode *node);
+void set_davprops(DavResource *res);
+
+/*
+ * parses the content of a resourcetype element
+ * returns 1 if the resourcetype is a collection, 0 otherwise
+ */
+int parse_resource_type(xmlNode *node);
+
+CxBuffer* create_proppatch_request(DavResourceData *data);
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash);
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response);
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock);
+
+CURLcode do_head_request(DavSession *sn);
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override);
+
+CxBuffer* create_lock_request(void);
+int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock);
+CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout);
+CURLcode do_unlock_request(DavSession *sn, char *locktoken);
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken);
+
+CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METHODS_H */
+
diff --git a/libidav/pwdstore.c b/libidav/pwdstore.c
new file mode 100644 (file)
index 0000000..466226b
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pwdstore.h"
+
+#include "utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+
+#ifdef _WIN32
+#include <winsock.h>
+#pragma comment(lib, "Ws2_32.lib")
+#else
+#include <netinet/in.h>
+#endif
+
+
+static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput;
+static void *pw_input_data = "Master password: ";
+
+char * pwdstore_default_pwinput(char *prompt) {
+    return util_password_input(prompt);
+}
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata) {
+    pw_input = func;
+    pw_input_data = userdata;
+}
+
+PwdStore* pwdstore_open(const char *file) {
+    FILE *in = fopen(file, "r");
+    if(!in) {
+        return NULL;
+    }
+    
+    CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+    fclose(in);
+    
+    if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) {
+        cxBufferFree(buf);
+        return NULL;
+    }
+    
+    PwdStore *p = malloc(sizeof(PwdStore));
+    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->content = buf;
+    p->key = NULL;
+    p->unlock_cmd = NULL;
+    p->lock_cmd = NULL;
+    p->encoffset = PWDS_HEADER_SIZE;
+    p->isdecrypted = 0;
+    
+    if(pwdstore_getindex(p)) {
+        pwdstore_free(p);
+        return NULL;
+    }
+    
+    return p;
+}
+
+PwdStore* pwdstore_new(void) {
+    PwdStore *p = calloc(1, sizeof(PwdStore));
+    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    PWDS_MAGIC(p) = PWDS_MAGIC_CHAR;
+    PWDS_VERSION(p) = 1;
+    PWDS_ENC(p) = DAV_KEY_AES256;
+    PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256;
+    dav_rand_bytes((unsigned char*)p->content->space+4, 16);
+    p->isdecrypted = 1;
+    p->encoffset = PWDS_HEADER_SIZE;
+    return p;
+}
+
+PwdStore* pwdstore_clone(PwdStore *p) {
+    CxBuffer *newbuffer = calloc(1, sizeof(CxBuffer));
+    *newbuffer = *p->content;
+    newbuffer->space = malloc(p->content->capacity);
+    memcpy(newbuffer->space, p->content->space, p->content->capacity);
+    
+    DavKey *key = NULL;
+    if(p->key) {
+        key = malloc(sizeof(DavKey));
+        key->data = malloc(p->key->length);
+        memcpy(key->data, p->key->data, p->key->length);
+        key->length = p->key->length;
+        key->type = p->key->type;
+        key->name = NULL;
+    }
+    
+    PwdStore *newp = calloc(1, sizeof(PwdStore));
+    newp->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    newp->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    newp->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    newp->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    newp->content = newbuffer;
+    newp->key = key;
+    newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) : NULL;
+    newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) : NULL;
+    newp->encoffset = p->encoffset;
+    newp->isdecrypted = p->isdecrypted;
+    
+    CxIterator i = cxMapIterator(p->ids);
+    cx_foreach(CxMapEntry *, e, i) {
+        PwdEntry *entry = e->value;
+        pwdstore_put(newp, entry->id, entry->user, entry->password);
+    }
+    
+    i = cxMapIterator(p->index);
+    cx_foreach(CxMapEntry *, e, i) {
+        PwdIndexEntry *entry = e->value;
+        CxList *locations = NULL;
+        if(entry->locations) {
+            locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+            CxIterator li = cxListIterator(entry->locations);
+            cx_foreach(char *, location, li) {
+                cxListAdd(locations, strdup(location));
+            }
+        }        
+        pwdstore_put_index(newp, strdup(entry->id), locations);
+    }
+    
+    return newp;
+}
+
+static int readval(CxBuffer *in, char **val, int allowzero) {
+    // value  = length string
+    // length = uint32
+    // string = bytes
+    
+    *val = NULL;
+    
+    // get length
+    uint32_t length = 0;
+    if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) {
+        return 0;
+    }
+    length = ntohl(length); // convert from BE to host byte order
+    if(length == 0) {
+        if(allowzero) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+    if(length > PWDSTORE_MAX_LEN) {
+        return 0;
+    }
+    
+    // get value
+    char *value = malloc(length + 1);
+    value[length] = 0;
+    if(cxBufferRead(value, 1, length, in) != length) {
+        free(value);
+        return 0;
+    }
+    
+    *val = value;
+    return 1;
+}
+
+static int read_indexentry(PwdStore *p, CxBuffer *in) {
+    // read type of index element
+    int type = cxBufferGet(in);
+    if(type == EOF || type != 0) {
+        // only type 0 supported yet
+        return 0;
+    }
+      
+    char *id = NULL;
+    CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    cxDefineDestructor(locations, free);
+    
+    // get id (required)
+    int ret = 0;
+    if(readval(in, &id, FALSE)) {
+        // get locations
+        char *location = NULL;
+        while((ret = readval(in, &location, TRUE)) == 1) {
+            if(!location) {
+                break;
+            }
+            cxListAdd(locations, location);
+        }
+    }
+    
+    if(ret) {
+        pwdstore_put_index(p, id, locations);
+        if(cxListSize(locations) == 0) {
+            cxListDestroy(locations);
+        }
+    } else {
+        if(id) free(id);
+        cxListDestroy(locations);
+    }
+    
+    return ret;
+}
+
+static int read_pwdentry(PwdStore *p, CxBuffer *in) {
+    int type = cxBufferGet(in);
+    if(type == EOF || type != 0) {
+        // only type 0 supported yet
+        return 0;
+    }
+    
+    char *id = NULL;
+    char *user = NULL;
+    char *password = NULL;
+    
+    int ret = 0;
+    if(readval(in, &id, FALSE)) {
+        if(readval(in, &user, FALSE)) {
+            if(readval(in, &password, FALSE)) {
+                pwdstore_put(p, id, user, password);
+                ret = 1;
+            }
+        }
+    }
+    
+    if(id) free(id);
+    if(user) free(user);
+    if(password) free(password);
+    
+    return ret;
+}
+
+static void remove_list_entries(PwdStore *s, const char *id) {
+    CxIterator i = cxListMutIterator(s->locations);
+    cx_foreach(PwdIndexEntry*, ie, i) {
+        if(!strcmp(ie->id, id)) {
+            cxIteratorFlagRemoval(i);
+            cxIteratorNext(i);
+            break;
+        }
+    }
+    i = cxListMutIterator(s->noloc);
+    cx_foreach(PwdIndexEntry*, ie, i) {
+        if(!strcmp(ie->id, id)) {
+            cxIteratorFlagRemoval(i);
+            cxIteratorNext(i);
+            break;
+        }
+    }
+}
+
+void pwdstore_remove_entry(PwdStore *s, const char *id) {
+    remove_list_entries(s, id);
+    
+    CxHashKey key = cx_hash_key_str(id);
+    PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key);
+    PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
+    
+    if(i) {
+        if(i->locations) {
+            cxListDestroy(i->locations);
+        }
+        free(i->id);
+        free(i);
+    }
+    if(e) {
+        free(e->id);
+        free(e->user);
+        free(e->password);
+        free(e);
+    }
+}
+
+int pwdstore_getindex(PwdStore *s) {
+    uint32_t netindexlen;
+    
+    // set the position to the last 4 bytes of the header
+    // for reading index length
+    s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+    
+    // read indexlen and convert to host byte order
+    if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) {
+        return 1;
+    }
+    uint32_t indexlen = ntohl(netindexlen);
+    
+    // integer overflow check
+    if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) {
+        return 1;
+    }
+    if(s->content->size < PWDS_HEADER_SIZE + indexlen) {
+        return 1;
+    }
+    // encrypted content starts after the index content
+    s->encoffset = PWDS_HEADER_SIZE + indexlen;
+    
+    // the index starts after the header
+    CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0);
+    index->size = indexlen;
+    
+    // read index
+    while(read_indexentry(s, index)) {}
+    
+    // free index buffer structure (not the content)
+    cxBufferFree(index);
+    
+    return 0;
+}
+
+int pwdstore_decrypt(PwdStore *p) {
+    if(!p->key) {
+        return 1;
+    }
+    if(p->isdecrypted) {
+        return 0;
+    }
+    
+    // decrypt contet
+    size_t encsz = p->content->size - p->encoffset;
+    CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0);
+    enc->size = encsz;
+    enc->size = p->content->size - p->encoffset;
+    CxBuffer *content = aes_decrypt_buffer(enc, p->key);
+    cxBufferFree(enc);
+    if(!content) {
+        return 1;
+    }
+    
+    while(read_pwdentry(p, content)) {}
+    
+    cxBufferFree(content);
+    
+    p->isdecrypted = 1;
+    
+    return 0;
+}
+
+int pwdstore_setpassword(PwdStore *p, const char *password) {
+    DavKey *key = dav_pw2key(
+            password,
+            (unsigned char*)(p->content->space + 4),
+            16,
+            PWDS_PWFUNC(p),
+            PWDS_ENC(p));
+    if(!key) {
+        return 1;
+    }
+    
+    p->key = key;
+    return 0;
+}
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) {
+    PWDS_ENC(p) = enc;
+    PWDS_PWFUNC(p) = pwfunc;
+}
+
+void pwdstore_free_entry(PwdEntry *e) {
+    if(e->id) free(e->id);
+    if(e->user) free(e->user);
+    if(e->password) free(e->password);
+    free(e);
+}
+
+void pwdstore_free(PwdStore* p) {
+    cxDefineDestructor(p->ids, pwdstore_free_entry);
+    cxMapDestroy(p->ids);
+    
+    cxListDestroy(p->locations);
+    
+    if(p->content) {
+        cxBufferFree(p->content);
+    }
+    
+    free(p);
+}
+
+int pwdstore_has_id(PwdStore *s, const char *id) {
+    return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0;
+}
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id) {
+    PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id));
+    if(e && e->user && e->password) {
+        return e;
+    } else {
+        return NULL;
+    }
+}
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) {
+    PwdEntry *entry = malloc(sizeof(PwdEntry));
+    entry->id = strdup(id);
+    entry->user = strdup(username);
+    entry->password = strdup(password);
+    cxMapPut(p->ids, cx_hash_key_str(id), entry);
+}
+
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) {
+    PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id));
+    if(e) {
+        return;
+    }
+    PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry));
+    newentry->id = id;
+    if(locations && cxListSize(locations) > 0) {
+        newentry->locations = locations;
+        cxListAdd(p->locations, newentry);
+    } else {
+        newentry->locations = NULL;
+        cxListAdd(p->noloc, newentry);
+    }
+    cxMapPut(p->index, cx_hash_key_str(id), newentry);
+}
+
+void write_index_entry(CxBuffer *out, PwdIndexEntry *e) {
+    uint32_t idlen = strlen(e->id);
+    uint32_t netidlen = htonl(idlen);
+    
+    cxBufferPut(out, 0); // type
+
+    cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out);
+    cxBufferWrite(e->id, 1, idlen, out);
+    
+    if(e->locations) {
+        CxIterator i = cxListIterator(e->locations);
+        cx_foreach(char *, location, i) {
+            uint32_t locationlen = strlen(location);
+            uint32_t netlocationlen = htonl(locationlen);
+
+            cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
+            cxBufferWrite(location, 1, locationlen, out);
+        }
+    }
+    
+    uint32_t terminate = 0;
+    cxBufferWrite(&terminate, 1, sizeof(uint32_t), out);
+}
+
+int pwdstore_store(PwdStore *p, const char *file) {
+    if(!p->key) {
+        return 1;
+    }
+    
+    CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // create index
+    CxIterator i = cxListIterator(p->noloc);
+    cx_foreach(PwdIndexEntry*, e, i) {
+        write_index_entry(index, e);
+    }
+    i = cxListIterator(p->locations);
+    cx_foreach(PwdIndexEntry*, e, i) {
+        write_index_entry(index, e);
+    }
+    
+    i = cxMapIteratorValues(p->ids);
+    cx_foreach(PwdEntry*, value, i) {
+        if(!value->id || !value->user || !value->password) {
+            continue;
+        }
+        
+        uint32_t idlen = strlen(value->id);
+        uint32_t ulen = strlen(value->user);
+        uint32_t plen = strlen(value->password);
+        uint32_t netidlen = htonl(idlen);
+        uint32_t netulen = htonl(ulen);
+        uint32_t netplen = htonl(plen);
+        
+        // content buffer
+        cxBufferPut(content, 0); // type
+        
+        cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->id, 1, idlen, content);
+        cxBufferWrite(&netulen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->user, 1, ulen, content);
+        cxBufferWrite(&netplen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->password, 1, plen, content);
+    }
+      
+    content->pos = 0;
+    CxBuffer *enc = aes_encrypt_buffer(content, p->key);
+
+    p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+    p->content->size = PWDS_HEADER_SIZE;
+    
+    // add index after header
+    uint32_t netindexlen = htonl((uint32_t)index->size);
+    cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content);
+    cxBufferWrite(index->space, 1, index->size, p->content);
+    
+    // add encrypted buffer
+    cxBufferWrite(enc->space, 1, enc->size, p->content);
+    
+    cxBufferFree(enc);
+    
+    FILE *out = fopen(file, "w");
+    if(!out) {
+        return 1;
+    }
+    fwrite(p->content->space, 1, p->content->size, out);
+    fclose(out);
+    
+    return 0;
+}
+
+int pwdstore_decrypt_secrets(PwdStore *secrets) {
+    if(!pw_input) {
+        return 1;
+    }
+    
+    char *ps_password = NULL;
+    if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) {
+        CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        if(!util_exec_command(secrets->unlock_cmd, cmd_out)) {
+            // command successful, get first line from output without newline
+            // and use that as password for the secretstore
+            size_t len = 0;
+            for(size_t i=0;i<=cmd_out->size;i++) {
+                if(i == cmd_out->size || cmd_out->space[i] == '\n') {
+                    len = i;
+                    break;
+                }
+            }
+            if(len > 0) {
+                ps_password = malloc(len + 1);
+                memcpy(ps_password, cmd_out->space, len);
+                ps_password[len] = 0;
+            }
+        }
+        cxBufferFree(cmd_out);
+    }
+    
+    if(!ps_password) {
+        ps_password = pw_input(pw_input_data);
+        if(!ps_password) {
+            return 1;
+        }
+    }
+    
+    int err = pwdstore_setpassword(secrets, ps_password);
+    free(ps_password);
+    if(err) {
+        fprintf(stderr, "Error: cannot create key from password\n");
+        return 1;
+    }
+    if(pwdstore_decrypt(secrets)) {
+        fprintf(stderr, "Error: cannot decrypt secrets store\n");
+        return 1;
+    }
+    return 0;
+}
diff --git a/libidav/pwdstore.h b/libidav/pwdstore.h
new file mode 100644 (file)
index 0000000..d865039
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBIDAV_PWDSTORE_H
+#define LIBIDAV_PWDSTORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <cx/map.h>
+#include <cx/buffer.h>
+#include <cx/linked_list.h>
+#include "crypto.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PWDSTORE_MAX_LEN 4096
+    
+/*
+ * File Format:
+ * 
+ * file = header, index, enc_content
+ * header = magic, version, enc, pwfunc, salt, indexlen
+ * magic = 1 byte
+ * version = 1 byte
+ * enc = 1 byte
+ * pwfunc = 1 byte
+ * salt = 16 bytes
+ * indexlen = uint32
+ * index = { itype length id locations zero }
+ * enc_content = iv bytes
+ * iv = 16 bytes
+ * content = { entry }
+ * entry = itype length id length username length password
+ * length = uint32
+ * zero = 4 zero bytes
+ * itype = 1 byte
+ * id = string
+ * locations = { length string }
+ * username = string
+ * password = string
+ * 
+ * The content is AES encrypted with a key derived from a password
+ * and the salt. The first 16 bytes are the aes iv.
+ * 
+ * All integers are big endian
+ */
+    
+#define PWDS_HEADER_SIZE 24
+    
+typedef struct PwdStore        PwdStore;
+typedef struct PwdEntry        PwdEntry;
+typedef struct PwdIndexEntry   PwdIndexEntry;
+
+struct PwdStore {
+    /*
+     * map of all credentials
+     * key is the username
+     * value is PwdEntry*
+     */
+    CxMap *ids;
+    
+    /*
+     * list of all credentials with location
+     * value is PwdIndexEntry*
+     */
+    CxList *locations;
+    
+    /*
+     * list of all credentials without location
+     * value is PwdIndexEntry*
+     */
+    CxList *noloc;
+    
+    /*
+     * index map that contains all elements from the lists
+     * 'locations' and 'noloc'
+     */
+    CxMap *index;
+    
+    /*
+     * a buffer containing the complete file content
+     */
+    CxBuffer *content;
+    
+    /*
+     * key used for encryption/decryption
+     */
+    DavKey *key;
+    
+    /*
+     * optional shell command, that is used for getting the master password
+     */
+    char *unlock_cmd;
+    
+    /*
+     * optional shell command, that is exected when the secretstore is closed
+     */
+    char *lock_cmd;
+    
+    /*
+     * start offset of the encrypted buffer
+     */
+    uint32_t encoffset;
+    
+    /*
+     * indicates if the PwdStore is decrypted with pwdstore_decrypt
+     */
+    uint8_t isdecrypted;
+};
+
+#define PWDS_MAGIC(p) (p)->content->space[0]
+#define PWDS_VERSION(p) (p)->content->space[1]
+#define PWDS_ENC(p) (p)->content->space[2]
+#define PWDS_PWFUNC(p) (p)->content->space[3]
+
+#define PWDS_MAGIC_CHAR 'P'
+
+struct PwdEntry {
+    char *id;
+    char *user;
+    char *password;
+};
+
+struct PwdIndexEntry {
+    char *id;
+    CxList *locations;
+};
+
+/*
+ * opens the password store
+ * the content is still encrypted and must be decrypted using pwdstore_decrypt
+ */
+PwdStore* pwdstore_open(const char *file);
+
+PwdStore* pwdstore_new(void);
+
+PwdStore* pwdstore_clone(PwdStore *p);
+
+/*
+ * decrypts the password store with the previously set password
+ */
+int pwdstore_decrypt(PwdStore *p);
+
+int pwdstore_setpassword(PwdStore *p, const char *password);
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc);
+
+void pwdstore_free_entry(PwdEntry *e);
+void pwdstore_free(PwdStore* p);
+
+int pwdstore_has_id(PwdStore *s, const char *id);
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id);
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password);
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations);
+
+void pwdstore_remove_entry(PwdStore *s, const char *id);
+
+int pwdstore_store(PwdStore *p, const char *file);
+
+
+int pwdstore_decrypt_secrets(PwdStore *secrets);
+
+
+
+
+
+
+typedef char*(*pwdstore_pwinput_func)(void *userdata);
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata);
+
+char * pwdstore_default_pwinput(char *prompt);
+
+
+
+/* private */
+int pwdstore_getindex(PwdStore *s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_PWDSTORE_H */
+
diff --git a/libidav/resource.c b/libidav/resource.c
new file mode 100644 (file)
index 0000000..305358f
--- /dev/null
@@ -0,0 +1,1882 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <libxml/tree.h>
+
+#include "utils.h"
+#include "session.h"
+#include "methods.h"
+#include "crypto.h"
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.h>
+#include <cx/array_list.h>
+
+#include "resource.h"
+#include "xml.h"
+#include "davqlexec.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+DavResource* dav_resource_new(DavSession *sn, const char *path) {
+    //char *href = util_url_path(url);
+    //DavResource *res = dav_resource_new_href(sn, href);
+    char *parent = util_parent_path(path);
+    const char *name = util_resource_name(path); 
+    char *href = dav_session_create_plain_href(sn, path);
+    
+    DavResource *res = dav_resource_new_full(sn, parent, name, href);
+    free(parent);
+    return res;
+}
+
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name) {
+    char *path = util_concat_path(parent->path, name);
+    char *href = dav_session_create_plain_href(sn, path);
+    DavResource *res = dav_resource_new_full(sn, parent->path, name, href);
+    free(path);
+    return res;
+}
+
+
+DavResource* dav_resource_new_href(DavSession *sn, const char *href) {  
+    DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    resource_set_info(res, href);
+    
+    // initialize resource data
+    res->data = resource_data_new(sn);
+    
+    return res;
+}
+
+DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href) {
+    cxstring n = cx_str(name);
+    // the name must not contain path separators
+    if(n.length > 0 && href) {
+        for(int i=0;i<n.length-1;i++) {
+            char c = n.ptr[i];
+            if(c == '/' || c == '\\') {
+                n = cx_str(util_resource_name(href));
+                break;
+            }
+        }
+    }
+    // remove trailing '/'
+    if(n.length > 0 && n.ptr[n.length-1] == '/') {
+        n.length--;
+    }
+    
+    DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    res->name = cx_strdup_a(sn->mp->allocator, n).ptr;
+    
+    char *path = util_concat_path(parent_path, name); 
+    res->path = dav_session_strdup(sn, path);
+    
+    res->href = href;
+    
+    // initialize resource data
+    res->data = resource_data_new(sn);
+    
+    // cache href/path
+    if(href) {
+        dav_session_cache_path(sn, cx_str(path), cx_str(href));
+    }
+    free(path);
+    
+    return res;
+}
+
+void resource_free_properties(DavSession *sn, CxMap *properties) {
+    if(!properties) return;
+    
+    CxIterator i = cxMapIteratorValues(properties);
+    cx_foreach(DavProperty*, property, i) {
+        // TODO: free everything
+        dav_session_free(sn, property);
+    }
+    cxMapDestroy(properties);
+}
+
+void dav_resource_free(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    dav_session_free(sn, res->name);
+    dav_session_free(sn, res->path);
+    if(res->href) {
+        dav_session_free(sn, res->href);
+    }
+    
+    DavResourceData *data = res->data;
+    resource_free_properties(sn, data->properties);
+    resource_free_properties(sn, data->crypto_properties);
+    
+    if(data->set) {
+        CxIterator i = cxListIterator(data->set);
+        cx_foreach(DavProperty *, p, i) {
+            dav_session_free(sn, p->ns->name);
+            if(p->ns->prefix) {
+                dav_session_free(sn, p->ns->prefix);
+            }
+            dav_session_free(sn, p->ns);
+
+            dav_session_free(sn, p->name);
+            dav_free_xml_node_sn(sn, p->value);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(data->remove) {
+        CxIterator i = cxListIterator(data->remove);
+        cx_foreach(DavProperty *, p, i) {
+            dav_session_free(sn, p->ns->name);
+            if(p->ns->prefix) {
+                dav_session_free(sn, p->ns->prefix);
+            }
+            dav_session_free(sn, p->ns);
+
+            dav_session_free(sn, p->name);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(data->crypto_set) {
+        CxIterator i = cxListIterator(data->crypto_set);
+        cx_foreach(DavProperty *, p, i) {
+            dav_session_free(sn, p->ns->name);
+            if(p->ns->prefix) {
+                dav_session_free(sn, p->ns->prefix);
+            }
+            dav_session_free(sn, p->ns);
+
+            dav_session_free(sn, p->name);
+            dav_free_xml_node_sn(sn, p->value);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(data->crypto_remove) {
+        CxIterator i = cxListIterator(data->crypto_remove);
+        cx_foreach(DavProperty *, p, i) {
+            dav_session_free(sn, p->ns->name);
+            if(p->ns->prefix) {
+                dav_session_free(sn, p->ns->prefix);
+            }
+            dav_session_free(sn, p->ns);
+
+            dav_session_free(sn, p->name);
+            dav_session_free(sn, p);
+        }
+    }
+    
+    if(!data->read && data->content) {
+        dav_session_free(sn, data->content);
+    }
+    dav_session_free(sn, data);
+    
+    dav_session_free(sn, res);
+}
+
+void dav_resource_free_all(DavResource *res) {
+    DavResource *child = res->children;
+    dav_resource_free(res);
+    while(child) {
+        DavResource *next = child->next;
+        dav_resource_free_all(child);
+        child = next;
+    }
+}
+
+void resource_set_href(DavResource *res, cxstring href) {
+    res->href = cx_strdup_a(res->session->mp->allocator, href).ptr;
+}
+
+void resource_set_info(DavResource *res, const char *href_str) {
+    char *url_str = NULL;
+    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
+    cxstring name = cx_str(util_resource_name(href_str));
+    cxstring href = cx_str(href_str);
+    
+    cxstring base_href = cx_str(util_url_path(res->session->base_url));
+    cxstring path = cx_strsubs(href, base_href.length - 1);
+    
+    const CxAllocator *a = res->session->mp->allocator;
+    CURL *handle = res->session->handle;
+    
+    int nlen = 0;
+    char *uname = curl_easy_unescape(handle, name.ptr, name.length , &nlen);
+    int plen = 0;
+    char *upath = curl_easy_unescape(handle, path.ptr, path.length, &plen); 
+    
+    res->name = cx_strdup_a(a, cx_strn(uname, nlen)).ptr;
+    res->href = cx_strdup_a(a, href).ptr;
+    res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr;
+    
+    curl_free(uname);
+    curl_free(upath);
+}
+
+DavResourceData* resource_data_new(DavSession *sn) {
+    DavResourceData *data = cxMalloc(
+            sn->mp->allocator,
+            sizeof(DavResourceData));
+    if(!data) {
+        return NULL;
+    }
+    data->properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
+    data->crypto_properties = NULL;
+    data->set = NULL;
+    data->remove = NULL;
+    data->crypto_set = NULL;
+    data->crypto_remove = NULL;
+    data->read = NULL;
+    data->content = NULL;
+    data->seek = NULL;
+    data->length = 0;
+    return data;
+}
+
+char* dav_resource_get_href(DavResource *resource) {
+    if(!resource->href) {
+        resource->href = dav_session_get_href(
+                resource->session,
+                resource->path);
+    }
+    return resource->href;
+}
+
+void resource_add_prop(DavResource *res, const char *ns, const char *name, DavXmlNode *val) {
+    DavSession *sn = res->session;
+    
+    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+    namespace->prefix = NULL;
+    namespace->name = dav_session_strdup(sn, ns);
+    
+    DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+    prop->name = dav_session_strdup(sn, name);
+    prop->ns = namespace;
+    prop->value = val;
+    
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+    cxMapPut(((DavResourceData*)res->data)->properties, key, prop);
+    free(keystr.ptr);
+}
+
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val) {
+    if(!val) {
+        return;
+    }
+    
+    resource_add_prop(res, ns, name, dav_convert_xml(res->session, val));
+}
+
+void resource_add_string_property(DavResource *res, char *ns, char *name, char *val) {
+    if(!val) {
+        return;
+    }
+    
+    resource_add_prop(res, ns, name, dav_text_node(res->session, val));
+}
+
+void resource_set_crypto_properties(DavResource *res, CxMap *cprops) {
+    DavResourceData *data = res->data;
+    resource_free_properties(res->session, data->crypto_properties);
+    data->crypto_properties = cprops;
+}
+
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name) {
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+    DavXmlNode *ret = resource_get_property_k(res, key);
+    free(keystr.ptr);
+    
+    return ret;
+}
+
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name) {
+    cxmutstr keystr = dav_property_key(ns, name);
+    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+    DavXmlNode *ret = resource_get_encrypted_property_k(res, key);
+    free(keystr.ptr);
+    
+    return ret;
+}
+
+DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = cxMapGet(data->properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = cxMapGet(data->crypto_properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+cxmutstr dav_property_key(const char *ns, const char *name) {
+    return dav_property_key_a(cxDefaultAllocator, ns, name);
+}
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name) {
+    cxstring ns_str = cx_str(ns);
+    cxstring name_str = cx_str(name);
+    
+    return cx_strcat_a(a, 4, ns_str, CX_STR("\0"), name_str, CX_STR("\0"));
+}
+
+
+
+
+void resource_add_child(DavResource *parent, DavResource *child) {
+    child->next = NULL;
+    if(parent->children) {
+        DavResource *last = parent->children;
+        while(last->next) {
+            last = last->next;
+        }
+        last->next = child;
+        child->prev = last;
+    } else {
+        child->prev = NULL;
+        parent->children = child;
+    }
+    child->parent = parent;
+}
+
+static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion *cr) {
+    if(!(res1 && res2)) {
+        return 0;
+    }
+    
+    int ret;
+    if(cr->type == 0) {
+        switch(cr->column.resprop) {
+            case DAVQL_RES_NAME: {
+                ret = strcmp(res1->name, res2->name);
+                break;
+            }
+            case DAVQL_RES_PATH: {
+                ret = strcmp(res1->path, res2->path);
+                break;
+            }
+            case DAVQL_RES_HREF: {
+                ret = strcmp(res1->href, res2->href);
+                break;
+            }
+            case DAVQL_RES_CONTENTLENGTH: {
+                int c = res1->contentlength == res2->contentlength;
+                ret = c ? 0 : (res1->contentlength < res2->contentlength?-1:1);
+                break;
+            }
+            case DAVQL_RES_CONTENTTYPE: {
+                ret = strcmp(res1->contenttype, res2->contenttype);
+                break;
+            }
+            case DAVQL_RES_CREATIONDATE: {
+                int c = res1->creationdate == res2->creationdate;
+                ret = c ? 0 : (res1->creationdate < res2->creationdate?-1:1);
+                break;
+            }
+            case DAVQL_RES_LASTMODIFIED: {
+                int c = res1->lastmodified == res2->lastmodified;
+                ret = c ? 0 : (res1->lastmodified < res2->lastmodified?-1:1);
+                break;
+            }
+            case DAVQL_RES_ISCOLLECTION: {
+                int c = res1->iscollection == res2->iscollection;
+                ret = c ? 0 : (res1->iscollection < res2->iscollection?-1:1);
+                break;
+            }
+            default: ret = 0;
+        }
+    } else if(cr->type == 1) {
+        DavXmlNode *xvalue1 = resource_get_property_k(res1, cr->column.property);
+        DavXmlNode *xvalue2 = resource_get_property_k(res2, cr->column.property);
+        char *value1 = dav_xml_getstring(xvalue1);
+        char *value2 = dav_xml_getstring(xvalue2);
+        if(!value1) {
+            ret = value2 ? -1 : 0;
+        } else if(!value2) {
+            ret = value1 ? 1 : 0;
+        } else {
+            ret = strcmp(value1, value2);
+        }
+    } else {
+        return 0;
+    }
+    
+    return cr->descending ? -ret : ret;
+}
+
+void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr) {
+    if(!ordercr) {
+        resource_add_child(parent, child);
+        return;
+    }
+    
+    child->parent = parent;
+    
+    if(!parent->children) {
+        child->next = NULL;
+        child->prev = NULL;
+        parent->children = child;
+    } else {
+        DavResource *resource = parent->children;
+        while(resource) {
+            int r = 0;
+            CxIterator i = cxListIterator(ordercr);
+            cx_foreach(DavOrderCriterion*, cr, i) {
+                r = resource_cmp(child, resource, cr);
+                if(r != 0) {
+                    break;
+                }
+            }
+            
+            if(r < 0) {
+                // insert child before resource
+                child->prev = resource->prev;
+                child->next = resource;
+                if(resource->prev) {
+                    resource->prev->next = child;
+                } else {
+                    parent->children = child;
+                }
+                resource->prev = child;
+                break;
+            } if(!resource->next) {
+                // append child
+                child->prev = resource;
+                child->next = NULL;
+                resource->next = child;
+                break;
+            } else {
+                resource = resource->next;
+            }
+        }
+    }
+}
+
+char* dav_get_string_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    if(!pns || !pname) {
+        return NULL;
+    }
+    return dav_get_string_property_ns(res, pns, pname);
+}
+
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name) {
+    DavXmlNode *prop = dav_get_property_ns(res, ns, name);
+    if(!prop) {
+        return NULL;
+    }
+    return dav_xml_getstring(prop);
+}
+
+DavXmlNode* dav_get_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    if(!pns || !pname) {
+        return NULL;
+    }
+    return dav_get_property_ns(res, pns, pname);
+}
+
+static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const char *ns, const char *name) {
+    if(!ns || !name) {
+        return NULL;
+    }
+    
+    DavResourceData *data = res->data;
+    
+    DavXmlNode *property = NULL;
+    CxList *remove_list = NULL;
+    CxList *set_list = NULL;
+    
+    if(encrypted) {
+        // check if crypto_properties because it will only be created
+        // if the resource has encrypted properties
+        if(!data->crypto_properties) {
+            return NULL;
+        }
+        property = resource_get_encrypted_property(res, ns, name);
+        remove_list = data->crypto_remove;
+        set_list = data->crypto_set;
+    } else {
+        property = resource_get_property(res, ns, name);
+        remove_list = data->remove;
+        set_list = data->set;
+    }
+    
+    // resource_get_property only returns persistent properties
+    // check the remove and set list
+    if(property && remove_list) {
+        // if the property is in the remove list, we return NULL
+        CxIterator i = cxListIterator(remove_list);
+        cx_foreach(DavProperty*, p, i) {
+            if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+                return NULL;
+            }
+        }
+    }
+    
+    // the set list contains property updates
+    // we return an updated property if possible
+    if(set_list) {
+        CxIterator i = cxListIterator(set_list);
+        cx_foreach(DavProperty*, p, i) {
+            if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+                return p->value; // TODO: fix
+            }
+        }
+    }
+    
+    // no property update
+    
+    return property;
+}
+
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name) {
+    DavXmlNode *property_value = get_property_ns(res, FALSE, ns, name);
+    
+    if(!property_value && DAV_DECRYPT_PROPERTIES(res->session)) {
+        property_value = get_property_ns(res, TRUE, ns, name);
+    }
+    
+    return property_value;
+}
+
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name) {
+    return get_property_ns(res, TRUE, ns, name);
+}
+
+static DavProperty* createprop(DavSession *sn, const char *ns, const char *name) {
+    DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+    property->name = dav_session_strdup(sn, name);
+    property->value = NULL;
+    
+    DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+    namespace->prefix = NULL;
+    namespace->name = dav_session_strdup(sn, ns);
+    
+    property->ns = namespace;
+    
+    return property;
+}
+
+int dav_set_string_property(DavResource *res, char *name, char *value) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    if(!pns) {
+        res->session->errorstr = "Property namespace not found";
+        return 1;
+    }
+    dav_set_string_property_ns(res, pns, pname, value);
+    return 0;
+}
+
+static int add2propertylist(const CxAllocator *a, CxList **list, DavProperty *property) {
+    if(!*list) {
+        CxList *newlist = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+        if(!newlist) {
+            return 1;
+        }
+        *list = newlist;
+    }
+    cxListAdd(*list, property);
+    return 0;
+}
+
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    DavSession *sn = res->session;
+    const CxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        add2propertylist(a, &data->crypto_set, property);
+    } else {
+        add2propertylist(a, &data->set, property);
+    }
+}
+
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_set_property_ns(res, pns, pname, value);
+}
+
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    DavSession *sn = res->session;
+    const CxAllocator *a = sn->mp->allocator; 
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(sn, ns, name);
+    // TODO: this function should copy the value
+    //       but we also need a function, that doesn't create a copy
+    property->value = value;
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        add2propertylist(a, &data->crypto_set, property);
+    } else {
+        add2propertylist(a, &data->set, property);
+    }
+}
+
+void dav_remove_property(DavResource *res, char *name) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_remove_property_ns(res, pns, pname);
+}
+
+void dav_remove_property_ns(DavResource *res, char *ns, char *name) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    const CxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        add2propertylist(a, &data->crypto_remove, property);
+    } else {
+        add2propertylist(a, &data->remove, property);
+    }
+}
+
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    const CxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    const CxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
+    DavResourceData *data = res->data;
+    const CxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    add2propertylist(a, &data->crypto_remove, property);
+}
+
+static int compare_propname(const void *a, const void *b) {
+    const DavPropName *p1 = a;
+    const DavPropName *p2 = b;
+    
+    int result = strcmp(p1->ns, p2->ns);
+    if(result) {
+        return result;
+    } else {
+        return strcmp(p1->name, p2->name);
+    }
+}
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count) {
+    DavResourceData *data = res->data;
+    
+    *count = cxMapSize(data->properties);
+    DavPropName *names = dav_session_calloc(
+            res->session,
+            *count,
+            sizeof(DavPropName));
+    
+    
+    CxIterator i = cxMapIteratorValues(data->properties);
+    cx_foreach(DavProperty*, value, i) {
+        DavPropName *name = &names[i.index];
+        name->ns = value->ns->name;
+        name->name = value->name;
+    }
+    
+    qsort(names, *count, sizeof(DavPropName), compare_propname);
+    
+    return names;
+}
+
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+    DavResourceData *data = res->data;
+    data->content = stream;
+    data->read = read_func;
+    data->seek = seek_func;
+    data->length = 0;
+}
+
+void dav_set_content_data(DavResource *res, char *content, size_t length) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    data->content = dav_session_malloc(sn, length);
+    memcpy(data->content, content, length);
+    data->read = NULL;
+    data->seek = NULL;
+    data->length = length;
+}
+
+void dav_set_content_length(DavResource *res, size_t length) {
+    DavResourceData *data = res->data;
+    data->length = length;
+}
+
+
+int dav_load(DavResource *res) {
+    CxBuffer *rqbuf = create_allprop_propfind_request();
+    int ret = dav_propfind(res->session, res, rqbuf);
+    cxBufferFree(rqbuf);
+    return ret;
+}
+
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
+    CxMempool *mp = cxMempoolCreate(64, NULL);
+    const CxAllocator *a = mp->allocator;
+    
+    CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop);
+    for(size_t i=0;i<numprop;i++) {
+        DavProperty p;
+        p.name = properties[i].name;
+        p.ns = cxMalloc(a, sizeof(DavNamespace));
+        p.ns->name = properties[i].ns;
+        if(!strcmp(properties[i].ns, "DAV:")) {
+            p.ns->prefix = "D";
+        } else {
+            p.ns->prefix = cx_asprintf_a(a, "x%d", (int)i).ptr;
+        }
+        p.value = NULL;
+        cxListAdd(proplist, &p);
+    }
+    
+    CxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
+    int ret = dav_propfind(res->session, res, rqbuf);
+    cxBufferFree(rqbuf);
+    cxMempoolDestroy(mp);
+    return ret;
+}
+
+
+static void init_hash_stream(HashStream *hstr, void *stream, dav_read_func readfn, dav_seek_func seekfn) {
+    hstr->sha = NULL;
+    hstr->stream = stream;
+    hstr->read = readfn;
+    hstr->seek = seekfn;
+    hstr->error = 0;
+}
+
+static size_t dav_read_h(void *buf, size_t size, size_t nelm, void *stream) {
+    HashStream *s = stream;
+    if(!s->sha) {
+        s->sha = dav_hash_init();
+    }
+     
+    size_t r = s->read(buf, size, nelm, s->stream);
+    dav_hash_update(s->sha, buf, r);
+    return r;
+}
+
+static int dav_seek_h(void *stream, long offset, int whence) {
+    HashStream *s = stream;
+    if(offset == 0 && whence == SEEK_SET) {
+        unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
+        dav_hash_final(s->sha, buf);
+        s->sha = NULL;
+    } else {
+        s->error = 1;
+    }
+    return s->seek(s->stream, offset, whence);
+}
+
+
+int dav_store(DavResource *res) {
+    DavSession *sn = res->session;
+    DavResourceData *data = res->data;
+    
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(sn, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    // store content
+    if(data->content) {
+        curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, dav_session_put_progress);
+        curl_easy_setopt(sn->handle, CURLOPT_XFERINFODATA, res);
+        curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 0L);
+
+        int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key;
+        CURLcode ret;
+        if(encryption) {
+            AESEncrypter *enc = NULL;
+            CxBuffer *buf = NULL;
+            if(data->read) {
+                enc = aes_encrypter_new(
+                        sn->key,
+                        data->content,
+                        data->read,
+                        data->seek);
+            } else {
+                buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+                buf->size = data->length;
+                enc = aes_encrypter_new(
+                        sn->key,
+                        buf,
+                        (dav_read_func)cxBufferRead,
+                        (dav_seek_func)cxBufferSeek);
+            }
+              
+            // put resource
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    enc,
+                    (dav_read_func)aes_read,
+                    (dav_seek_func)aes_encrypter_reset,
+                    0);
+            
+            // get sha256 hash
+            dav_get_hash(&enc->sha256, (unsigned char*)data->hash);
+            char *enc_hash = aes_encrypt(data->hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+            
+            aes_encrypter_close(enc);
+            if(buf) {
+                cxBufferFree(buf);
+            }
+            
+            // add crypto properties
+            // TODO: store the properties later
+            if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
+                free(enc_hash);
+                curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL);
+                curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L);
+                return 1;
+            }
+            resource_add_string_property(res, DAV_NS, "crypto-hash", enc_hash);
+            free(enc_hash);
+        } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+            HashStream hstr;
+            CxBuffer *iobuf = NULL;
+            if(!data->read) {
+                iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+                iobuf->size = data->length;
+                init_hash_stream(
+                        &hstr,
+                        iobuf,
+                        (dav_read_func)cxBufferRead,
+                        (dav_seek_func)cxBufferSeek);
+            } else {
+                init_hash_stream(
+                        &hstr,
+                        data->content,
+                        data->read,
+                        data->seek);
+            }
+            
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    &hstr,
+                    dav_read_h,
+                    (dav_seek_func)dav_seek_h,
+                    data->length);
+            
+            if(hstr.sha) {
+                dav_hash_final(hstr.sha, (unsigned char*)data->hash);
+                char *hash = util_hexstr((unsigned char*)data->hash, 32);
+                dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
+                free(hash);
+            }
+        } else {
+            ret = do_put_request(
+                    sn,
+                    locktoken,
+                    TRUE,
+                    data->content,
+                    data->read,
+                    data->seek,
+                    data->length);
+        }
+
+        curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL);
+        curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L);
+        
+        long status = 0;
+        curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &status);
+        if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+            res->session->error = 0;
+            // cleanup node data
+            if(!data->read) {
+                cxFree(sn->mp->allocator, data->content);
+            }
+            data->content = NULL;
+            data->read = NULL;
+            data->length = 0;
+        } else {
+            dav_session_set_error(sn, ret, status);
+            return 1;
+        }
+    }
+    
+    // generate crypto-prop content
+    if(DAV_ENCRYPT_PROPERTIES(sn) && sn->key && (data->crypto_set || data->crypto_remove)) {
+        DavResource *crypto_res = dav_resource_new_href(sn, res->href);
+        int ret = 1;
+        
+        if(crypto_res) {
+            CxBuffer *rqbuf = create_cryptoprop_propfind_request();
+            ret = dav_propfind(res->session, res, rqbuf);
+            cxBufferFree(rqbuf);
+        }
+        
+        if(!ret) {
+            DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
+            CxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
+            if(!crypto_props) {
+                // resource hasn't encrypted properties yet
+                crypto_props = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); // create new map
+            }
+            
+            // remove all properties
+            if(data->crypto_remove) {
+                CxIterator i = cxListIterator(data->crypto_remove);
+                cx_foreach(DavProperty *, property, i) {
+                    if(cxMapSize(crypto_props) == 0) {
+                        break; // map already empty, can't remove any more
+                    }
+
+                    cxmutstr key = dav_property_key(property->ns->name, property->name);
+                    DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length));
+                    if(existing_prop) {
+                        // TODO: free existing_prop
+                    }                
+                    free(key.ptr);
+                }
+            }
+            
+            // set properties
+            if(data->crypto_set) {
+                CxIterator i = cxListIterator(data->crypto_set);
+                cx_foreach(DavProperty *, property, i) {
+                    cxmutstr keystr = dav_property_key(property->ns->name, property->name);
+                    CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+                    DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key);
+                    cxMapPut(crypto_props, key, property);
+                    if(existing_prop) {
+                        // TODO: free existing_prop
+                    }  
+                    free(keystr.ptr);
+                }
+            }
+            
+            DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props);
+            if(crypto_prop_value) {
+                DavProperty *new_crypto_prop = createprop(sn, DAV_NS, "crypto-prop");
+                new_crypto_prop->value = crypto_prop_value;
+                add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop);
+            }
+            
+            dav_resource_free(crypto_res);
+        }
+        
+        if(ret) {
+            return 1;
+        }
+    }
+    
+    // store properties
+    int r = 0;
+    sn->error = DAV_OK;
+    if(data->set || data->remove > 0) {
+        CxBuffer *request = create_proppatch_request(data);
+        CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        //printf("request:\n%.*s\n\n", request->pos, request->space);
+
+        CURLcode ret = do_proppatch_request(sn, locktoken, request, response);
+        long status = 0;
+        curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+        if(ret == CURLE_OK && status == 207) {
+            //printf("%s\n", response->space);
+            // TODO: parse response
+            // TODO: cleanup node data correctly
+            data->set = NULL;
+            data->remove = NULL;
+        } else {
+            dav_session_set_error(sn, ret, status);
+            r = -1;
+        }
+        
+        cxBufferFree(request);
+        cxBufferFree(response);
+    }
+     
+    return r;
+}
+
+#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
+static void set_progressfunc(DavResource *res) {
+    CURL *handle = res->session->handle;
+    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
+    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, res);
+    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
+}
+
+static void unset_progressfunc(DavResource *res) {
+    CURL *handle = res->session->handle;
+    curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, NULL);
+    curl_easy_setopt(handle, CURLOPT_XFERINFODATA, NULL);
+    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
+}
+#else
+static void set_progressfunc(DavResource *res) {
+    
+}
+static void unset_progressfunc(DavResource *res) {
+    
+}
+#endif
+
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_fnc) { 
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(res->session, dav_resource_get_href(res));
+    
+    // check encryption
+    AESDecrypter *dec = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_CONTENT(sn)) {
+        char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+        if(keyname) {
+            key = dav_context_get_key(sn->context, keyname);
+            if(key) {
+                dec = aes_decrypter_new(key, stream, write_fnc);
+                stream = dec;
+                write_fnc = (dav_write_func)aes_write;
+            }
+        }
+    }
+    
+    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(handle, CURLOPT_PUT, 0L);
+    curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_fnc);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, stream);
+    
+    if(sn->get_progress) {
+        set_progressfunc(res);
+    }
+    
+    long status = 0;
+    CURLcode ret = dav_session_curl_perform(sn, &status);
+    
+    if(sn->get_progress) {
+        unset_progressfunc(res);
+    }
+    
+    char *hash = NULL;
+    if(dec) {
+        aes_decrypter_shutdown(dec); // get final bytes
+        
+        // get hash
+        unsigned char sha[DAV_SHA256_DIGEST_LENGTH];
+        dav_get_hash(&dec->sha256, sha);
+        hash = util_hexstr(sha, DAV_SHA256_DIGEST_LENGTH);
+        
+        aes_decrypter_close(dec);
+    }
+    
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        int verify_failed = 0;
+        if(DAV_DECRYPT_CONTENT(sn) && key) {
+            // try to verify the content
+            char *res_hash = dav_get_string_property_ns(res, DAV_NS, "crypto-hash");
+
+            if(res_hash) {
+                size_t len = 0;
+                char *dec_hash = aes_decrypt(res_hash, &len, key);
+                char *hex_hash = util_hexstr((unsigned char*)dec_hash, len);
+                if(strcmp(hash, hex_hash)) {
+                    verify_failed = 1;
+                }
+                free(dec_hash);
+                free(hex_hash);
+            }
+        }
+        if(hash) {
+            free(hash);
+        }
+        
+        if(verify_failed) {
+            res->session->error = DAV_CONTENT_VERIFICATION_ERROR;
+            return 1;
+        }
+        
+        res->session->error = DAV_OK;
+        return 0;
+    } else {
+        if(hash) {
+            free(hash);
+        }
+        dav_session_set_error(res->session, ret, status);
+        return 1;
+    }
+}
+
+DavResource* dav_create_child(DavResource *parent, char *name) {
+    DavResource *res = dav_resource_new_child(parent->session, parent, name);
+    if(dav_create(res)) {
+        dav_resource_free(res);
+        return NULL;
+    } else {
+        return res;
+    }
+}
+
+int dav_delete(DavResource *res) {
+    CURL *handle = res->session->handle;
+    util_set_url(res->session, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CxBuffer *response = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CURLcode ret = do_delete_request(res->session, locktoken, response);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    int r = 0;
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        res->session->error = DAV_OK;
+        res->exists = 0;
+        
+        // TODO: parse response
+        // TODO: free res
+    } else {
+        dav_session_set_error(res->session, ret, status);
+        r = 1;
+    }
+    
+    cxBufferFree(response);
+    return r;
+}
+
+static int create_ancestors(DavSession *sn, char *href, char *path) {
+    CURL *handle = sn->handle;
+    CURLcode code;
+    
+    DavLock *lock = dav_get_lock(sn, path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    long status = 0;
+    int ret = 0;
+    
+    if(strlen(path) <= 1) {
+        return 0;
+    }
+    
+    char *p = util_parent_path(path);
+    char *h = util_parent_path(href);
+    
+    for(int i=0;i<2;i++) {
+        util_set_url(sn, h);
+        code = do_mkcol_request(sn, locktoken);
+        curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
+        if(status == 201) {
+            // resource successfully created
+            char *name = (char*)util_resource_name(p);
+            int len = strlen(name);
+            if(name[len - 1] == '/') {
+                name[len - 1] = '\0';
+            }
+            if(resource_add_crypto_info(sn, h, name, NULL)) {
+                sn->error = DAV_ERROR;
+                dav_session_set_errstr(sn, "Cannot set crypto properties for ancestor");
+            }
+            break;
+        } else if(status == 405) {
+            // parent already exists
+            break;
+        } else if(status == 409) {
+            // parent doesn't exist
+            if(create_ancestors(sn, h, p)) {
+                ret = 1;
+                break;
+            }
+        } else {
+            dav_session_set_error(sn, code, status);
+            ret = 1;
+            break;
+        }
+    }
+    
+    free(p);
+    free(h);
+    return ret;
+}
+
+static int create_resource(DavResource *res, int *status) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode code;
+    if(res->iscollection) {
+        code = do_mkcol_request(sn, locktoken);
+    } else {
+        code = do_put_request(sn, locktoken, TRUE, "", NULL, NULL, 0); 
+    }
+    long s = 0;
+    curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &s);
+    *status = s;
+    if(code == CURLE_OK && (s >= 200 && s < 300)) {
+        sn->error = DAV_OK;
+        // if the session has encrypted file names, add crypto infos
+        if(!resource_add_crypto_info(sn, res->href, res->name, NULL)) {
+            // do a minimal propfind request
+            CxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0);
+            int ret = dav_propfind(sn, res, rqbuf);
+            cxBufferFree(rqbuf);
+            return ret;
+        } else {
+            return 1;
+        }
+    } else {
+        dav_session_set_error(sn, code, s);
+        return 1;
+    }
+}
+
+int dav_create(DavResource *res) {
+    int status;
+    if(!create_resource(res, &status)) {
+        // resource successfully created
+        res->exists = 1;
+        return 0;
+    }
+    
+    if(status == 403 || status == 409 || status == 404) {
+        // create intermediate collections
+        if(create_ancestors(res->session, res->href, res->path)) {
+            return 1;
+        }
+    }
+    
+    return create_resource(res, &status);
+}
+
+int dav_exists(DavResource *res) {
+    if(!dav_load_prop(res, NULL, 0)) {
+        res->exists = 1;
+        return 1;
+    } else {
+        if(res->session->error == DAV_NOT_FOUND) {
+            res->exists = 0;
+        }
+        return 0;
+    }
+}
+
+static int dav_cp_mv_url(DavResource *res, char *desturl, _Bool copy, _Bool override) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(sn, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode ret = do_copy_move_request(sn, desturl, locktoken, copy, override);
+    
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        return 0;
+    } else {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+}
+
+static int dav_cp_mv(DavResource *res, char *newpath, _Bool copy, _Bool override) {
+    char *dest = dav_session_get_href(res->session, newpath);
+    char *desturl = util_get_url(res->session, dest);
+    dav_session_free(res->session, dest);
+    
+    int ret = dav_cp_mv_url(res, desturl, copy, override);
+    free(desturl);
+    return ret;
+}
+
+int dav_copy(DavResource *res, char *newpath) {
+    return dav_cp_mv(res, newpath, true, false);
+}
+
+int dav_move(DavResource *res, char *newpath) {
+    return dav_cp_mv(res, newpath, false, false);
+}
+
+int dav_copy_o(DavResource *res, char *newpath, DavBool override) {
+    return dav_cp_mv(res, newpath, true, override);
+}
+
+int dav_move_o(DavResource *res, char *newpath, DavBool override) {
+    return dav_cp_mv(res, newpath, false, override);
+}
+
+int dav_copyto(DavResource *res, char *url, DavBool override) {
+    return dav_cp_mv_url(res, url, true, override);
+}
+
+int dav_moveto(DavResource *res, char *url, DavBool override) {
+    return dav_cp_mv_url(res, url, false, override);
+}
+
+int dav_lock(DavResource *res) {
+    return dav_lock_t(res, 0);
+}
+
+int dav_lock_t(DavResource *res, time_t timeout) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    CxBuffer *request = create_lock_request();
+    CxBuffer *response = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CURLcode ret = do_lock_request(sn, request, response, timeout);
+    
+    //printf("\nlock\n");
+    //printf("%.*s\n\n", request->size, request->space);
+    //printf("%.*s\n\n", response->size, response->space);
+    
+    cxBufferFree(request);
+    
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        LockDiscovery lock;
+        int parse_error = parse_lock_response(sn, response, &lock);
+        cxBufferFree(response);
+        if(parse_error) {
+            sn->error = DAV_ERROR;
+            return -1;
+        }
+        
+        DavLock *l = dav_create_lock(sn, lock.locktoken, lock.timeout);
+        free(lock.locktoken);
+        free(lock.timeout);
+        
+        int r = 0;
+        if(res->iscollection) {
+            r = dav_add_collection_lock(sn, res->path, l);
+        } else {
+            r = dav_add_resource_lock(sn, res->path, l);
+        }
+        
+        if(r == 0) {
+            return 0;
+        } else {
+            (void)dav_unlock(res);
+            sn->error = DAV_ERROR;
+            dav_destroy_lock(sn, l);
+            return -1;
+        }
+    } else {
+        dav_session_set_error(sn, ret, status);
+        cxBufferFree(response);
+        return -1;
+    }
+}
+
+int dav_unlock(DavResource *res) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    if(!lock) {
+        sn->error = DAV_ERROR;
+        return -1;
+    }
+    
+    CURLcode ret = do_unlock_request(sn, lock->token);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        dav_remove_lock(sn, res->path, lock);
+    } else {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+    
+    return 0;
+}
+
+
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash) {
+    if(!DAV_IS_ENCRYPTED(sn)) {
+        return 0;
+    }
+    
+    CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
+    CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    util_set_url(sn, href);
+    // TODO: lock
+    CURLcode ret = do_proppatch_request(sn, NULL, request, response);
+    cxBufferFree(request);
+    long status = 0;
+    curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        // TODO: parse response
+        sn->error = DAV_OK;   
+        cxBufferFree(response);
+        return 0;
+    } else {
+        dav_session_set_error(sn, ret, status);
+        cxBufferFree(response);
+        return 1;
+    }
+}
+
+/* ----------------------------- crypto-prop  ----------------------------- */
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) {
+    if(!sn->key) {
+        return NULL;
+    }
+    
+    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // create an xml document containing all properties
+    CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+    cxDefineDestructor(nsmap, free);
+    cxMapPut(nsmap, cx_hash_key_str("DAV:"), strdup("D"));
+    
+    cxBufferPutString(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    cxBufferPutString(content, "<D:prop xmlns:D=\"DAV:\">\n");
+    
+    CxIterator i = cxMapIteratorValues(properties);
+    cx_foreach(DavProperty*, prop, i) {
+        DavXmlNode pnode;
+        pnode.type = DAV_XML_ELEMENT;
+        pnode.namespace = prop->ns->name;
+        pnode.name = prop->name;
+        pnode.prev = NULL;
+        pnode.next = NULL;
+        pnode.children = prop->value;
+        pnode.parent = NULL;
+        pnode.attributes = NULL;
+        pnode.content = NULL;
+        pnode.contentlength = 0;
+        
+        dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode);
+        cxBufferPut(content, '\n');
+    }
+    
+    cxBufferPutString(content, "</D:prop>");
+    
+    cxMapDestroy(nsmap);
+    
+    // encrypt xml document
+    char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
+    cxBufferDestroy(content);
+    
+    DavXmlNode *ret = NULL;
+    if(crypto_prop_content) {
+        ret = dav_text_node(sn, crypto_prop_content);
+        free(crypto_prop_content);
+    }    
+    return ret;
+}
+
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) {
+    if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) {
+        return NULL;
+    }
+    
+    return parse_crypto_prop_str(sn, key, node->content);
+}
+
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) {
+    size_t len = 0;
+    char *dec_str = aes_decrypt(content, &len, key);
+    
+    xmlDoc *doc = xmlReadMemory(dec_str, len, NULL, NULL, 0);
+    free(dec_str);
+    if(!doc) {
+        return NULL;
+    }
+    
+    int err = 0;
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    if(xml_root) {
+        if(
+                !xml_root->ns ||
+                !xstreq(xml_root->name, "prop") ||
+                !xstreq(xml_root->ns->href, "DAV:"))
+        {
+            err = 1;
+        }
+    } else {
+        err = 1;
+    }
+    
+    if(err) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    // ready to get the properties
+    CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+    xmlNode *n = xml_root->children;
+    while(n) {
+        if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) {
+            DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+            property->name = dav_session_strdup(sn, (const char*)n->name);
+            property->ns = dav_session_malloc(sn, sizeof(DavNamespace));
+            property->ns->name = dav_session_strdup(sn, (const char*)n->ns->href);
+            property->ns->prefix = n->ns->prefix ?
+                    dav_session_strdup(sn, (const char*)n->ns->prefix) : NULL;
+            property->value = n->children ? dav_convert_xml(sn, n->children) : NULL;
+            
+            cxmutstr propkey = dav_property_key(property->ns->name, property->name);
+            cxMapPut(map, cx_hash_key_cxstr(propkey), property);
+            cx_strfree(&propkey);
+        }
+        n = n->next;
+    }
+    
+    xmlFreeDoc(doc);
+    if(cxMapSize(map) == 0) {
+        cxMapDestroy(map);
+        return NULL;
+    }
+    return map;
+}
+
+
+/* ----------------------------- streams  ----------------------------- */
+
+static size_t in_write(const char *ptr, size_t size, size_t nitems, void *in_stream) {
+    DavInputStream *in = in_stream;
+    size_t len = size * nitems;
+    
+    if(in->alloc < len) {
+        char *newb = realloc(in->buffer, len);
+        if(!newb) {
+            if(in->buffer) free(in->buffer);
+            in->eof = 1;
+            return 0;
+        }
+        
+        in->buffer = newb;
+        in->alloc = len;
+    }
+    
+    memcpy(in->buffer, ptr, len);
+    
+    in->size = len;
+    in->pos = 0;
+    
+    return nitems;
+}
+
+/*
+DavInputStream* dav_inputstream_open(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    DavInputStream *in = dav_session_malloc(sn, sizeof(DavInputStream));
+    if(!in) {
+        return NULL;
+    }
+    memset(in, 0, sizeof(DavInputStream));
+    
+    in->res = res;
+    
+    in->c = curl_easy_duphandle(sn->handle); 
+    char *url = util_get_url(sn, dav_resource_get_href(res));
+    curl_easy_setopt(in->c, CURLOPT_URL, url);
+    free(url);
+    
+    in->m = curl_multi_init();
+    
+    curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(in->c, CURLOPT_PUT, 0L);
+    curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L);
+    
+    curl_multi_add_handle(in->m, in->c);
+    
+    dav_write_func write_fnc = (dav_write_func)in_write;
+    void *stream = in;
+    
+    // check encryption 
+    AESDecrypter *dec = NULL;
+    DavKey *key = NULL;
+    if(DAV_DECRYPT_CONTENT(sn)) {
+        char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+        if(keyname) {
+            key = dav_context_get_key(sn->context, keyname);
+            if(key) {
+                dec = aes_decrypter_new(key, stream, write_fnc);
+                stream = dec;
+                write_fnc = (dav_write_func)aes_write;
+            }
+        }
+    }
+    
+    curl_easy_setopt(in->c, CURLOPT_WRITEFUNCTION, write_fnc);
+    curl_easy_setopt(in->c, CURLOPT_WRITEDATA, stream);
+    
+    in->dec = dec;
+    
+    return in;
+}
+
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in) {
+    size_t len = in->size - in->pos;
+    size_t rl = size * nitems;
+    if(len > 0) {
+        len = rl > len ? len : rl;
+        len -= len % size;
+        memcpy(buf, in->buffer + in->pos, len);
+        in->pos += len;
+        return len / size;
+    }
+    in->size = 0;
+    
+    if(in->eof) {
+        if(in->dec) {
+            aes_decrypter_shutdown(in->dec); // get final bytes
+            aes_decrypter_close(in->dec);
+            in->dec = NULL;
+        } else {
+            return 0;
+        }
+    } else {
+        int running;
+        while(!in->eof && in->size == 0) {
+            CURLMcode r = curl_multi_perform(in->m, &running);
+            if(r != CURLM_OK || running == 0) {
+                in->eof = 1;
+                break;
+            }
+
+            int numfds;
+            if(curl_multi_poll(in->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+                in->eof = 1;
+            }
+        }
+    }
+    
+    return in->size > 0 ? dav_read(buf, size, nitems, in) : 0;
+}
+
+void dav_inputstream_close(DavInputStream *in) {
+    curl_multi_cleanup(in->m);
+    curl_easy_cleanup(in->c);
+    if(in->buffer) free(in->buffer);
+    dav_session_free(in->res->session, in);
+}
+
+
+static size_t out_read(char *ptr, size_t size, size_t nitems, void *out_stream) {
+    DavOutputStream *out = out_stream;
+    size_t len = size * nitems;
+    size_t available = out->size - out->pos;
+    if(available == 0) {
+        return 0;
+    }
+    
+    size_t r = len > available ? available : len;
+    r -= r % size;
+    memcpy(ptr, out->buffer + out->pos, r);
+    
+    out->pos += r;
+
+    return r / size;
+}
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+    return s*n;
+}
+
+DavOutputStream* dav_outputstream_open(DavResource *res) {
+    DavSession *sn = res->session;
+    
+    DavOutputStream *out = dav_session_malloc(sn, sizeof(DavOutputStream));
+    if(!out) {
+        return NULL;
+    }
+    memset(out, 0, sizeof(DavOutputStream));
+    
+    out->res = res;
+    
+    out->c = curl_easy_duphandle(sn->handle); 
+    char *url = util_get_url(sn, dav_resource_get_href(res));
+    curl_easy_setopt(out->c, CURLOPT_URL, url);
+    free(url);
+    
+    out->m = curl_multi_init();
+    curl_multi_add_handle(out->m, out->c);
+    
+    void *stream = out;
+    dav_read_func read_fnc = (dav_read_func)out_read;
+    
+    // if encryption or hashing in enabled, we need a stream wrapper
+    if(DAV_ENCRYPT_CONTENT(sn) && sn->key) {
+        AESEncrypter *enc = aes_encrypter_new(sn->key, out, (dav_read_func)out_read, NULL);
+        out->enc = enc;
+        stream = enc;
+        read_fnc = (dav_read_func)aes_read;
+    } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+        HashStream *hstr = dav_session_malloc(sn, sizeof(HashStream));
+        out->hstr = hstr;
+        init_hash_stream(hstr, out, (dav_read_func)out_read, NULL);
+        stream = hstr;
+        read_fnc = (dav_read_func)dav_read_h;
+    }
+    
+    curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL);
+    curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL);
+    curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL);
+    curl_easy_setopt(out->c, CURLOPT_PUT, 1L);
+    curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L);
+    curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc);
+    curl_easy_setopt(out->c, CURLOPT_READDATA, stream);
+    curl_easy_setopt(out->c, CURLOPT_SEEKFUNCTION, NULL);
+    curl_easy_setopt(out->c, CURLOPT_INFILESIZE, -1);
+    curl_easy_setopt(out->c, CURLOPT_INFILESIZE_LARGE, -1L);
+    
+    curl_easy_setopt(out->c, CURLOPT_WRITEFUNCTION, dummy_write);
+    curl_easy_setopt(out->c, CURLOPT_WRITEDATA, NULL);
+    
+    return out;
+}
+
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out) {
+    if(out->eof) return 0;
+    
+    out->buffer = buf;
+    out->size = size * nitems;
+    out->pos = 0;
+    
+    int running;
+    while(!out->eof && (out->size == 0 || out->size - out->pos > 0)) {
+        CURLMcode r = curl_multi_perform(out->m, &running);
+        if(r != CURLM_OK || running == 0) {
+            out->eof = 1;
+            break;
+        }
+        
+        int numfds;
+        if(curl_multi_poll(out->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+            out->eof = 1;
+        }
+    }
+    
+    return (out->size - out->pos) / size;
+}
+
+int dav_outputstream_close(DavOutputStream *out) {
+    DavSession *sn = out->res->session;
+    DavResource *res = out->res;
+    DavResourceData *data = res->data;
+    
+    int ret = 0;
+    
+    dav_write(NULL, 1, 0, out);
+    
+    curl_multi_cleanup(out->m);
+    curl_easy_cleanup(out->c);
+    
+    int store = 0;
+    if(out->enc) {
+        // get sha256 hash
+        char hash[32];
+        dav_get_hash(&out->enc->sha256, (unsigned char*)data->hash);
+        aes_encrypter_close(out->enc);
+        char *enc_hash = aes_encrypt(hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+        // add crypto properties
+        if(resource_add_crypto_info(sn, out->res->href, out->res->name, enc_hash)) {
+            ret = 1;
+        }
+        free(enc_hash);
+    } else if(out->hstr) {
+        dav_hash_final(out->hstr->sha, (unsigned char*)data->hash);
+        char *hash = util_hexstr((unsigned char*)data->hash, 32);
+        dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
+        free(hash);
+        dav_session_free(sn, out->hstr);
+        store = 1;
+    }
+    
+    if(store) {
+        ret = dav_store(out->res);
+    }
+    
+    dav_session_free(out->res->session, out);
+    
+    return ret;
+}
+
+*/
diff --git a/libidav/resource.h b/libidav/resource.h
new file mode 100644 (file)
index 0000000..37f2033
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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 RESOURCE_H
+#define        RESOURCE_H
+
+#include "webdav.h"
+#include "crypto.h"
+#include <cx/string.h>
+#include <cx/hash_key.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavResourceData   DavResourceData;
+
+struct DavResourceData {
+    CxMap  *properties;
+    CxList *set;
+    CxList *remove;
+    CxList *crypto_set;
+    CxList *crypto_remove;
+    
+    /*
+     * properties encapsulated in a crypto-prop property or NULL
+     */
+    CxMap *crypto_properties;
+    
+    /*
+     * char* or stream
+     */
+    void      *content;
+    /*
+     * if NULL, content is a char*
+     */
+    dav_read_func read;
+    /*
+     * curl seek func
+     */
+    dav_seek_func seek;
+    /*
+     * content length
+     */
+    size_t    length;
+    
+    /*
+     * sha256 content hash
+     */
+    char hash[32];
+};
+
+/*
+ * read wrapper with integrated hashing
+ */
+typedef struct {
+    DAV_SHA_CTX *sha;
+    void *stream;
+    dav_read_func read;
+    dav_seek_func seek;
+    int error;
+} HashStream;
+
+struct DavInputStream {
+    DavResource *res;
+    CURLM *m;
+    CURL *c;
+    AESDecrypter *dec;
+    char *buffer;
+    size_t alloc;
+    size_t size;
+    size_t pos;
+    int eof;
+};
+
+struct DavOutputStream {
+    DavResource *res;
+    CURLM *m;
+    CURL *c;
+    AESEncrypter *enc;
+    HashStream *hstr;
+    const char *buffer;
+    size_t size;
+    size_t pos;
+    int eof;
+};
+
+DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href);
+
+void resource_free_properties(DavSession *sn, CxMap *properties);
+
+void resource_set_href(DavResource *res, cxstring href);
+
+void resource_set_info(DavResource *res, const char *href_str);
+DavResourceData* resource_data_new(DavSession *sn);
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val);
+void resource_set_crypto_properties(DavResource *res, CxMap *cprops);
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key);
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key);
+void resource_add_child(DavResource *parent, DavResource *child);
+void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr);
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash);
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name);
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties);
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node);
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RESOURCE_H */
+
diff --git a/libidav/session.c b/libidav/session.c
new file mode 100644 (file)
index 0000000..17806b3
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/buffer.h>
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+
+#include "utils.h"
+#include "session.h"
+#include "resource.h"
+#include "methods.h"
+
+DavSession* dav_session_new(DavContext *context, char *base_url) {
+    if(!base_url) {
+        return NULL;
+    }
+    cxstring url = cx_str(base_url);
+    if(url.length == 0) {
+        return NULL;
+    }
+    DavSession *sn = malloc(sizeof(DavSession));
+    memset(sn, 0, sizeof(DavSession));
+    sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
+    sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    sn->key = NULL;
+    sn->errorstr = NULL;
+    sn->error = DAV_OK;
+    sn->flags = 0;
+    
+    dav_session_set_baseurl(sn, base_url);
+    
+    sn->handle = curl_easy_init();
+    curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
+    
+    // lock manager is created on-demand
+    sn->locks = NULL;
+
+    // set proxy
+    DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy
+                                                  : context->http_proxy;
+    
+    if (proxy->url) {
+        curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
+        if (proxy->username) {
+            curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
+                proxy->username);
+            if (proxy->password) {
+                curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
+                    proxy->password);
+            } else {
+                // TODO: prompt
+            }
+        }
+        if(proxy->no_proxy) {
+            curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
+                proxy->no_proxy);
+        }
+    }
+    
+    // set url
+#if LIBCURL_VERSION_NUM >= 0x072D00
+    curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
+#endif
+    curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
+    
+    // add to context
+    dav_context_add_session(context, sn);
+    sn->context = context;
+    
+    return sn;
+}
+
+DavSession* dav_session_new_auth(
+        DavContext *context,
+        char *base_url,
+        char *user,
+        char *password)
+{
+    DavSession *sn = dav_session_new(context, base_url);
+    if(!sn) {
+        return NULL;
+    }
+    dav_session_set_auth(sn, user, password);
+    return sn;
+}
+
+DavSession* dav_session_clone(DavSession *sn) {
+    CURL *newhandle = curl_easy_duphandle(sn->handle);
+
+    DavSession *newsn = malloc(sizeof(DavSession));
+    memset(newsn, 0, sizeof(DavSession));
+    newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
+    newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    newsn->key = sn->key;
+    newsn->errorstr = NULL;
+    newsn->error = DAV_OK;
+    newsn->flags = 0;
+
+    newsn->handle = newhandle;
+
+    newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr;
+    newsn->auth_prompt = sn->auth_prompt;
+    newsn->authprompt_userdata = sn->authprompt_userdata;
+    newsn->logfunc = sn->logfunc;
+    newsn->get_progress = sn->get_progress;
+    newsn->put_progress = sn->put_progress;
+    newsn->progress_userdata = sn->progress_userdata;
+
+    // add to context
+    dav_context_add_session(sn->context, newsn);
+    newsn->context = sn->context;
+
+    return newsn;
+}
+
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password) {
+    if(user && password) {
+        dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
+    }
+}
+
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) {
+    if(user.length > 0 && password.length > 0) {
+        size_t upwdlen = user.length + password.length + 2;
+        char *upwdbuf = malloc(upwdlen);
+        snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr);
+        curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
+        free(upwdbuf);
+    }
+}
+
+void dav_session_set_baseurl(DavSession *sn, char *base_url) {
+    const CxAllocator *a = sn->mp->allocator;
+    if(sn->base_url) {
+        cxFree(a, sn->base_url);
+    }
+    
+    cxstring url = cx_str(base_url);
+    if(url.ptr[url.length - 1] == '/') {
+        cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
+        sn->base_url = url_m.ptr;
+    } else {
+        char *url_str = cxMalloc(a, url.length + 2);
+        memcpy(url_str, base_url, url.length);
+        url_str[url.length]     = '/';
+        url_str[url.length + 1] = '\0';
+        sn->base_url = url_str;
+    }
+}
+
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
+    sn->key = key;
+    // TODO: review sanity
+    if(flags != 0) {
+        sn->flags |= flags;
+    } else {
+        sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
+    }
+}
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
+    sn->auth_prompt = func;
+    sn->authprompt_userdata = userdata;
+}
+
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) {
+    sn->get_progress = get;
+    sn->put_progress = put;
+    sn->progress_userdata = userdata;
+}
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
+    return dav_session_curl_perform_buf(sn, NULL, NULL, status);
+}
+
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) {
+    CURLcode ret = curl_easy_perform(sn->handle);
+    long http_status;
+    curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+    if(ret == CURLE_OK) {
+        if(sn->logfunc) {
+            char *log_method;
+            char *log_url;
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url);
+            curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method);
+            char *log_reqbody = NULL;
+            size_t log_reqbodylen = 0;
+            char *log_rpbody = NULL;
+            size_t log_rpbodylen = 0;
+            if(request) {
+                log_reqbody = request->space;
+                log_reqbodylen = request->size;
+            }
+            if(response) {
+                log_rpbody = response->space;
+                log_rpbodylen = response->size;
+            }
+            sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
+        }
+        
+        if(http_status == 401 && sn->auth_prompt) {
+            if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
+                if(request) {
+                    cxBufferSeek(request, 0, SEEK_SET);
+                }
+                if(response) {
+                    cxBufferSeek(response, 0, SEEK_SET);
+                }
+                ret = curl_easy_perform(sn->handle);
+                curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+            }
+        }
+        
+    }
+    
+    if(status) {
+        *status = http_status;
+    }
+    return ret;
+}
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->get_progress) {
+        sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+    DavResource *res = clientp;
+    DavSession *sn = res->session;
+    if(sn->put_progress) {
+        sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
+    }
+    return 0;
+}
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
+    if(status > 0) {
+        switch(status) {
+            default: {
+                switch(c) {
+                    default: sn->error = DAV_ERROR;
+                }
+                break;
+            }
+            case 401: sn->error = DAV_UNAUTHORIZED; break;
+            case 403: sn->error = DAV_FORBIDDEN; break;
+            case 404: sn->error = DAV_NOT_FOUND; break;
+            case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
+            case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
+            case 409: sn->error = DAV_CONFLICT; break;
+            case 412: sn->error = DAV_PRECONDITION_FAILED; break;
+            case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
+            case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
+            case 423: sn->error = DAV_LOCKED; break;
+            case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
+        }
+    } else {
+        switch(c) {
+            case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
+            case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
+            case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
+            case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
+            case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
+            case CURLE_SSL_CONNECT_ERROR:
+            case CURLE_PEER_FAILED_VERIFICATION:
+            case CURLE_SSL_ENGINE_NOTFOUND:
+            case CURLE_SSL_ENGINE_SETFAILED:
+            case CURLE_SSL_CERTPROBLEM:
+            case CURLE_SSL_CIPHER:
+//#ifndef CURLE_SSL_CACERT
+//            case CURLE_SSL_CACERT:
+//#endif
+            case CURLE_SSL_CACERT_BADFILE:
+            case CURLE_SSL_SHUTDOWN_FAILED:
+            case CURLE_SSL_CRL_BADFILE:
+            case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
+            default: sn->error = DAV_ERROR; break;
+        }
+    }
+    if(c != CURLE_OK) {
+        dav_session_set_errstr(sn, curl_easy_strerror(c));
+    } else {
+        dav_session_set_errstr(sn, NULL);
+    }
+}
+
+void dav_session_set_errstr(DavSession *sn, const char *str) {
+    if(sn->errorstr) {
+        dav_session_free(sn, sn->errorstr);
+    }
+    char *errstr = NULL;
+    if(str) {
+        errstr = dav_session_strdup(sn, str);
+    }
+    sn->errorstr = errstr;
+}
+
+void dav_session_destroy(DavSession *sn) { 
+    // remove session from context
+    if (dav_context_remove_session(sn->context, sn)) {
+        fprintf(stderr, "Error: session not found in ctx->sessions\n");
+        dav_session_destructor(sn);
+    }
+}
+
+void dav_session_destructor(DavSession *sn) {
+    cxMempoolDestroy(sn->mp);
+    curl_easy_cleanup(sn->handle);
+    free(sn);
+}
+
+
+void* dav_session_malloc(DavSession *sn, size_t size) {
+    return cxMalloc(sn->mp->allocator, size);
+}
+
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
+    return cxCalloc(sn->mp->allocator, nelm, size);
+}
+
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
+    return cxRealloc(sn->mp->allocator, ptr, size);
+}
+
+void  dav_session_free(DavSession *sn, void *ptr) {
+    cxFree(sn->mp->allocator, ptr);
+}
+
+char* dav_session_strdup(DavSession *sn, const char *str) {
+    return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr;
+}
+
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path) {
+    if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
+        // non encrypted file names
+        char *url = util_path_to_url(sn, path);
+        char *href = dav_session_strdup(sn, util_url_path(url));
+        free(url);
+        return href;
+    } else {
+        return NULL;
+    }
+}
+
+char* dav_session_get_href(DavSession *sn, const char *path) {
+    if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
+        cxstring p = cx_str(path);
+        CxBuffer href;
+        CxBuffer pbuf;
+        cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        int start = 0;
+        int begin = 0;
+        
+        // check path cache
+        char *cp = strdup(path);
+        //printf("cp: %s\n", cp);
+        while(strlen(cp) > 1) {
+            char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
+            if(cached) {
+                start = strlen(cp);
+                begin = start;
+                cxBufferPutString(&href, cached);
+                break;
+            } else {
+                // check, if the parent path is cached
+                char *f = cp;
+                cp = util_parent_path(cp);
+                free(f);
+            }
+        }
+        free(cp);
+        if(href.pos == 0) {
+            // if there are no cached elements we have to add the base url path
+            // to the href buffer
+            cxBufferPutString(&href, util_url_path(sn->base_url));
+        }
+        
+        // create resource for name lookup
+        cxmutstr rp = cx_strdup(cx_strn(path, start));
+        DavResource *root = dav_resource_new(sn, rp.ptr);
+        free(rp.ptr);
+        resource_set_href(root, cx_strn(href.space, href.pos));
+        
+        // create request buffer for propfind requests
+        CxBuffer *rqbuf = create_basic_propfind_request();
+        
+        cxstring remaining = cx_strsubs(p, start);
+        CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX);
+        DavResource *res = root;
+        cxBufferPutString(&pbuf, res->path);
+        // iterate over all remaining path elements
+        cxstring elm;
+        while(cx_strtok_next(&elms, &elm)) {
+            if(elm.length > 0) {
+                //printf("elm: %.*s\n", elm.length, elm.ptr);
+                DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
+                
+                // if necessary add a path separator
+                if(pbuf.space[pbuf.pos-1] != '/') {
+                    if(href.space[href.pos-1] != '/') {
+                        cxBufferPut(&href, '/');
+                    }
+                    cxBufferPut(&pbuf, '/');
+                }
+                // add last path/href to the cache
+                cxstring pp = cx_strn(pbuf.space, pbuf.size);
+                cxstring hh = cx_strn(href.space, href.size);
+                dav_session_cache_path(sn, pp, hh);
+                
+                cxBufferWrite(elm.ptr, 1, elm.length, &pbuf);
+                if(child) {
+                    // href is already URL encoded, so don't encode again
+                    cxBufferPutString(&href, util_resource_name(child->href));
+                    res = child;
+                } else if(DAV_ENCRYPT_NAME(sn)) {
+                    char *random_name = util_random_str();
+                    cxBufferPutString(&href, random_name);
+                    free(random_name);
+                } else {
+                    // path is not URL encoded, so we have to do this here
+                    cxstring resname = cx_str(util_resource_name((const char*)path));
+                    // the name of collections ends with
+                    // a trailing slash, which MUST NOT be encoded
+                    if(resname.ptr[resname.length-1] == '/') {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length-1);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        cxBufferPut(&href, '/');
+                        curl_free(esc);
+                    } else  {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length);
+                        cxBufferWrite(esc, 1, strlen(esc), &href);
+                        curl_free(esc);
+                    }
+                }
+            }
+        }
+        
+        // if necessary add a path separator
+        if(p.ptr[p.length-1] == '/') {
+            if(href.space[href.pos-1] != '/') {
+                cxBufferPut(&href, '/');
+            }
+            cxBufferPut(&pbuf, '/');
+        }
+        // add the final path to the cache
+        cxstring pp = cx_strn(pbuf.space, pbuf.size);
+        cxstring hh = cx_strn(href.space, href.size);
+        dav_session_cache_path(sn, pp, hh);
+        
+        cxmutstr href_str = cx_strdup_a(
+                sn->mp->allocator,
+                cx_strn(href.space, href.size));
+        
+        // cleanup
+        dav_resource_free_all(root);
+        cxBufferFree(rqbuf);
+        
+        cxBufferDestroy(&pbuf);
+        cxBufferDestroy(&href);
+        
+        return href_str.ptr;
+    } else {
+        return dav_session_create_plain_href(sn, path);
+    }
+}
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) {
+    if(res && !dav_propfind(sn, res, rqbuf)) {
+        DavResource *child = res->children;
+        while(child) {
+            if(!strcmp(child->name, name)) {
+                return child;
+            }
+            child = child->next;
+        }
+    }
+    return NULL;
+}
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
+    CxHashKey path_key = cx_hash_key(path.ptr, path.length);
+    char *elm = cxMapGet(sn->pathcache, path_key);
+    if(!elm) {
+        cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
+        cxMapPut(sn->pathcache, path_key, href_s.ptr);
+    }
+}
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) {
+    DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
+    lock->path = NULL;
+    lock->token = dav_session_strdup(sn, token);
+    
+    // TODO: timeout
+    
+    return lock;
+}
+
+void dav_destroy_lock(DavSession *sn, DavLock *lock) {
+    dav_session_free(sn, lock->token);
+    if(lock->path) {
+        dav_session_free(sn, lock->path);
+    }
+    dav_session_free(sn, lock);
+}
+
+
+static int dav_lock_cmp(void const *left, void const *right) {
+    const DavLock *l = left;
+    const DavLock *r = right;
+    return strcmp(l->path, r->path);
+}
+
+static int create_lock_manager(DavSession *sn) {
+    // create lock manager
+    DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager));
+    locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16);
+    locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS);
+    sn->locks = locks;
+    return 0;
+}
+
+static DavLockManager* get_lock_manager(DavSession *sn) {
+    DavLockManager *locks = sn->locks;
+    if(!locks) {
+        if(create_lock_manager(sn)) {
+            return NULL;
+        }
+        locks = sn->locks;
+    }
+    return locks;
+}
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    CxHashKey path_key = cx_hash_key_str(path);
+    if(cxMapGet(locks->resource_locks, path_key)) {
+        return -1;
+    }
+    
+    cxMapPut(locks->resource_locks, path_key, lock);
+    return 0;
+}
+
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return -1;
+    }
+    
+    lock->path = dav_session_strdup(sn, path);
+    cxListAdd(locks->collection_locks, lock);
+    cxListSort(locks->collection_locks);
+    
+    return 0;
+}
+
+DavLock* dav_get_lock(DavSession *sn, const char *path) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return NULL;
+    }
+    
+    cxstring p = cx_str(path);
+    
+    DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
+    if(lock) {
+        return lock;
+    }
+    
+    CxIterator i = cxListIterator(locks->collection_locks);
+    cx_foreach(DavLock*, col_lock, i) {
+        int cmd = strcmp(path, col_lock->path);
+        if(cmd == 0) {
+            return col_lock;
+        } else if(cx_strprefix(p, cx_str(col_lock->path)))  {
+            return col_lock;
+        } else if(cmd > 0) {
+            break;
+        }
+    }
+    
+    return NULL;
+}
+
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) {
+    DavLockManager *locks = get_lock_manager(sn);
+    if(!locks) {
+        return;
+    }
+    
+    if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
+        return;
+    }
+
+    cxListFindRemove(locks->collection_locks, lock);
+}
diff --git a/libidav/session.h b/libidav/session.h
new file mode 100644 (file)
index 0000000..6759da4
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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 DAV_SESSION_H
+#define        DAV_SESSION_H
+
+#include <cx/buffer.h>
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// initial size of the session mempool
+#define DAV_SESSION_MEMPOOL_SIZE 1024
+// initial size of the path cache map
+#define DAV_PATH_CACHE_SIZE 32
+    
+#define DAV_ENCRYPT_NAME(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_NAME) == DAV_SESSION_ENCRYPT_NAME)
+    
+#define DAV_DECRYPT_NAME(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_NAME) == DAV_SESSION_DECRYPT_NAME)
+    
+#define DAV_ENCRYPT_CONTENT(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_CONTENT) == DAV_SESSION_ENCRYPT_CONTENT)
+    
+#define DAV_DECRYPT_CONTENT(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_CONTENT) == DAV_SESSION_DECRYPT_CONTENT)
+
+#define DAV_IS_ENCRYPTED(sn) \
+    (DAV_ENCRYPT_NAME(sn) || DAV_ENCRYPT_CONTENT(sn))
+    
+#define DAV_CRYPTO(sn) \
+    (DAV_ENCRYPT_NAME(sn) || DAV_DECRYPT_NAME(sn) || \
+     DAV_ENCRYPT_CONTENT(sn) || DAV_DECRYPT_CONTENT(sn))
+
+#define DAV_ENCRYPT_PROPERTIES(sn) \
+    (((sn)->flags & DAV_SESSION_ENCRYPT_PROPERTIES) == DAV_SESSION_ENCRYPT_PROPERTIES)
+
+#define DAV_DECRYPT_PROPERTIES(sn) \
+    (((sn)->flags & DAV_SESSION_DECRYPT_PROPERTIES) == DAV_SESSION_DECRYPT_PROPERTIES)
+    
+/*
+typedef struct DavPathCacheElement {
+    char *name;
+    char *encrypted_name;
+    int  exists;
+} DavPathCacheElement;
+*/
+    
+typedef struct DavLock DavLock;
+struct DavLock {
+    char *path;
+    char *token;
+};
+
+typedef struct DavLockManager {
+    CxMap  *resource_locks;
+    CxList *collection_locks;
+} DavLockManager;
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status);
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status);
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status);
+void dav_session_set_errstr(DavSession *sn, const char *str);
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path);
+
+char* dav_session_get_href(DavSession *sn, const char *path);
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name);
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href);
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout);
+void dav_destroy_lock(DavSession *sn, DavLock *lock);
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock);
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock);
+
+DavLock* dav_get_lock(DavSession *sn, const char *path);
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_SESSION_H */
+
diff --git a/libidav/utils.c b/libidav/utils.c
new file mode 100644 (file)
index 0000000..d1f3776
--- /dev/null
@@ -0,0 +1,1306 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <libxml/tree.h>
+#include <curl/curl.h>
+
+#ifdef _WIN32
+#include <conio.h>
+#define getpasswordchar() getch()
+#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\')
+#define PATH_SEPARATOR '\\'
+#else
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <termios.h>
+#define getpasswordchar() getchar()
+#define IS_PATH_SEPARATOR(c) (c == '/')
+#define PATH_SEPARATOR '/'
+#endif
+
+#include "webdav.h"
+#include "utils.h"
+#include "crypto.h"
+#include "session.h"
+
+/*
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/rand.h>
+*/
+
+static size_t extractval(cxstring str, char *result, char delim) {
+    size_t n = 0;
+    for(size_t i = 0; i < str.length ; i++) {
+        if(isdigit(str.ptr[i])) {
+            result[n++] = str.ptr[i];
+        } else if(str.ptr[i] != delim) {
+            return 0;
+        }
+    }
+    result[n] = '\0';
+    return n;
+}
+
+static time_t parse_iso8601(char *iso8601str) {
+
+    // safety
+    if(!iso8601str) {
+        return 0;
+    }
+    
+    // local vars
+    struct tm tparts;
+    memset(&tparts, 0, sizeof(struct tm));
+    long val;
+    char conv[16];
+    
+    // work on the trimmed string
+    cxstring date = cx_strtrim(cx_str(iso8601str));
+
+    cxstring time = cx_strchr(date, 'T');
+    if(time.length == 0) {
+        return 0;
+    }
+    date.length = time.ptr - date.ptr;
+    time.ptr++; time.length--;
+    
+    cxstring tzinfo;
+    if((tzinfo = cx_strchr(time, 'Z')).length > 0 ||
+        (tzinfo = cx_strchr(time, '+')).length > 0 ||
+        (tzinfo = cx_strchr(time, '-')).length > 0) {
+        
+        time.length = tzinfo.ptr - time.ptr;
+    }
+
+    // parse date
+    if((date.length != 8 && date.length != 10)
+            || extractval(date, conv , '-') != 8) {
+        return 0;
+    }
+    val = atol(conv);
+    if(val < 19000000L) {
+        return 0;
+    }
+    tparts.tm_mday = val % 100;
+    tparts.tm_mon = (val % 10000) / 100 - 1;
+    tparts.tm_year = val / 10000 - 1900;
+    
+    // parse time and skip possible fractional seconds
+    cxstring frac;
+    if((frac = cx_strchr(time, '.')).length > 0 ||
+        (frac = cx_strchr(time, ',')).length > 0) {
+        time.length = frac.ptr - time.ptr;
+    }
+    if((time.length != 6 && time.length != 8)
+            || extractval(time, conv , ':') != 6) {
+        return 0;
+    }
+    val = atol(conv);
+    tparts.tm_sec = val % 100;
+    tparts.tm_min = (val % 10000) / 100;
+    tparts.tm_hour = val / 10000;
+
+
+    // parse time zone (if any)
+    if(tzinfo.length == 0) {
+        // local time
+        tparts.tm_isdst = -1;
+        return mktime(&tparts);
+    } else if(!cx_strcmp(tzinfo, cx_str("Z"))) {
+#if defined(__FreeBSD__)
+        return timegm(&tparts);
+#elif defined(_WIN32)
+        return _mkgmtime(&tparts);
+#else
+        return mktime(&tparts) - timezone;
+#endif
+    } else if(tzinfo.ptr[0] == '+' || tzinfo.ptr[0] == '-') {
+        int sign = (tzinfo.ptr[0] == '+') ? -1 : 1;
+
+        if(tzinfo.length > 6) {
+            return 0;
+        } else {
+            tzinfo.ptr++; tzinfo.length--;
+            extractval(tzinfo, conv, ':');
+            val = atol(conv);
+            val = 60 * (val / 100) + (val % 100);
+#if defined(__FreeBSD__)
+            return timegm(&tparts) + (time_t) (60 * val * sign);
+#elif defined(_WIN32)
+            return _mkgmtime(&tparts) + (time_t)(60 * val * sign);
+#else
+            return mktime(&tparts) - timezone + (time_t) (60 * val * sign);            
+#endif
+        }
+    } else {
+        return 0;
+    }
+}
+
+
+time_t util_parse_creationdate(char *str) {
+    // parse a ISO-8601 date (rfc-3339)
+    // example: 2012-11-29T21:35:35Z
+    if(!str) {
+        return 0;
+    }
+    
+    return parse_iso8601(str);
+}
+
+time_t util_parse_lastmodified(char *str) {
+    // parse a rfc-1123 date
+    // example: Thu, 29 Nov 2012 21:35:35 GMT
+    if(!str) {
+        return 0;
+    } else {
+        time_t result = curl_getdate(str, NULL);
+        if(result == -1) {
+            // fall back to the ISO-8601 format (e.g. Microsoft Sharepoint
+            // illegally uses this format for lastmodified, but also some
+            // users might want to give an ISO-8601 date)
+            return util_parse_creationdate(str);
+        } else {
+            return result;
+        }
+    }
+}
+
+int util_getboolean(const char *v) {
+    if(v[0] == 'T' || v[0] == 't') {
+        return 1;
+    }
+    return 0;
+}
+
+int util_strtouint(const char *str, uint64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
+    char *end;
+    errno = 0;
+    uint64_t val = strtoull(str, &end, 0);
+    if(errno == 0 && *end == '\0') {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_strtoint(const char *str, int64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
+    char *end;
+    errno = 0;
+    int64_t val = strtoll(str, &end, 0);
+    if(errno == 0 && *end == '\0') {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_szstrtouint(const char *str, uint64_t *value) {
+    if (str == NULL || *str == '\0') return 0;
+    char *end;
+    errno = 0;
+    size_t len = strlen(str);
+    uint64_t val = strtoull(str, &end, 0);
+    if(errno != 0) {
+        return 0;
+    } if(end == str+len) {
+        *value = val;
+        return 1;
+    } else if(end == str+len-1) {
+        uint64_t mul = 1;
+        switch(end[0]) {
+            case 'k':
+            case 'K': mul = 1024; break;
+            case 'm':
+            case 'M': mul = 1024*1024; break;
+            case 'g':
+            case 'G': mul = 1024*1024*1024; break;
+            default: return 0;
+        }
+        
+        uint64_t result = 0;
+        if(util_uint_mul(val, mul, &result)) {
+            return 0;
+        }
+        *value = result;
+        return 1;
+    }
+    return 0;
+}
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result) {
+    if(a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    uint64_t r = a * b;
+    if(r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
+
+cxstring util_url_base_s(cxstring url) {
+    size_t i = 0;
+    if(url.length > 0) {
+        int slmax;
+        if(cx_strprefix(url, cx_str("http://"))) {
+            slmax = 3;
+        } else if(cx_strprefix(url, cx_str("https://"))) {
+            slmax = 3;
+        } else {
+            slmax = 1;
+        }
+        int slashcount = 0;
+        for(i=0;i<url.length;i++) {
+            if(url.ptr[i] == '/') {
+                slashcount++;
+                if(slashcount == slmax) {
+                    i++;
+                    break;
+                }
+            }
+        }
+    }
+    return cx_strsubsl(url, 0, i);
+}
+
+char* util_url_base(const char *url) {
+    return cx_strdup(util_url_base_s(cx_str(url))).ptr;
+}
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#endif
+
+const char* util_url_path(const char *url) {
+    return util_url_path_s(cx_str(url)).ptr;
+}
+
+cxstring util_url_path_s(cxstring url) {
+    cxstring path = { "", 0 };
+    int slashcount = 0;
+    int slmax;
+    if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) {
+        slmax = 3;
+    } else if(url.length > 8 && !strncasecmp(url.ptr, "https://", 8)) {
+        slmax = 3;
+    } else {
+        slmax = 1;
+    }
+    char c;
+    for(int i=0;i<url.length;i++) {
+        c = url.ptr[i];
+        if(c == '/') {
+            slashcount++;
+            if(slashcount == slmax) {
+                path = cx_strsubs(url, i);
+                break;
+            }
+        }
+    }
+    return path;
+}
+
+char* util_url_decode(DavSession *sn, const char *url) {
+    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+    char *ret = strdup(unesc);
+    curl_free(unesc);
+    return ret;
+}
+
+static size_t util_header_callback(char *buffer, size_t size,
+        size_t nitems, void *data) {
+    
+    cxstring sbuffer = cx_strn(buffer, size*nitems);
+    
+    CxMap *map = (CxMap*) data;
+    
+    // if we get a status line, clear the map and exit
+    if(cx_strprefix(sbuffer, cx_str("HTTP/"))) {
+        // TODO: use new map destructor   ucx_map_free_content(map, free);
+        cxMapClear(map);
+        return size*nitems;
+    }
+    
+    // if we get the terminating CRLF, just exit
+    if(!cx_strcmp(sbuffer, cx_str("\r\n"))) {
+        return 2;
+    }
+    
+    cxstring key = sbuffer;
+    cxstring value = cx_strchr(sbuffer, ':');
+    
+    if(value.length == 0) {
+        return 0; // invalid header line
+    }
+    
+    key.length = value.ptr - key.ptr;
+    value.ptr++; value.length--;
+    
+    cxmutstr key_cp = cx_strdup(cx_strtrim(key));
+    cx_strlower(key_cp);
+    cxmutstr value_cp = cx_strdup(cx_strtrim(value));
+        
+    cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr);
+    
+    free(key_cp.ptr);
+    
+    return sbuffer.length;
+}
+
+int util_path_isrelated(const char *path1, const char *path2) {
+    cxstring p1 = cx_str(path1);
+    cxstring p2 = cx_str(path2);
+    
+    if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) {
+        p1.length--;
+    }
+    if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) {
+        p2.length--;
+    }
+    
+    if(p2.length < p1.length) {
+        return 0;
+    }
+    
+    if(!cx_strcmp(p1, p2)) {
+        return 1;
+    }
+    
+    if(cx_strprefix(p2, p1)) {
+        if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+#ifdef _WIN32
+int util_path_isabsolut(const char *path) {
+    if(strlen(path) < 3) {
+        return 0;
+    }
+    
+    // check if first char is A-Z or a-z
+    char c = path[0];
+    if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) {
+        return 0;
+    }
+    
+    if(path[1] == ':' && path[2] == '\\') {
+        return 1;
+    }
+    return 0;
+}
+#else
+int util_path_isabsolut(const char *path) {
+    return path[0] == '/';
+}
+#endif
+
+char* util_path_normalize(const char *path) {
+    size_t len = strlen(path);
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, len+1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    if(path[0] == '/') {
+        cxBufferPut(&buf, '/');
+    }
+    
+    int add_separator = 0;
+    int seg_start = 0;
+    for(int i=0;i<=len;i++) {
+        char c = path[i];
+        if(IS_PATH_SEPARATOR(c) || c == '\0') {
+            const char *seg_ptr = path+seg_start;
+            int seg_len = i - seg_start;
+            if(IS_PATH_SEPARATOR(seg_ptr[0])) {
+                seg_ptr++;
+                seg_len--;
+            }
+            
+            if(seg_len > 0) {
+                cxstring seg = cx_strn(seg_ptr, seg_len);
+                if(!cx_strcmp(seg, CX_STR(".."))) {
+                    for(int j=buf.pos;j>=0;j--) {
+                        char t = j < buf.pos ? buf.space[j] : 0;
+                        if(IS_PATH_SEPARATOR(t) || j == 0) {
+                            buf.pos = j;
+                            buf.size = j;
+                            buf.space[j] = 0;
+                            add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0;
+                            break;
+                        }
+                    }
+                } else if(!cx_strcmp(seg, CX_STR("."))) {
+                    // ignore
+                } else {
+                    if(add_separator) {
+                        cxBufferPut(&buf, PATH_SEPARATOR);
+                    }
+                    cxBufferWrite(seg_ptr, 1, seg_len, &buf);
+                    add_separator = 1;
+                }
+            }
+            
+            seg_start = i;
+        }
+    }
+    
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
+}
+
+static char* create_relative_path(const char *abspath, const char *base) {
+    size_t path_len = strlen(abspath);
+    size_t base_len = strlen(base);
+    
+    if(IS_PATH_SEPARATOR(abspath[path_len-1])) {
+        path_len--;
+    }
+    if(IS_PATH_SEPARATOR(base[base_len-1])) {
+        base_len--;
+    }
+    // get base parent
+    for(int i=base_len-1;i>=0;i--) {
+        if(IS_PATH_SEPARATOR(base[i])) {
+            base_len = i+1;
+            break;
+        }
+    }
+    
+    size_t max = path_len > base_len ? base_len : path_len;
+    
+    // get prefix of abspath and base
+    // this dir is the root of the link
+    size_t last_dir = 0;
+    for(size_t i=0;i<max;i++) {
+        char c = abspath[i];
+        if(c != base[i]) {
+            break;
+        } else if(IS_PATH_SEPARATOR(c)) {
+            last_dir = i;
+        }
+    }
+    
+    char *ret = NULL;
+    CxBuffer out;
+    if(last_dir+1 < base_len) {
+        // base is deeper than the link root, we have to go backwards
+        size_t dircount = 0;
+        for(size_t i=last_dir+1;i<base_len;i++) {
+            if(IS_PATH_SEPARATOR(base[i])) {
+                dircount++;
+            }
+        }
+        
+        cxBufferInit(&out, NULL, dircount*3+path_len-last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        for(size_t i=0;i<dircount;i++) {
+            cxBufferPutString(&out, "../");
+        }
+    } else {
+        cxBufferInit(&out, NULL, path_len - last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    }
+    
+    cxBufferPutString(&out, abspath + last_dir + 1);
+    cxBufferPut(&out, 0);
+    
+    return out.space;
+}
+
+#ifdef _WIN32
+char* util_create_relative_path(const char *abspath, const char *base) {
+    char *abspath_converted = strdup(abspath);
+    char *base_converted = strdup(base);
+    size_t abs_len = strlen(abspath_converted);
+    size_t base_len = strlen(base_converted);
+    
+    for(int i=0;i<abs_len;i++) {
+        if(abspath_converted[i] == '\\') {
+            abspath_converted[i] = '/';
+        }
+    }
+    for(int i=0;i<base_len;i++) {
+        if(base_converted[i] == '\\') {
+            base_converted[i] = '/';
+        }
+    }
+    
+    char *ret = create_relative_path(abspath_converted, base_converted);
+    free(abspath_converted);
+    free(base_converted);
+    return ret;
+}
+#else
+char* util_create_relative_path(const char *abspath, const char *base) {
+    return create_relative_path(abspath, base);
+}
+#endif
+
+
+void util_capture_header(CURL *handle, CxMap* map) {
+    if(map) {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, util_header_callback);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, map);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, NULL);
+    }
+}
+
+const char* util_resource_name(const char *url) {
+    cxstring urlstr = cx_str(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    cxstring resname = cx_strrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+const char* util_resource_name_c(const char *url, char pathseparator) {
+    cxstring urlstr = cx_str(url);
+    if(urlstr.ptr[urlstr.length-1] == pathseparator) {
+        urlstr.length--;
+    }
+    cxstring resname = cx_strrchr(urlstr, pathseparator);
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+const char* util_path_file_name(const char *url) {
+#ifdef _WIN32
+    return util_resource_name_c(url, '\\');
+#else
+    return util_resource_name_c(url, '/');
+#endif
+}
+
+
+int util_mkdir(char *path, mode_t mode) {
+#ifdef _WIN32
+    return mkdir(path);
+#else
+    return mkdir(path, mode);
+#endif
+}
+
+char* util_concat_path(const char *url_base, const char *p) {
+    cxstring base = cx_str(url_base);
+    cxstring path;
+    if(p) {
+        path = cx_str((char*)p);
+    } else {
+        path = CX_STR("");
+    }
+    
+    return util_concat_path_s(base, path).ptr;
+}
+
+cxmutstr util_concat_path_s(cxstring base, cxstring path) {
+    if(!path.ptr) {
+        path = CX_STR("");
+    }
+    
+    int add_separator = 0;
+    if(base.length != 0 && base.ptr[base.length-1] == '/') {
+        if(path.ptr[0] == '/') {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != '/') {
+            add_separator = 1;
+        }
+    }
+    
+    cxmutstr url;
+    if(add_separator) {
+        url = cx_strcat(3, base, CX_STR("/"), path);
+    } else {
+        url = cx_strcat(2, base, path);
+    }
+    
+    return url;
+}
+
+cxmutstr util_concat_path_ext(cxstring base, cxstring path, char separator) {
+    if(!path.ptr) {
+        path = CX_STR("");
+    }
+
+    int add_separator = 0;
+    if(base.length != 0 && base.ptr[base.length-1] == separator) {
+        if(path.ptr[0] == separator) {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != separator) {
+            add_separator = 1;
+        }
+    }
+
+    cxmutstr url;
+    if(add_separator) {
+        url = cx_strcat(3, base, cx_strn(&separator, 1), path);
+    } else {
+        url = cx_strcat(2, base, path);
+    }
+
+    return url;
+}
+
+cxmutstr util_concat_sys_path(cxstring base, cxstring path) {
+#ifdef _WIN32
+    return util_concat_path_ext(base, path, '\\');
+#else
+    return util_concat_path_ext(base, path, '/');
+#endif
+}
+
+char* util_get_url(DavSession *sn, const char *href) {
+    cxstring base = cx_str(sn->base_url);
+    cxstring href_str = cx_str(href);
+    
+    const char *base_path = util_url_path(sn->base_url);
+    base.length -= strlen(base_path);
+    
+    cxmutstr url = cx_strcat(2, base, href_str);
+    return url.ptr;
+}
+
+void util_set_url(DavSession *sn, const char *href) {
+    char *url = util_get_url(sn, href);
+    curl_easy_setopt(sn->handle, CURLOPT_URL, url);
+    free(url);
+}
+
+char* util_path_to_url(DavSession *sn, const char *path) {
+    size_t pathlen = path ? strlen(path) : 0;
+    if(pathlen == 0) {
+        return strdup(sn->base_url);
+    }
+    
+    CxBuffer url;
+    cxBufferInit(&url, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // add base url
+    cxBufferWrite(sn->base_url, 1, strlen(sn->base_url), &url);
+    // remove trailing slash
+    cxBufferSeek(&url, -1, SEEK_CUR);
+    
+    cxstring p = cx_strn(path, pathlen);
+    
+    CxStrtokCtx tkctx = cx_strtok(p, CX_STR("/"), INT_MAX);
+    cxstring node;
+    while(cx_strtok_next(&tkctx, &node)) {
+        if(node.length > 0) {
+            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+            cxBufferPut(&url, '/');
+            cxBufferWrite(esc, 1, strlen(esc), &url);
+            curl_free(esc);
+        }
+    }
+    
+    if(path[p.length-1] == '/') {
+        cxBufferPut(&url, '/');
+    }
+    cxBufferPut(&url, 0);
+    
+    return url.space;
+}
+
+char* util_parent_path(const char *path) {
+    const char *name = util_resource_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+char* util_sys_parent_path(const char *path) {
+    const char *name = util_path_file_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength) {
+    return util_size_str2(iscollection, contentlength, contentlength, 1);
+}
+
+char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision) {
+    char *str = malloc(16);
+    uint64_t size = contentlength;
+
+    if(iscollection) {
+        str[0] = '\0'; // currently no information for collections
+    } else if(dimension < 0x400) {
+        snprintf(str, 16, "%" PRIu64 " bytes", size);
+    } else if(dimension < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.*f KiB", precision, s);
+        } else {
+            snprintf(str, 16, "%.0f KiB", s);
+        }
+    } else if(dimension < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.*f MiB", precision, s);
+        } else {
+            size /= 0x100000;
+            snprintf(str, 16, "%.0f MiB", s);
+        }
+    } else if(dimension < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.*f GiB", precision, s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f GiB", s);
+        }
+    } else {
+        size /= 1024;
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.*f TiB", precision, s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f TiB", s);
+        }
+    }
+    return str;
+}
+
+char* util_date_str(time_t tm) {
+    struct tm t;
+    struct tm n;
+    time_t now = time(NULL);
+#ifdef _WIN32
+    memcpy(&t, localtime(&tm), sizeof(struct tm));
+    memcpy(&n, localtime(&now), sizeof(struct tm));
+#else
+    localtime_r(&tm, &t);
+    localtime_r(&now, &n);
+#endif /* _WIN32 */
+    char *str = malloc(16);
+    if(t.tm_year == n.tm_year) {
+        strftime(str, 16, "%b %d %H:%M", &t);
+    } else {
+        strftime(str, 16, "%b %d  %Y", &t);
+    }
+    return str;
+}
+
+
+char* util_xml_get_text(const xmlNode *elm) {
+    xmlNode *node = elm->children;
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+
+char* util_base64decode(const char *in) {
+    int len = 0;
+    return util_base64decode_len(in, &len);
+}
+
+#define WHITESPACE 64
+#define EQUALS     65
+#define INVALID    66
+static char b64dectable[] = {
+    66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
+    54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+    10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,
+    29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66
+};
+char* util_base64decode_len(const char* in, int *outlen) {
+    /* code is mostly from wikibooks */
+    
+    if(!in) {
+        *outlen = 0;
+        return NULL;
+    }
+    
+    size_t inlen = strlen(in);
+    size_t bufsize = (inlen*3) / 4;
+    char *outbuf = malloc(bufsize+1);
+    *outlen = -1;
+    
+    unsigned char *out = (unsigned char*)outbuf;
+    
+    const char *end = in + inlen;
+    char iter = 0;
+    uint32_t buf = 0;
+    size_t len = 0;
+    
+    while (in < end) {
+        unsigned char c = b64dectable[*in++];
+        
+        switch (c) {
+            case WHITESPACE: continue; /* skip whitespace */
+            case INVALID: {
+                  /* invalid input */
+                outbuf[0] = 0;
+                return outbuf;
+            }
+            case EQUALS: {
+                /* pad character, end of data */
+                in = end;
+                continue;
+            }
+            default: {
+                buf = buf << 6 | c;
+                iter++; // increment the number of iteration
+                /* If the buffer is full, split it into bytes */
+                if (iter == 4) {
+                    if ((len += 3) > bufsize) {
+                        /* buffer overflow */
+                        outbuf[0] = 0;
+                        return outbuf;
+                    }
+                    *(out++) = (buf >> 16) & 255;
+                    *(out++) = (buf >> 8) & 255;
+                    *(out++) = buf & 255;
+                    buf = 0; iter = 0;
+
+                }
+            }
+        }
+    }
+   
+    if (iter == 3) {
+        if ((len += 2) > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 10) & 255;
+        *(out++) = (buf >> 2) & 255;
+    }
+    else if (iter == 2) {
+        if (++len > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 4) & 255;
+    }
+
+    *outlen = len; /* modify to reflect the actual output size */
+    outbuf[len] = 0;
+    return outbuf;
+}
+
+
+static char* b64enctable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char* util_base64encode(const char *in, size_t len) {
+    // calculate length of base64 output and create buffer
+    size_t outlen = 4 * ((len + 2) / 3);
+    int pad = len % 3;
+    
+    char *out = malloc(outlen + 1);
+    out[outlen] = 0;
+    size_t pos = 0;
+    
+    // encode blocks of 3 bytes
+    size_t i;
+    size_t blockend = len - pad;
+    for(i=0;i<blockend;i++) {
+        unsigned char b1 = in[i++];
+        unsigned char b2 = in[i++];
+        unsigned char b3 = in[i];
+        uint32_t inb = b1 << 16 | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+    }
+    
+    // encode last bytes
+    if(pad > 0) {
+        char p[3] = {0, 0, 0};
+        for(int j=0;i<len;i++) {
+            p[j++] = in[i];
+        }
+        unsigned char b1 = p[0];
+        unsigned char b2 = p[1];
+        unsigned char b3 = p[2];
+        uint32_t inb = (b1 << 16) | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+        for(int k=outlen-1;k>=outlen-(3-pad);k--) {
+            out[k] = '=';
+        }
+    }
+    
+    return out;
+}
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_encrypt_str_k(sn, str, k);
+}
+
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    char *enc_str = aes_encrypt(str, strlen(str), key);
+    char *ret_str = dav_session_strdup(sn, enc_str);
+    free(enc_str);
+    return ret_str;
+}
+
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_decrypt_str_k(sn, str, k);
+}
+
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    size_t len = 0;
+    char *dec_str = aes_decrypt(str, &len, key);
+    char *ret_str = dav_session_strdup(sn, dec_str);
+    free(dec_str);
+    return ret_str;
+}
+
+char* util_random_str() {
+    unsigned char *str = malloc(25);
+    str[24] = '\0';
+    
+    cxstring t = CX_STR(
+            "01234567890"
+            "abcdefghijklmnopqrstuvwxyz"
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    const unsigned char *table = (const unsigned char*)t.ptr;
+    
+#ifdef DAV_USE_OPENSSL
+    RAND_bytes(str, 24);
+#else
+    dav_rand_bytes(str, 24);
+#endif
+    for(int i=0;i<24;i++) {
+        int c = str[i] % t.length;
+        str[i] = table[c];
+    }
+    
+    return (char*)str;
+}
+
+/*
+ * gets a substring from 0 to the appearance of the token
+ * tokens are separated by space
+ * sets sub to the substring and returns the remaining string
+ */
+// TODO: remove if it isn't used
+/*
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
+    int i;
+    int token_start = -1;
+    int token_end = -1;
+    for(i=0;i<=str.length;i++) {
+        int c;
+        if(i == str.length) {
+            c = ' ';
+        } else {
+            c = str.ptr[i];
+        }
+        if(c < 33) {
+            if(token_start != -1) {
+                token_end = i;
+                size_t len = token_end - token_start;
+                sstr_t tk = sstrsubsl(str, token_start, len);
+                //printf("token: {%.*s}\n", token.length, token.ptr);
+                if(!sstrcmp(tk, token)) {
+                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+                    break;
+                }
+                token_start = -1;
+                token_end = -1;
+            }
+        } else {
+            if(token_start == -1) {
+                token_start = i;
+            }
+        }
+    }
+    
+    if(i < str.length) {
+        return sstrtrim(sstrsubs(str, i));
+    } else {
+        str.ptr = NULL;
+        str.length = 0;
+        return str;
+    }
+}
+*/
+
+cxmutstr util_readline(FILE *stream) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    int c;
+    while((c = fgetc(stream)) != EOF) {
+        if(c == '\n') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    
+    cxmutstr str = cx_strdup(cx_strtrim(cx_strn(buf.space, buf.size)));
+    cxBufferDestroy(&buf);
+    return str;
+}
+
+char* util_password_input(char *prompt) {
+    fprintf(stderr, "%s", prompt);
+    fflush(stderr);
+    
+#ifndef _WIN32
+    // hide terminal input
+    struct termios oflags, nflags;
+    if(isatty(fileno(stdin))) {
+        tcgetattr(fileno(stdin), &oflags);
+        nflags = oflags;
+        nflags.c_lflag &= ~ECHO;
+        nflags.c_lflag |= ECHONL;
+        if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
+            perror("tcsetattr");
+        }
+    }
+    
+#endif
+    
+    // read password input
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    int c = 0;
+    while((c = getpasswordchar()) != EOF) {
+        if(c == '\n' || c == '\r') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    cxBufferPut(&buf, 0);
+    fflush(stdin);
+    
+#ifndef _WIN32
+    // restore terminal settings
+    if (isatty(fileno(stdin)) && tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
+        perror("tcsetattr");
+    }
+#endif
+    
+    return buf.space;
+}
+
+int util_exec_command(char *command, CxBuffer *outbuf) {
+#ifdef _WIN32
+    fprintf(stderr, "util_exec_command unsupported\n");
+    return 1;
+#else
+    
+    int pout[2];
+    if(pipe(pout)) {
+        perror("pipe");
+        return 1;
+    }
+    
+    int ret = 0;
+    
+    // close stdin and stderr, use pipe for stdout
+    posix_spawn_file_actions_t actions;
+    posix_spawn_file_actions_init(&actions);
+    posix_spawn_file_actions_addclose(&actions, 0);
+    posix_spawn_file_actions_adddup2(&actions, pout[1], 1);
+    posix_spawn_file_actions_addclose(&actions, 2);
+    
+    char *args[4];
+    args[0] = "sh";
+    args[1] = "-c";
+    args[2] = command;
+    args[3] = NULL;
+    
+    pid_t pid; // child pid
+    ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, args, NULL);
+    
+    close(pout[1]);
+    
+    if(!ret) {
+        ssize_t r;
+        char buf[1024];
+        while((r = read(pout[0], buf, 1024)) > 0) {
+            cxBufferWrite(buf, 1, r, outbuf);
+        }
+    }
+    
+    // wait for child process
+    ret = 1;
+    waitpid(pid, &ret, 0);
+    
+    posix_spawn_file_actions_destroy(&actions);
+    close(pout[0]);
+    
+    return ret;
+#endif
+}
+
+char* util_hexstr(const unsigned char *data, size_t len) {
+    size_t buflen = 2*len + 4;
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, buflen + 1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    for(int i=0;i<len;i++) {
+        cx_bprintf(&buf, "%02x", data[i]);
+    }
+    cxBufferPut(&buf, 0);
+    return buf.space;
+}
+
+void util_remove_trailing_pathseparator(char *path) {
+    size_t len = strlen(path);
+    if(len < 2) {
+        return;
+    }
+    
+    if(path[len-1] == '/') {
+        path[len-1] = '\0';
+    }
+}
+
+char* util_file_hash(const char *path) {
+    FILE *in = fopen(path, "r");
+    if(!in) {
+        return NULL;
+    }
+    
+    DAV_SHA_CTX *sha = dav_hash_init();
+    char *buf = malloc(16384);
+    
+    size_t r;
+    while((r = fread(buf, 1, 16384, in)) > 0) {
+        dav_hash_update(sha, buf, r);
+    }
+    
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    dav_hash_final(sha, hash);
+    free(buf);
+    fclose(in);
+    
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);    
+}
+
diff --git a/libidav/utils.h b/libidav/utils.h
new file mode 100644 (file)
index 0000000..8952c76
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 UTILS_H
+#define        UTILS_H
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <io.h>
+#endif /* _WIN32 */
+
+#include <sys/types.h>
+#include <libxml/tree.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <curl/curl.h>
+#include "webdav.h"
+
+#ifdef _WIN32
+#ifndef mode_t
+#define mode_t int
+#endif
+#endif
+
+#ifndef S_IRWXG
+/* if one is not defined, the others are probably also not defined */
+#define S_IRWXU 0700
+#define S_IRWXG  070
+#define S_IRGRP  040
+#define S_IWGRP  020
+#define S_IXGRP  010
+#define S_IRWXO   07
+#define S_IROTH   04
+#define S_IWOTH   02
+#define S_IXOTH   01
+#endif /* S_IRWXG */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+time_t util_parse_creationdate(char *str);
+time_t util_parse_lastmodified(char *str);
+
+int util_mkdir(char *path, mode_t mode);
+
+char* util_url_base(const char *url);
+cxstring util_url_base_s(cxstring url);
+const char* util_url_path(const char *url);
+cxstring util_url_path_s(cxstring url);
+char* util_url_decode(DavSession *sn, const char *url);
+const char* util_resource_name(const char *url);
+const char* util_resource_name_c(const char *url, char pathseparator);
+const char* util_path_file_name(const char *url);
+
+char* util_concat_path(const char *url_base, const char *path);
+cxmutstr util_concat_path_s(cxstring url_base, cxstring path);
+cxmutstr util_concat_path_ext(cxstring url_base, cxstring path, char separator);
+cxmutstr util_concat_sys_path(cxstring base, cxstring path);
+char* util_get_url(DavSession *sn, const char *href);
+void util_set_url(DavSession *sn, const char *href);
+
+/*
+ * returns true if path1 and path2 are equal or if path2 is a child of path1
+ */
+int util_path_isrelated(const char *path1, const char *path2);
+
+int util_path_isabsolut(const char *path);
+
+char* util_path_normalize(const char *path);
+char* util_create_relative_path(const char *abspath, const char *base);
+
+void util_capture_header(CURL *handle, CxMap* map);
+
+char* util_path_to_url(DavSession *sn, const char *path);
+char* util_parent_path(const char *path);
+char* util_sys_parent_path(const char *path);
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength);
+char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision);
+char* util_date_str(time_t tm);
+
+int util_getboolean(const char *v);
+int util_strtouint(const char *str, uint64_t *value);
+int util_strtoint(const char *str, int64_t *value);
+int util_szstrtouint(const char *str, uint64_t *value);
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result);
+
+char* util_xml_get_text(const xmlNode *elm);
+
+char* util_base64decode(const char *in);
+char* util_base64decode_len(const char *in, int *outlen);
+char* util_base64encode(const char *in, size_t len);
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+
+char* util_random_str();
+
+//sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub);
+
+cxmutstr util_readline(FILE *stream);
+char* util_password_input(char *prompt);
+
+int util_exec_command(char *command, CxBuffer *outbuf);
+
+char* util_hexstr(const unsigned char *data, size_t len);
+
+void util_remove_trailing_pathseparator(char *path);
+
+char* util_file_hash(const char *path);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UTILS_H */
+
diff --git a/libidav/versioning.c b/libidav/versioning.c
new file mode 100644 (file)
index 0000000..e0a4514
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "versioning.h"
+
+#include "methods.h"
+#include "utils.h"
+#include "session.h"
+
+static int basic_deltav_op(DavResource *res, char *method) {
+    DavSession *sn = res->session;
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    DavLock *lock = dav_get_lock(res->session, res->path);
+    char *locktoken = lock ? lock->token : NULL;
+    
+    CURLcode ret = do_simple_request(sn, method, locktoken);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(!(ret == CURLE_OK && (status >= 200 && status < 300))) {
+        dav_session_set_error(sn, ret, status);
+        return 1;
+    }
+    return 0;
+}
+
+int dav_versioncontrol(DavResource *res) {
+    return basic_deltav_op(res, "VERSION-CONTROL");
+}
+
+int dav_checkout(DavResource *res) {
+    return basic_deltav_op(res, "CHECKOUT");
+}
+
+int dav_checkin(DavResource *res) {
+    return basic_deltav_op(res, "CHECKIN");
+}
+
+int dav_uncheckout(DavResource *res) {
+    return basic_deltav_op(res, "UNCHECKOUT");
+}
+
+DavResource* dav_versiontree(DavResource *res, char *properties) {
+    DavSession *sn = res->session;
+    util_set_url(sn, dav_resource_get_href(res));
+    
+    CxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, cx_str(properties));
+        
+        // check if the list already contains a D:version-name property
+        int add_vname = 1;
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty *, p, i) {
+            if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) {
+                add_vname = 0;
+                break;
+            }
+        }
+        if(add_vname) {
+            // we need at least the D:version-name prop
+            DavProperty p;
+            p.ns = dav_get_namespace(sn->context, "D");
+            p.name = strdup("version-name");
+            p.value = NULL;
+            cxListInsert(proplist, 0, &p);
+        }
+    }
+    
+    
+    
+    // create a version-tree request, which is almost the same as propfind
+    CxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1);
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // do the request
+    CURLcode ret = do_report_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+    int error = 0;
+    DavResource *versions = NULL;
+    if(ret == CURLE_OK && status == 207) {
+        sn->error = DAV_OK;
+        
+        // parse multistatus response
+        PropfindParser *parser = create_propfind_parser(rpbuf, NULL);
+        if(parser) {
+            DavResource *list_end = NULL;
+            
+            ResponseTag response;
+            int r;
+            
+            // we don't want name decryption for version resources
+            int snflags = sn->flags;
+            sn->flags = 0;
+            while((r = get_propfind_response(parser, &response)) != 0) {
+                if(r == -1) {
+                    res->session->error = DAV_ERROR;
+                    error = 1;
+                    break;
+                }
+                DavResource *v = response2resource(sn, &response, NULL);
+                // add version to list
+                if(!versions) {
+                    versions = v;
+                } else {
+                    list_end->next = v;
+                }
+                list_end = v;
+                
+                cleanup_response(&response);
+            }
+            sn->flags = snflags;
+            
+            destroy_propfind_parser(parser);
+        } else {
+            sn->error = DAV_ERROR;
+            error = 1;
+        }
+    } else {
+        dav_session_set_error(sn, ret, status);
+        error = 1;
+    }
+    
+    // cleanup
+    if(proplist) {
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty*, p, i) {
+            free(p->name);
+        }
+        cxListDestroy(proplist);
+    }
+    
+    if(error && versions) {
+        DavResource *cur = versions;
+        while(cur) {
+            DavResource *next = cur->next;
+            dav_resource_free(cur);
+            cur = next;
+        }
+        versions = NULL;
+    }
+    
+    return versions;
+}
diff --git a/libidav/versioning.h b/libidav/versioning.h
new file mode 100644 (file)
index 0000000..1cf2649
--- /dev/null
@@ -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.
+ */
+
+#ifndef VERSIONING_H
+#define VERSIONING_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VERSIONING_H */
+
diff --git a/libidav/webdav.c b/libidav/webdav.c
new file mode 100644 (file)
index 0000000..bbc3ef2
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libxml/tree.h>
+
+#include "utils.h"
+#include "webdav.h"
+#include "session.h"
+#include "methods.h"
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+#include <cx/compare.h>
+#include "davqlparser.h"
+#include "davqlexec.h"
+
+
+DavContext* dav_context_new(void) {
+    // initialize
+    DavContext *context = calloc(1, sizeof(DavContext));
+    if(!context) {
+        return NULL;
+    }
+    context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_ptr, CX_STORE_POINTERS);
+    cxDefineDestructor(context->sessions, dav_session_destructor);
+    context->http_proxy = calloc(1, sizeof(DavProxy));
+    if(!context->http_proxy) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->https_proxy = calloc(1, sizeof(DavProxy));
+    if(!context->https_proxy) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->namespaces) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->namespaceinfo) {
+        dav_context_destroy(context);
+    }
+    context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    if(!context->keys) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    // add DAV: namespace
+    if(dav_add_namespace(context, "D", "DAV:")) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    
+    // add idav namespace
+    if(dav_add_namespace(context, "idav", DAV_NS)) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    
+    // add idavprops namespace
+    if(dav_add_namespace(context, "idavprops", DAV_PROPS_NS)) {
+        dav_context_destroy(context);
+        return NULL;
+    }   
+
+    return context;
+}
+
+void dav_context_destroy(DavContext *ctx) {
+    // destroy all sessions assoziated with this context
+    // ctx->sessions destructor must be dav_session_destructor
+    cxListDestroy(ctx->sessions);
+    
+    if(ctx->http_proxy) {
+        free(ctx->http_proxy);
+    }
+    if(ctx->https_proxy) {
+        free(ctx->https_proxy);
+    }
+    
+    if(ctx->namespaces) {
+        CxIterator i = cxMapIteratorValues(ctx->namespaces);
+        cx_foreach(DavNamespace*, ns, i) {
+            if(!ns) continue;
+            if(ns->prefix) {
+                free(ns->prefix);
+            }
+            if(ns->name) {
+                free(ns->name);
+            }
+            free(ns);
+        }
+        cxMapDestroy(ctx->namespaces);
+    }
+    if(ctx->namespaceinfo) {
+        // TODO: implement
+    }
+    if(ctx->keys) {
+        CxIterator i = cxMapIteratorValues(ctx->keys);
+        cx_foreach(DavKey*, key, i) {
+            if(!key) continue;
+            if(key->name) {
+                free(key->name);
+            }
+            if(key->data) {
+                free(key->data);
+            }
+            free(key);
+        }
+        cxMapDestroy(ctx->keys);
+    }    
+    
+    free(ctx);
+}
+
+#ifndef _WIN32
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+    if (enable) {
+        pthread_mutex_init(&ctx->mutex, NULL);
+    } else {
+        pthread_mutex_destroy(&ctx->mutex);
+    }
+    ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        pthread_mutex_lock(&ctx->mutex);
+    }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        pthread_mutex_unlock(&ctx->mutex);
+    }
+}
+
+#else
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+    if (enable) {
+        ctx->mutex = CreateMutex(NULL, FALSE, NULL);
+    } else {
+        CloseHandle(ctx->mutex);
+    }
+    ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        WaitForSingleObject(ctx->mutex, INFINITE);
+    }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        ReleaseMutex(ctx->mutex);
+    }
+}
+
+#endif
+
+void dav_context_add_key(DavContext *context, DavKey *key) {
+    dav_context_lock(context);
+    cxMapPut(context->keys, cx_hash_key_str(key->name), key);
+    dav_context_unlock(context);
+}
+
+DavKey* dav_context_get_key(DavContext *context, const char *name) {
+    DavKey *key = NULL;
+    dav_context_lock(context);
+    if(name) {
+        key = cxMapGet(context->keys, cx_hash_key_str(name));
+    }
+    dav_context_unlock(context);
+    return key;
+}
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *name) {
+    DavNamespace *namespace = malloc(sizeof(DavNamespace));
+    if(!namespace) {
+        return 1;
+    }
+    
+    char *p = strdup(prefix);
+    if (!p) {
+        free(namespace);
+        return 1;
+    }
+    char *n = strdup(name);
+    if (!n) {
+        free(namespace);
+        free(p);
+        return 1;
+    }
+
+    dav_context_lock(context);
+
+    int err = 0;
+    if(p && n) {
+        namespace->prefix = p;
+        namespace->name = n;
+        err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace);
+    }
+    
+    if(err) {
+        free(namespace);
+        if(p) free(p);
+        if(n) free(n);
+    }
+
+    dav_context_unlock(context);
+    
+    return err;
+}
+
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
+    dav_context_lock(context);
+    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix));
+    dav_context_unlock(context);
+    return ns;
+}
+
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) {
+    dav_context_lock(context);
+    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
+    dav_context_unlock(context);
+    return ns;
+}
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
+    dav_context_lock(context);
+
+    CxHashKey hkey = cx_hash_key_str(ns);
+    DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey);
+    if(!info) {
+        info = calloc(1, sizeof(DavNSInfo));
+        info->encrypt = encrypt;
+        cxMapPut(context->namespaceinfo, hkey, info);
+    } else {
+        info->encrypt = encrypt;
+    }
+
+    dav_context_unlock(context);
+    return 0;
+}
+
+int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
+    int ret = 0;
+    dav_context_lock(context);
+    
+    DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns));
+    if(info) {
+        ret = info->encrypt;
+    }
+    dav_context_unlock(context);
+    return ret;
+}
+
+void dav_get_property_namespace_str(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **ns,
+        char **name)
+{
+    // TODO: rewrite using dav_get_property_ns
+    
+    char *pname = strchr(prefixed_name, ':');
+    char *pns = "DAV:";
+    if(pname) {
+        DavNamespace *davns = dav_get_namespace_s(
+                ctx,
+                cx_strn(prefixed_name, pname-prefixed_name));
+        if(davns) {
+            pns = davns->name;
+            pname++;
+        } else {
+            pns = NULL;
+            pname = NULL;
+        }
+    } else {
+        pname = prefixed_name;
+    }
+    *ns = pns;
+    *name = pname;
+}
+
+DavNamespace* dav_get_property_namespace(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **name)
+{
+    char *pname = strchr(prefixed_name, ':');
+    if(pname) {
+        DavNamespace *ns = dav_get_namespace_s(
+                ctx,
+                cx_strn(prefixed_name, pname-prefixed_name));
+        if(ns) {
+            *name = pname +1;
+            return ns;
+        } else {
+            *name = NULL;
+            return NULL;
+        }
+    } else {
+        *name = prefixed_name;
+        return dav_get_namespace_s(ctx, cx_str("D"));
+    }
+}
+
+int dav_context_add_session(DavContext *context, DavSession *sn) {
+    dav_context_lock(context);
+    int ret = cxListAdd(context->sessions, sn);
+    dav_context_unlock(context);
+    return ret;
+}
+
+int dav_context_remove_session(DavContext *context, DavSession *sn) {
+    int ret = 0;
+    dav_context_lock(context);
+    CxList *sessions = context->sessions;
+    ssize_t i = cxListFind(sessions, sn);
+    if(i >= 0) {
+        cxListRemove(sessions, i);
+    } else {
+        ret = 1;
+    }
+    dav_context_unlock(context);
+    return ret;
+}
+
+
+// TODO: add sstr_t version of dav_get_property_ns
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource) {
+    char *eff_url;
+    curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url);
+    if(eff_url) {
+        const char *href = util_url_path(eff_url);
+        if(strcmp(href, resource->href)) {
+            dav_session_free(sn, resource->href);
+            resource->href = dav_session_strdup(sn, href);
+        }
+    }
+}
+
+DavResource* dav_get(DavSession *sn, char *path, const char *properties) {  
+    CURL *handle = sn->handle;
+    DavResource *resource = dav_resource_new(sn, path);
+    util_set_url(sn, dav_resource_get_href(resource));
+    
+    CxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, cx_str(properties));
+    }
+    CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0);
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    //fwrite(rqbuf->space, 1, rqbuf->size, stdout);
+    //printf("\n");
+    
+    CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        dav_set_effective_href(sn, resource);
+        
+        //printf("response\n%s\n", rpbuf->space);
+        // TODO: use PropfindParser
+        resource = parse_propfind_response(sn, resource, rpbuf);
+        resource->exists = 1;
+        sn->error = DAV_OK;
+    } else  {
+        dav_session_set_error(sn, ret, status);
+        dav_resource_free(resource);
+        resource = NULL;
+    }
+    
+    cxBufferFree(rqbuf);
+    cxBufferFree(rpbuf);
+    
+    if(proplist) {
+        CxIterator i = cxListIterator(proplist);
+        cx_foreach(DavProperty*, p, i) {
+            free(p->name);
+        }
+        cxListDestroy(proplist);
+    }
+    
+    return resource;
+}
+
+
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) {
+    // clean resource properties
+    DavResourceData *data = root->data;
+    cxMapClear(data->properties); // TODO: free existing content
+    
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(root));
+     
+    CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    DavResource *resource = root;
+    CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+    long status = 0;
+    long error = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && status == 207) {
+        //printf("response\n%s\n", rpbuf->space); 
+        dav_set_effective_href(sn, resource);
+        // TODO: use PropfindParser
+        resource = parse_propfind_response(sn, resource, rpbuf);
+        sn->error = DAV_OK;
+        root->exists = 1;
+    } else  {
+        dav_session_set_error(sn, ret, status);
+        error = 1;
+    }
+    cxBufferFree(rpbuf);
+    return error;
+}
+
+CxList* parse_properties_string(DavContext *context, cxstring str) {
+    CxList *proplist = cxLinkedListCreateSimple(sizeof(DavProperty));
+    
+    CxStrtokCtx tok = cx_strtok(str, cx_str(","), INT_MAX);
+    cxstring s;
+    while(cx_strtok_next(&tok, &s)) {
+        cxstring nsname = cx_strchr(s, ':');
+        if(nsname.length > 0) {
+            cxstring nspre = cx_strsubsl(s, 0, nsname.ptr - s.ptr);
+            nsname.ptr++;
+            nsname.length--;
+            
+            DavProperty dp;
+            cxstring pre = cx_strtrim(nspre);
+            dp.ns = dav_get_namespace_s(context, pre);
+            dp.name = cx_strdup(nsname).ptr;
+            dp.value = NULL;
+            if(dp.ns && dp.name) {
+                cxListAdd(proplist, &dp);
+            } else {
+                free(dp.name);
+            }
+        }
+    }
+    
+    return proplist;
+}
+
+DavResource* dav_query(DavSession *sn, char *query, ...) {
+    DavQLStatement *stmt = dav_parse_statement(cx_str(query));
+    if(!stmt) {
+        sn->error = DAV_ERROR;
+        return NULL;
+    }
+    if(stmt->errorcode != 0) {
+        sn->error = DAV_QL_ERROR;
+        dav_free_statement(stmt);
+        return NULL;
+    }
+    
+    va_list ap;
+    va_start(ap, query);
+    DavResult result = dav_statement_execv(sn, stmt, ap);
+    va_end(ap);
+    
+    dav_free_statement(stmt);
+    
+    if(result.status == -1) {
+        if(result.result) {
+            dav_resource_free(result.result);
+            result.result = NULL;
+        }
+    }
+    
+    return result.result;
+}
+
+
+
+
+void dav_verbose_log(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen)
+{
+    fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status);
+    fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body);
+    fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body);
+}
diff --git a/libidav/webdav.h b/libidav/webdav.h
new file mode 100644 (file)
index 0000000..3bd60ac
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ * 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 WEBDAV_H
+#define        WEBDAV_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <cx/map.h>
+#include <cx/mempool.h>
+#include <cx/linked_list.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <curl/curl.h>
+#include <libxml/tree.h>
+
+#ifndef _WIN32
+#include <pthread.h>
+#else
+#include <Windows.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef char DavBool;
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+typedef struct DavContext      DavContext;
+typedef struct DavProxy        DavProxy;
+typedef struct DavSession      DavSession;
+typedef struct DavResource     DavResource;
+typedef struct DavResult       DavResult;
+typedef struct DavNamespace    DavNamespace;
+typedef struct DavProperty     DavProperty;
+typedef struct DavPropName     DavPropName;
+typedef struct DavKey          DavKey;
+typedef struct DavNSInfo       DavNSInfo;
+typedef struct DavXmlNode      DavXmlNode;
+typedef struct DavXmlAttr      DavXmlAttr;
+
+typedef struct DavInputStream  DavInputStream;
+typedef struct DavOutputStream DavOutputStream;
+
+#ifndef _WIN32
+#define DAV_MUTEX pthread_mutex_t 
+#else
+#define DAV_MUTEX HANDLE
+#endif
+
+typedef size_t(*dav_read_func)(void*, size_t, size_t, void*);
+typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*);
+typedef int(*dav_seek_func)(const void *, long, int);
+
+typedef int(*dav_auth_func)(DavSession *, void *);
+typedef void(*dav_progress_func)(DavResource *, int64_t, int64_t, void *);
+
+
+typedef void(*dav_rqlog_func)(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen);
+
+enum DavError {
+    DAV_OK = 0,
+    DAV_ERROR,
+    DAV_NOT_FOUND,
+    DAV_UNAUTHORIZED,
+    DAV_FORBIDDEN,
+    DAV_METHOD_NOT_ALLOWED,
+    DAV_CONFLICT,
+    DAV_LOCKED,
+    DAV_UNSUPPORTED_PROTOCOL,
+    DAV_COULDNT_RESOLVE_PROXY,
+    DAV_COULDNT_RESOLVE_HOST,
+    DAV_COULDNT_CONNECT,
+    DAV_TIMEOUT,
+    DAV_SSL_ERROR,
+    DAV_QL_ERROR,
+    DAV_CONTENT_VERIFICATION_ERROR,
+    DAV_PRECONDITION_FAILED,
+    DAV_REQUEST_ENTITY_TOO_LARGE,
+    DAV_REQUEST_URL_TOO_LONG,
+    DAV_PROXY_AUTH_REQUIRED,
+    DAV_NET_AUTH_REQUIRED
+};
+
+typedef enum DavError DavError;
+
+enum DavXmlNodeType {
+    DAV_XML_NONE = 0,
+    DAV_XML_ELEMENT,
+    DAV_XML_TEXT
+};
+
+typedef enum DavXmlNodeType DavXmlNodeType;
+
+#define DAV_SESSION_ENCRYPT_CONTENT     0x0001
+#define DAV_SESSION_ENCRYPT_NAME        0x0002
+#define DAV_SESSION_ENCRYPT_PROPERTIES  0x0004
+#define DAV_SESSION_DECRYPT_CONTENT     0x0008
+#define DAV_SESSION_DECRYPT_NAME        0x0010
+#define DAV_SESSION_DECRYPT_PROPERTIES  0x0020
+#define DAV_SESSION_STORE_HASH          0x0040
+
+#define DAV_SESSION_CONTENT_ENCRYPTION  0x0009
+#define DAV_SESSION_FULL_ENCRYPTION     0x003f
+
+
+#define DAV_NS       "http://davutils.org/"
+#define DAV_PROPS_NS "http://davutils.org/props/"
+
+struct DavNamespace {
+    char *prefix;
+    char *name;
+};
+
+struct DavResource {
+    DavSession    *session;
+    DavResource   *prev;
+    DavResource   *next;
+    DavResource   *parent;
+    DavResource   *children;
+    char          *name;
+    char          *path;
+    char          *href;
+    uint64_t      contentlength;
+    char          *contenttype;
+    time_t        creationdate;
+    time_t        lastmodified;
+    void          *data;
+    int           iscollection;
+    int           exists;
+};
+
+struct DavSession {
+    DavContext    *context;
+    CURL          *handle;
+    char          *base_url;
+    CxMempool     *mp;
+    CxMap         *pathcache;
+    DavKey        *key;
+    void          *locks;
+    uint32_t      flags;
+    DavError      error;
+    char          *errorstr;
+    
+    int(*auth_prompt)(DavSession *sn, void *userdata);
+    void *authprompt_userdata;
+    
+    dav_rqlog_func logfunc;
+    
+    void(*get_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+    void(*put_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+    void *progress_userdata;
+};
+
+struct DavContext {
+    CxMap     *namespaces;
+    CxMap     *namespaceinfo;
+    CxMap     *keys;
+    CxList    *sessions;
+    DavProxy  *http_proxy;
+    DavProxy  *https_proxy;
+    DAV_MUTEX mutex;
+    DavBool   mtsafe;
+};
+
+struct DavProxy {
+    char *url;
+    char *username;
+    char *password;
+    char *no_proxy;
+};
+
+struct DavProperty {
+    DavNamespace *ns;
+    char         *name;
+    DavXmlNode   *value;
+};
+
+struct DavPropName {
+    char *ns;
+    char *name;
+};
+
+struct DavResult {
+    DavResource *result;
+    int         status;
+};
+
+#define DAV_KEY_AES128     0
+#define DAV_KEY_AES256     1
+
+struct DavKey {
+    char    *name;
+    int     type;
+    void    *data;
+    size_t  length;
+};
+
+struct DavNSInfo {
+    char    *prefix;
+    DavBool encrypt;
+};
+
+struct DavXmlNode {
+    DavXmlNodeType type;
+    
+    char           *namespace;
+    char           *name;
+    
+    DavXmlNode     *prev;
+    DavXmlNode     *next;
+    DavXmlNode     *children;
+    DavXmlNode     *parent;
+    
+    DavXmlAttr     *attributes;
+    
+    char           *content;
+    size_t         contentlength;
+};
+
+struct DavXmlAttr {
+    char *name;
+    char *value;
+    DavXmlAttr *next;
+};
+
+DavContext* dav_context_new(void);
+void dav_context_destroy(DavContext *ctx);
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable);
+
+void dav_context_lock(DavContext *ctx);
+void dav_context_unlock(DavContext *ctx);
+
+void dav_context_add_key(DavContext *context, DavKey *key);
+DavKey* dav_context_get_key(DavContext *context, const char *name);
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *ns);
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix);
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix);
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt);
+int dav_namespace_is_encrypted(DavContext *context, const char *ns);
+
+int dav_context_add_session(DavContext *context, DavSession *sn);
+int dav_context_remove_session(DavContext *context, DavSession *sn);
+
+DavSession* dav_session_new(DavContext *context, char *base_url);
+DavSession* dav_session_new_auth(
+        DavContext *context,
+        char *base_url,
+        char *user,
+        char *password);
+DavSession* dav_session_clone(DavSession *sn);
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password);
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password);
+void dav_session_set_baseurl(DavSession *sn, char *base_url);
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags);
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata);
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata);
+
+void dav_session_destroy(DavSession *sn);
+
+void dav_session_destructor(DavSession *sn);
+
+void* dav_session_malloc(DavSession *sn, size_t size);
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size);
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size);
+void  dav_session_free(DavSession *sn, void *ptr);
+char* dav_session_strdup(DavSession *sn, const char *str);
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource);
+DavResource* dav_get(DavSession *sn, char *path, const char *properties);
+
+CxList* parse_properties_string(DavContext *context, cxstring str);
+
+DavResource* dav_query(DavSession *sn, char *query, ...);
+
+cxmutstr dav_property_key(const char *ns, const char *name);
+void dav_get_property_namespace_str(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **ns,
+        char **name);
+DavNamespace* dav_get_property_namespace(
+        DavContext *ctx,
+        char *prefixed_name,
+        char **name);
+
+/* ------------------------ resource functions ------------------------ */
+
+DavResource* dav_resource_new(DavSession *sn, const char *path);
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name);
+DavResource* dav_resource_new_href(DavSession *sn, const char *href);
+
+void dav_resource_free(DavResource *res);
+void dav_resource_free_all(DavResource *res);
+
+char* dav_resource_get_href(DavResource *resource);
+
+DavResource* dav_create_child(DavResource *parent, char *name);
+int dav_delete(DavResource *res);
+int dav_create(DavResource *res);
+int dav_exists(DavResource *res);
+
+int dav_copy(DavResource *res, char *newpath);
+int dav_move(DavResource *res, char *newpath);
+int dav_copy_o(DavResource *res, char *newpath, DavBool override);
+int dav_move_o(DavResource *res, char *newpath, DavBool override);
+int dav_copyto(DavResource *res, char *url, DavBool override);
+int dav_moveto(DavResource *res, char *url, DavBool override);
+
+int dav_lock(DavResource *res);
+int dav_lock_t(DavResource *res, time_t timeout);
+int dav_unlock(DavResource *res);
+
+DavXmlNode* dav_get_property(DavResource *res, char *name);
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name);
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name);
+char* dav_get_string_property(DavResource *res, char *name);
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name);
+int dav_set_string_property(DavResource *res, char *name, char *value);
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value);
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_remove_property(DavResource *res, char *name);
+void dav_remove_property_ns(DavResource *res, char *ns, char *name);
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name);
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count);
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+void dav_set_content_data(DavResource *res, char *content, size_t length);
+void dav_set_content_length(DavResource *res, size_t length);
+
+int dav_load(DavResource *res);
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop);
+int dav_store(DavResource *res);
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_func);
+
+DavInputStream* dav_inputstream_open(DavResource *res);
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in);
+void dav_inputstream_close(DavInputStream *in);
+
+DavOutputStream* dav_outputstream_open(DavResource *res);
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out);
+int dav_outputstream_close(DavOutputStream *out);
+
+void dav_verbose_log(
+        DavSession *sn,
+        const char *method,
+        const char *url,
+        const char *request_body,
+        size_t request_bodylen,
+        int status,
+        const char *response_body,
+        size_t response_bodylen);
+
+// private
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf);
+
+
+/* --------------------------- DeltaV ---------------------------- */
+
+int dav_versioncontrol(DavResource *res);
+int dav_checkout(DavResource *res);
+int dav_checkin(DavResource *res);
+int dav_uncheckout(DavResource *res);
+DavResource* dav_versiontree(DavResource *res, char *properties);
+
+/* ------------------------ xml functions ------------------------ */
+char* dav_xml_getstring(DavXmlNode *node);
+DavBool dav_xml_isstring(DavXmlNode *node);
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node);
+DavXmlNode* dav_text_node(DavSession *sn, const char *text);
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text);
+
+DavXmlNode* dav_copy_node(DavXmlNode *node);
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node);
+void dav_free_xml_node(DavXmlNode *node);
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name);
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text);
+DavXmlNode* dav_xml_createtextnode(const char *text);
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child);
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value);
+char* dav_xml_get_attr(DavXmlNode *node, const char *name);
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WEBDAV_H */
+
diff --git a/libidav/xml.c b/libidav/xml.c
new file mode 100644 (file)
index 0000000..32221ad
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/utils.h>
+#include <cx/printf.h>
+
+#include "xml.h"
+
+static DavXmlNodeType convert_type(xmlElementType type) {
+    DavXmlNodeType ct;
+    switch(type) {
+        default: ct = DAV_XML_NONE; break;
+        case XML_ELEMENT_NODE: ct = DAV_XML_ELEMENT; break;
+        case XML_TEXT_NODE: ct = DAV_XML_TEXT;
+    }
+    return ct;
+}
+
+typedef struct {
+    xmlNode    *node;
+    DavXmlNode *parent;
+} ConvXmlElm;
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) {
+    if(!node) {
+        return NULL;
+    }
+    DavXmlNodeType newnt = convert_type(node->type);
+    if(newnt == DAV_XML_NONE) {
+        return NULL;
+    }
+    
+    const CxAllocator *a = sn->mp->allocator;
+    
+    ConvXmlElm ce;
+    ce.node = node;
+    ce.parent = NULL;
+    CxList *stack = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(ConvXmlElm));
+    if(!stack) {
+        return NULL;
+    }
+    cxListInsert(stack, 0, &ce);
+    
+    DavXmlNode *ret = NULL;
+    
+    while(cxListSize(stack) > 0) {
+        ConvXmlElm *c = cxListAt(stack, 0);
+        xmlNode *n = c->node;
+        DavXmlNode *c_parent = c->parent;
+        DavXmlNode *prev = NULL;
+        cxListRemove(stack, 0);
+        while(n) {
+            DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+            if(!ret) {
+                ret = newxn;
+            }
+            newxn->type = convert_type(n->type);
+            newxn->parent = c_parent;
+            if(c_parent && !c_parent->children) {
+                c_parent->children = newxn;
+            }
+            newxn->prev = prev;
+            if(prev) {
+                prev->next = newxn;
+            }
+            
+            if(newxn->type == DAV_XML_ELEMENT) {
+                newxn->name = dav_session_strdup(sn, (char*)n->name);
+                if(n->ns && n->ns->href) {
+                    newxn->namespace = dav_session_strdup(sn, (char*)n->ns->href);
+                }
+                
+                xmlAttr *attr = n->properties;
+                DavXmlAttr *newattr = NULL;
+                DavXmlAttr *newattr_last = NULL;
+                while(attr) {
+                    DavXmlAttr *na = cxCalloc(a, 1, sizeof(DavXmlAttr));
+                    na->name = dav_session_strdup(sn, (char*)attr->name);
+                    if(attr->children && attr->children->type == XML_TEXT_NODE) {
+                        na->value = dav_session_strdup(sn, (char*)attr->children->content);
+                    }
+                    if(!newattr) {
+                        newattr = na;
+                    } else {
+                        newattr_last->next = na;
+                    }
+                    newattr_last = na;
+                    
+                    attr = attr->next;
+                }
+                newxn->attributes = newattr;
+                
+                if(n->children) {
+                    ConvXmlElm convc;
+                    convc.node = n->children;
+                    convc.parent = newxn;
+                    cxListInsert(stack, 0, &convc);
+                }
+            } else if(newxn->type == DAV_XML_TEXT) {
+                cxmutstr content = cx_strdup_a(a, cx_str((char*)n->content));
+                newxn->content = content.ptr;
+                newxn->contentlength = content.length;
+            }
+            
+            prev = newxn;
+            n = n->next;
+        }
+    }
+    
+    return ret;
+}
+
+void dav_print_xml(DavXmlNode *node) {
+    if(node->type == DAV_XML_ELEMENT) {
+        printf("<%s", node->name);
+        DavXmlAttr *attr = node->attributes;
+        while(attr) {
+            printf(" %s=\"%s\"", attr->name, attr->value);
+            attr = attr->next;
+        }
+        putchar('>');
+        
+        DavXmlNode *child = node->children;
+        if(child) {
+            dav_print_xml(child);
+        }
+        
+        printf("</%s>", node->name);
+    } else {
+        fwrite(node->content, 1, node->contentlength, stdout);
+        fflush(stdout);
+    }
+    if(node->next) {
+        dav_print_xml(node->next);
+    }
+}
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node) {
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            char *tagend = node->children ? ">" : " />";          
+            char *prefix = NULL;
+            char *prefix_fr = NULL;
+            if(node->namespace) {
+                prefix = cxMapGet(nsmap, cx_hash_key_str(node->namespace));
+                if(!prefix) {
+                    cxmutstr newpre = cx_asprintf("x%zu", cxMapSize(nsmap)+1);
+                    // TODO: fix
+                    //cxMapPut(nsmap, node->namespace, newpre.ptr);
+                    prefix = newpre.ptr;
+                    prefix_fr = prefix;
+                    cx_fprintf(
+                            stream,
+                            writef,
+                            "<%s:%s xmlns:%s=\"%s\"",
+                            prefix,
+                            node->name,
+                            prefix,
+                            node->namespace);
+                } else {
+                    cx_fprintf(stream, writef, "<%s:%s", prefix, node->name);
+                }
+            } else {
+                cx_fprintf(stream, writef, "<%s", node->name);
+            }
+            
+            DavXmlAttr *attr = node->attributes;
+            while(attr) {
+                cx_fprintf(stream, writef, " %s=\"%s\"", attr->name, attr->value);
+                attr = attr->next;
+            }
+            writef(tagend, 1, strlen(tagend), stream); // end xml tag
+            
+            if(node->children) {
+                dav_print_node(stream, writef, nsmap, node->children);
+                if(prefix) {
+                    cx_fprintf(stream, writef, "</%s:%s>", prefix, node->name);
+                } else {
+                    cx_fprintf(stream, writef, "</%s>", node->name);
+                }
+            }
+            
+            if(prefix_fr) {
+                free(prefix_fr);
+            }
+        } else if(node->type == DAV_XML_TEXT) {
+            writef(node->content, 1, node->contentlength, stream);
+        }
+        
+        node = node->next;
+    }
+}
+
+/* ------------------------- public API ------------------------- */
+
+char* dav_xml_getstring(DavXmlNode *node) {
+    if(node && node->type == DAV_XML_TEXT) {
+        return node->content;
+    } else {
+        return NULL;
+    }
+}
+
+DavBool dav_xml_isstring(DavXmlNode *node) {
+    if(node && node->type == DAV_XML_TEXT && !node->next) {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node) {
+    node = node->next;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            return node;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+DavXmlNode* dav_text_node(DavSession *sn, const char *text) {
+    const CxAllocator *a = sn->mp->allocator; 
+    DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+    newxn->type = DAV_XML_TEXT;
+    cxmutstr content = cx_strdup_a(a, cx_str(text));
+    newxn->content = content.ptr;
+    newxn->contentlength = content.length;
+    return newxn;
+}
+
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text) {
+    const CxAllocator *a = sn->mp->allocator; 
+    DavXmlNode *newelm = cxCalloc(a, 1, sizeof(DavXmlNode));
+    newelm->type = DAV_XML_ELEMENT;
+    newelm->namespace = cx_strdup_a(a, cx_str(ns)).ptr;
+    newelm->name = cx_strdup_a(a, cx_str(name)).ptr;
+    newelm->children = dav_text_node(sn, text);
+    return newelm;
+}
+
+static void dav_free_xml_node_a(const CxAllocator *a, DavXmlNode *node) {
+    if(node->name) cxFree(a, node->name);
+    if(node->namespace) cxFree(a, node->namespace);
+    if(node->content) cxFree(a, node->content);
+    DavXmlAttr *attr = node->attributes;
+    while(attr) {
+        if(attr->name) cxFree(a, attr->name);
+        if(attr->value) cxFree(a, attr->value);
+        attr = attr->next;
+    }
+    DavXmlNode *children = node->children;
+    while(children) {
+        DavXmlNode *next_ch = children->next;
+        dav_free_xml_node_a(a, children);
+        children = next_ch;
+    }
+    cxFree(a, node);
+}
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node) {
+    dav_free_xml_node_a(sn->mp->allocator, node);
+}
+
+void dav_free_xml_node(DavXmlNode *node) {
+    dav_free_xml_node_a(cxDefaultAllocator, node);
+}
+
+DavXmlAttr* dav_copy_xml_attr(DavXmlAttr *attr) {
+    if(!attr) {
+        return NULL;
+    }
+    DavXmlAttr *newattr = NULL;
+    DavXmlAttr *prev = NULL;
+    while(attr) {
+        DavXmlAttr *n = calloc(1, sizeof(DavXmlAttr));
+        n->name = strdup(attr->name);
+        n->value = strdup(attr->value);
+        if(prev) {
+            prev->next = n;
+        } else {
+            newattr = n;
+        }
+        prev = n;
+        attr = attr->next;
+    }
+    return newattr;
+}
+
+DavXmlNode* dav_copy_node(DavXmlNode *node) {
+    DavXmlNode *ret = NULL;
+    DavXmlNode *prev = NULL;
+    while(node) {
+        DavXmlNode *copy = calloc(1, sizeof(DavXmlNode));
+        copy->type = node->type;
+        if(node->type == DAV_XML_ELEMENT) {
+            copy->namespace = strdup(node->namespace);
+            copy->name = strdup(node->name);
+            copy->children = dav_copy_node(node->children);
+            copy->attributes = dav_copy_xml_attr(node->attributes);
+        } else {
+            copy->contentlength = node->contentlength;
+            copy->content = malloc(node->contentlength+1);
+            memcpy(copy->content, node->content, node->contentlength);
+            copy->content[copy->contentlength] = 0;
+        }
+        if(!ret) {
+            ret = copy;
+        }
+        if(prev) {
+            prev->next = copy;
+            copy->prev = prev;
+        }
+        prev = copy;
+        node = node->next;
+    }
+    return ret;
+}
+
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    return node;
+}
+
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    
+    DavXmlNode *textnode = dav_xml_createtextnode(text);
+    node->children = textnode;
+    
+    return node;
+}
+
+DavXmlNode* dav_xml_createtextnode(const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_TEXT;
+    cxmutstr content = cx_strdup(cx_str((char*)text));
+    node->content = content.ptr;
+    node->contentlength = content.length;
+    return node;
+}
+
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child) {
+    DavXmlNode *last_child = NULL;
+    DavXmlNode *c = node->children;
+    while(c) {
+        last_child = c;
+        c = c->next;
+    }
+    if(last_child) {
+        last_child->next = child;
+        child->prev = last_child;
+    } else {
+        node->children = child;
+    }
+}
+
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value) {
+    DavXmlAttr *attr = calloc(1, sizeof(DavXmlAttr));
+    attr->name = strdup(name);
+    attr->value = strdup(value);
+    
+    if(node->attributes) { 
+        DavXmlAttr *end = node->attributes;
+        DavXmlAttr* last = end;
+        while(end) {
+            last = end;
+            end = end->next;
+        }
+        last->next = attr;
+    } else {
+        node->attributes = attr;
+    }
+}
+
+char* dav_xml_get_attr(DavXmlNode *node, const char *name) {
+    DavXmlAttr *attr = node->attributes;
+    while(attr) {
+        if(!strcmp(attr->name, name)) {
+            return attr->value;
+        }
+        
+        attr = attr->next;
+    }
+    return NULL;
+}
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len) {
+    xmlDoc *doc = xmlReadMemory(str, len, NULL, NULL, 0);
+    if(!doc) {
+        return NULL;
+    }
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    if(!xml_root) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    DavXmlNode *x = dav_convert_xml(sn, xml_root);
+    xmlFreeDoc(doc);
+    return x;
+}
diff --git a/libidav/xml.h b/libidav/xml.h
new file mode 100644 (file)
index 0000000..834440b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 DAV_XML_H
+#define DAV_XML_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node);
+
+void dav_print_xml(DavXmlNode *node);
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_XML_H */
+
diff --git a/make/Makefile.mk b/make/Makefile.mk
new file mode 100644 (file)
index 0000000..f41b7f7
--- /dev/null
@@ -0,0 +1,57 @@
+#
+# 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/libidav
+BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
+
+all: $(BUILD_DIRS) ucx ui libidav application
+       
+
+$(BUILD_DIRS):
+       mkdir -p $@
+
+ui: ucx FORCE
+       cd ui; $(MAKE) all
+
+ucx: FORCE
+       cd ucx; $(MAKE) all
+
+libidav: ucx FORCE
+       cd libidav; $(MAKE) all
+
+application: ui libidav FORCE
+       cd application; $(MAKE)
+
+FORCE:
+
diff --git a/make/cc.mk b/make/cc.mk
new file mode 100644 (file)
index 0000000..149bde6
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# cc toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
\ No newline at end of file
diff --git a/make/clang.mk b/make/clang.mk
new file mode 100644 (file)
index 0000000..772ebb7
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# clang toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
diff --git a/make/configure.vm b/make/configure.vm
new file mode 100644 (file)
index 0000000..b8acad3
--- /dev/null
@@ -0,0 +1,673 @@
+#!/bin/sh
+
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+    :
+else
+    echo "Cannot create tmp dir $TEMP_DIR"
+    echo "Abort"
+    exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
+
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
+
+# custom variables
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.varName}=`${var.value}`
+#else
+${var.varName}="${var.value}"
+#end
+#end
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.auto} )
+${feature.varName}=auto
+#end
+#end
+
+# clean abort
+abort_configure()
+{
+    rm -Rf "$TEMP_DIR"
+    exit 1
+}
+
+# help text
+printhelp()
+{
+    echo "Usage: $0 [OPTIONS]..."
+    cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       run-time variable data [LOCALSTATEDIR/run]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+
+#if( $options.size() > 0 )
+Options:
+  --debug                 add extra compile flags for debug builds
+  --release               add extra compile flags for release builds
+#foreach( $opt in $options )
+  --${opt.argument}=${opt.valuesString}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+${feature.helpText}
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments
+#
+BUILD_TYPE="default"
+#set( $D = '$' )
+for ARG in "$@"
+do
+    case "$ARG" in
+        "--prefix="*)         prefix=${D}{ARG#--prefix=} ;;
+        "--exec-prefix="*)    exec_prefix=${D}{ARG#--exec-prefix=} ;;
+        "--bindir="*)         bindir=${D}{ARG#----bindir=} ;;
+        "--sbindir="*)        sbindir=${D}{ARG#--sbindir=} ;;
+        "--libdir="*)         libdir=${D}{ARG#--libdir=} ;;
+        "--libexecdir="*)     libexecdir=${D}{ARG#--libexecdir=} ;;
+        "--datarootdir="*)    datarootdir=${D}{ARG#--datarootdir=} ;;
+        "--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} ;;
+        "--localedir"*)       localedir=${D}{ARG#--localedir} ;;
+        "--help"*) printhelp; abort_configure ;;
+        "--debug")           BUILD_TYPE="debug" ;;
+        "--release")         BUILD_TYPE="release" ;;
+    #foreach( $opt in $options )
+        "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
+    #end
+    #foreach( $feature in $features )
+        "--enable-${feature.arg}") ${feature.varName}=on ;;
+        "--disable-${feature.arg}") unset ${feature.varName} ;;
+    #end
+        "-"*) echo "unknown option: $ARG"; abort_configure ;;
+    esac
+done
+
+## Begin unparsed content. **
+#[[
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+    # CONFIG_SITE may contain space separated file names
+    for cs in $CONFIG_SITE; do
+        printf "loading defaults from $cs... "
+        . "$cs"
+        echo ok
+    done
+elif [ -f "$prefix/share/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/share/config.site"
+    echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+    printf "loading site defaults... "
+    . "$prefix/etc/config.site"
+    echo ok
+fi
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+    PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+    PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+    PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+    PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+    PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+    PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+    PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notisplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ "$p" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+istoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+notistoolchain()
+{
+    for t in $TOOLCHAIN
+    do
+        if [ "$t" = "$1" ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+]]#
+## End of unparsed content **
+
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
+#foreach( $var in $vars )
+${var.varName}=${D}${var.varName}
+#end
+__EOF__
+
+# toolchain detection utilities
+. make/toolchain.sh
+
+#
+# DEPENDENCIES
+#
+
+# check languages
+lang_c=
+lang_cpp=
+#foreach( $lang in $languages )
+if detect_${lang}_compiler ; then
+    lang_${lang}=1
+fi
+#end
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
+{
+    if "$PKG_CONFIG" --exists "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+    else return 1 ; fi
+    if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+    else return 1 ; fi
+    return 0
+}
+
+print_check_msg()
+{
+    if [ -z "$1" ]; then
+        shift
+        printf "$@"
+    fi
+}
+
+#foreach( $dependency in $namedDependencies )
+dependency_error_${dependency.id}()
+{
+    print_check_msg "${D}dep_checked_${dependency.id}" "checking for ${dependency.name}... "
+    #foreach( $sub in $dependency.subdependencies )
+    # dependency $sub.fullName
+    while true
+    do
+        #if( $sub.platform )
+        if notisplatform "${sub.platform}"; then
+            break
+        fi
+        #end
+        #if( $sub.toolchain )
+        if notistoolchain "${sub.toolchain}"; then
+            break
+        fi
+        #end
+        #foreach( $np in $sub.notList )
+        if isplatform "${np}" || istoolchain "${np}"; then
+            break
+        fi
+        #end
+        #foreach( $lang in $sub.lang )
+        if [ -z "$lang_${lang}" ] ; then
+            break
+        fi
+        #end
+        #if( $sub.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        #end
+        #foreach( $test in $sub.tests )
+        if $test > /dev/null ; then
+            :
+        else
+            break
+        fi
+        #end
+        #foreach( $pkg in $sub.pkgconfig )
+        if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+        else
+            break
+        fi
+        #end
+        #foreach( $flags in $sub.flags )
+        #if( $flags.exec )
+        if tmp_flags=`$flags.value` ; then
+            TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
+        else
+            break
+        fi
+        #else
+        TEMP_$flags.varName="$TEMP_$flags.varName $flags.value"
+        #end
+        #end
+        #if ( $sub.make.length() > 0 )
+        cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name
+$sub.make
+__EOF__
+        #end
+        print_check_msg "${D}dep_checked_${dependency.id}" "yes\n"
+        dep_checked_${dependency.id}=1
+        return 1
+    done
+
+    #end
+    print_check_msg "${D}dep_checked_${dependency.id}" "no\n"
+    dep_checked_${dependency.id}=1
+    return 0
+}
+#end
+
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+    #if( $dependency.platform )
+    if notisplatform "${dependency.platform}"; then
+        break
+    fi
+    #end
+    #if( $dependency.toolchain )
+    if notistoolchain "${dependency.toolchain}"; then
+        break
+    fi
+    #end
+    #foreach( $np in $dependency.notList )
+    if isplatform "${np}" || istoolchain "${np}"; then
+        break
+    fi
+    #end
+    while true
+    do
+        #foreach( $lang in $dependency.lang )
+        if [ -z "$lang_${lang}" ] ; then
+            ERROR=1
+            break
+        fi
+        #end
+        #if( $dependency.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+            ERROR=1
+            break
+        fi
+        #end
+        #foreach( $pkg in $dependency.pkgconfig )
+        print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "checking for pkg-config package $pkg.name... "
+        if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+            print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "yes\n"
+            dep_pkgconfig_checked_${pkg.id}=1
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+        else
+            print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "no\n"
+            dep_pkgconfig_checked_${pkg.id}=1
+            ERROR=1
+            break
+        fi
+        #end
+
+        #foreach( $flags in $dependency.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if tmp_flags=`$flags.value` ; then
+            TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
+        else
+            ERROR=1
+            break
+        fi
+        #else
+        TEMP_$flags.varName="$TEMP_$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 flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+    VERR=0
+    #foreach( $dep in $val.dependencies )
+    if dependency_error_$dep ; then
+        VERR=1
+    fi
+    #end
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    #foreach( $def in $val.defines )
+        TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}"
+        TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}"
+    #end
+    #if( $val.hasMake() )
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+$val.make
+__EOF__
+    #end
+    return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+
+#foreach( $target in $targets )
+echo >> "$TEMP_DIR/flags.mk"
+#if ( $target.name )
+echo "configuring target: $target.name"
+echo "# flags for target $target.name" >> "$TEMP_DIR/flags.mk"
+#else
+echo "configuring global target"
+echo "# flags for unnamed target" >> "$TEMP_DIR/flags.mk"
+#end
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+if dependency_error_$dependency; then
+    DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+    ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ -n "${D}${feature.varName}" ]; then
+#foreach( $dependency in $feature.dependencies )
+    # check dependency
+    if dependency_error_$dependency ; then
+        # "auto" features can fail and are just disabled in this case
+        if [ "${D}${feature.varName}" = "auto" ]; then
+            DISABLE_${feature.varName}=1
+        else
+            DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+            ERROR=1
+        fi
+    fi
+#end
+    if [ -n "$DISABLE_${feature.varName}" ]; then
+        unset ${feature.varName}
+    fi
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z "${D}${opt.varName}" ]; then
+    echo "auto-detecting option '${opt.argument}'"
+    SAVED_ERROR="$ERROR"
+    SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+    ERROR=1
+    while true
+    do
+        #foreach( $optdef in $opt.defaults )
+        #if( $optdef.platform )
+        if isplatform "$optdef.platform"; then
+        #end
+        if $optdef.func ; 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
+        SAVED_DEPENDENCIES_FAILED="option '${opt.argument}' $SAVED_DEPENDENCIES_FAILED"
+    fi
+    ERROR="$SAVED_ERROR"
+    DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
+else
+    echo "checking option ${opt.argument} = ${D}${opt.varName}"
+    if false; then
+        false
+    #foreach( $optval in $opt.values )
+    elif [ "${D}${opt.varName}" = "${optval.value}" ]; then
+        echo "  ${opt.argument}: ${D}${opt.varName}" >> $TEMP_DIR/options
+        if $optval.func ; then
+            :
+        else
+            ERROR=1
+            DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
+        fi
+    #end
+    fi
+fi
+#end
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+    echo "${target.cFlags}  += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+    echo "${target.cxxFlags}  += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+    if [ -n "$lang_c" ]; then
+        echo '${target.cFlags} += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo '${target.cxxFlags} += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+    if [ -n "$lang_c" ]; then
+        echo '${target.cFlags} += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+    if [ -n "$lang_cpp" ]; then
+        echo '${target.cxxFlags} += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+    fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+    echo "${target.ldFlags} += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+#end
+
+# final result
+if [ $ERROR -ne 0 ]; then
+    echo
+    echo "Error: Unresolved dependencies"
+    echo "$DEPENDENCIES_FAILED"
+    abort_configure
+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
+#if ( $features.size() > 0 )
+echo "Features:"
+#foreach( $feature in $features )
+if [ -n "${D}${feature.varName}" ]; then
+echo "  $feature.name: on"
+else
+echo "  $feature.name: off"
+fi
+#end
+#end
+echo
+
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
+
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
diff --git a/make/gcc.mk b/make/gcc.mk
new file mode 100644 (file)
index 0000000..5eea8e1
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# gcc toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
+
diff --git a/make/mingw.mk b/make/mingw.mk
new file mode 100644 (file)
index 0000000..340102e
--- /dev/null
@@ -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
diff --git a/make/osx.mk b/make/osx.mk
new file mode 100644 (file)
index 0000000..0db5e1c
--- /dev/null
@@ -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
diff --git a/make/package_osx.sh b/make/package_osx.sh
new file mode 100644 (file)
index 0000000..b6ecb00
--- /dev/null
@@ -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/
+
diff --git a/make/package_unix.sh b/make/package_unix.sh
new file mode 100644 (file)
index 0000000..13f4793
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+
diff --git a/make/package_windows.sh b/make/package_windows.sh
new file mode 100644 (file)
index 0000000..13f4793
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+
diff --git a/make/project.xml b/make/project.xml
new file mode 100644 (file)
index 0000000..ff244ec
--- /dev/null
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://unixwork.de/uwproj">
+       <dependency>
+               <lang>c</lang>
+       </dependency>
+       
+       <dependency name="curl" platform="macos">
+               <cflags exec="true">curl-config --cflags</cflags>
+               <ldflags exec="true">curl-config --ldflags</ldflags>
+       </dependency>
+       <dependency name="curl">
+               <pkgconfig>libcurl</pkgconfig>
+       </dependency>
+       <dependency name="curl">
+               <cflags exec="true">curl-config --cflags</cflags>
+               <ldflags exec="true">curl-config --libs</ldflags>
+       </dependency>
+       
+       <dependency name="libxml2" platform="windows">
+               <cflags exec="true">xml2-config --cflags</cflags>
+               <ldflags exec="true">xml2-config --libs</ldflags>
+       </dependency>
+       <dependency name="libxml2" platform="macos">
+               <cflags exec="true">xml2-config --cflags</cflags>
+               <ldflags exec="true">xml2-config --libs</ldflags>
+       </dependency>
+       <dependency name="libxml2">
+               <pkgconfig>libxml-2.0</pkgconfig>
+       </dependency>
+       <dependency name="libxml2">
+               <cflags exec="true">xml2-config --cflags</cflags>
+               <ldflags exec="true">xml2-config --libs</ldflags>
+       </dependency>
+       
+       <dependency name="openssl" platform="windows">
+               <ldflags>-lssl -lcrypto</ldflags>
+       </dependency>
+       <dependency name="openssl" platform="macos">
+               <ldflags>-framework CoreFoundation</ldflags>
+       </dependency>
+       <dependency name="openssl" platform="bsd" not="macos">
+               <ldflags>-lssl -lcrypto</ldflags>
+       </dependency>
+       <dependency name="openssl">
+               <pkgconfig>openssl</pkgconfig>
+       </dependency>
+       
+       <dependency name="libadwaita">
+               <pkgconfig>libadwaita-1</pkgconfig>
+               <cflags>-DUI_GTK4 -DUI_LIBADWAITA</cflags>
+               <ldflags>-lpthread</ldflags>
+       </dependency>
+       <dependency name="gtk4">
+               <pkgconfig>gtk4</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 exec="true">qmake-qt4 -o - /dev/null | grep DEFINES\ </cflags>
+               <cflags exec="true">qmake-qt4 -o - /dev/null | grep INCPATH\ </cflags>
+               <ldflags exec="true">qmake-qt4 -o - /dev/null | grep LIBS\ </ldflags>
+       </dependency>
+       
+       <dependency name="qt5">
+               <test>which qmake-qt5</test>
+               <cflags exec="true">qmake-qt5 -o - /dev/null | grep DEFINES\ </cflags>
+               <cflags exec="true">qmake-qt5 -o - /dev/null | grep INCPATH\ </cflags>
+               <ldflags exec="true">qmake-qt5 -o - /dev/null | grep LIBS\ </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="dav">
+               <dependencies>curl,libxml2,openssl</dependencies>
+       </target>
+       
+       <target name="tk">
+               <option arg="toolkit">
+                       <value str="libadwaita">
+                               <dependencies>libadwaita</dependencies>
+                               <make>TOOLKIT = gtk</make>
+                               <make>GTKOBJ = draw_cairo.o</make>
+                       </value>
+                       <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="gtk4" />
+                       <default value="gtk3" />
+                       <default value="qt5" />
+                       <default value="gtk2" />
+                       <default value="qt4" />
+                       <default value="motif" />
+               </option>
+       </target>
+</project>
+
diff --git a/make/suncc.mk b/make/suncc.mk
new file mode 100644 (file)
index 0000000..38cbb87
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# suncc toolchain
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
diff --git a/make/toolchain.sh b/make/toolchain.sh
new file mode 100644 (file)
index 0000000..ac5fb68
--- /dev/null
@@ -0,0 +1,200 @@
+#!/bin/sh
+#
+# toolchain detection
+#
+
+if isplatform "bsd" && notisplatform "openbsd"; then
+  C_COMPILERS="clang gcc cc"
+  CPP_COMPILERS="clang++ g++ CC"
+else
+  C_COMPILERS="gcc clang suncc cc"
+  CPP_COMPILERS="g++ clang++ sunCC CC"
+fi
+unset TOOLCHAIN
+unset TOOLCHAIN_NAME
+unset TOOLCHAIN_CC
+unset TOOLCHAIN_CXX
+
+check_c_compiler()
+{
+  cat > "$TEMP_DIR/test.c" << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(_MSC_VER)
+  printf("msc\n");
+#elif defined(__clang__)
+  printf("clang gnuc\n");
+#elif defined(__GNUC__)
+  printf("gcc gnuc\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
+}
+
+check_cpp_compiler()
+{
+  cat > "$TEMP_DIR/test.cpp" << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(_MSC_VER)
+  std::cout << "msc" << std::endl;
+#elif defined(__clang__)
+  std::cout << "clang gnuc" << std::endl;
+#elif defined(__GNUC__)
+  std::cout << "gcc gnuc" << std::endl;
+#elif defined(__sun)
+  std::cout << "suncc" << std::endl;
+#else
+  std::cout << "cc" << 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
+}
+
+create_libtest_source()
+{
+  # $1: filename
+  # $2: optional include
+  cat > "$TEMP_DIR/$1" << __EOF__
+/* libtest file */
+int main(int argc, char **argv) {
+  return 0;
+}
+__EOF__
+  if [ -n "$2" ]; then
+    echo "#include <$2>" >> "$TEMP_DIR/$1"
+  fi
+}
+
+check_c_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -z "$TOOLCHAIN_CC" ]; then
+    return 1
+  fi
+  create_libtest_source "test.c" "$2"
+  rm -f "$TEMP_DIR/checklib"
+  $TOOLCHAIN_CC -o "$TEMP_DIR/checklib" $CFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.c" 2> /dev/null
+}
+
+check_cpp_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -z "$TOOLCHAIN_CXX" ]; then
+    return 1
+  fi
+  create_libtest_source "test.cpp" "$2"
+  rm -f "$TEMP_DIR/checklib"
+  $TOOLCHAIN_CXX -o "$TEMP_DIR/checklib" $CXXFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.cpp" 2> /dev/null
+}
+
+check_lib()
+{
+  # $1: libname
+  # $2: optional include
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    check_c_lib "$1" "$2"
+  elif  [ -n "$TOOLCHAIN_CXX" ]; then
+    check_cpp_lib "$1" "$2"
+  fi
+}
+
+detect_c_compiler()
+{
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    return 0
+  fi
+  printf "detect C compiler... "
+  if [ -n "$CC" ]; then
+    if check_c_compiler "$CC"; then
+      TOOLCHAIN_CC=$CC
+      TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+      TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+      echo "$CC"
+      return 0
+    else
+      echo "$CC is not a working C compiler"
+      return 1
+    fi
+  else
+    for COMP in $C_COMPILERS
+    do
+      if check_c_compiler "$COMP"; then
+        TOOLCHAIN_CC=$COMP
+        TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+        TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+        echo "$COMP"
+        return 0
+      fi
+    done
+    echo "not found"
+    return 1
+  fi
+}
+
+detect_cpp_compiler()
+{
+  if [ -n "$TOOLCHAIN_CXX" ]; then
+    return 0
+  fi
+  printf "detect C++ compiler... "
+
+  if [ -n "$CXX" ]; then
+    if check_cpp_compiler "$CXX"; then
+      TOOLCHAIN_CXX=$CXX
+      TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+      TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+      echo "$CXX"
+      return 0
+    else
+      echo "$CXX is not a working C++ compiler"
+      return 1
+    fi
+  else
+    for COMP in $CPP_COMPILERS
+    do
+      if check_cpp_compiler "$COMP"; then
+        TOOLCHAIN_CXX=$COMP
+        TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+        TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+        echo "$COMP"
+        return 0
+      fi
+    done
+    echo "${TOOLCHAIN_CXX:-"not found"}"
+    return 1
+  fi
+}
+
+write_toolchain_defaults()
+{
+  echo "# toolchain" >> "$1"
+  if [ -n "$TOOLCHAIN_CC" ]; then
+    echo "CC = ${TOOLCHAIN_CC}" >> "$1"
+  fi
+  if [ -n "$TOOLCHAIN_CXX" ]; then
+    echo "CXX = ${TOOLCHAIN_CXX}" >> "$1"
+  fi
+  echo >> "$1"
+  if [ -f "make/${TOOLCHAIN_NAME}.mk" ]; then
+    cat "make/${TOOLCHAIN_NAME}.mk" >> "$1"
+  elif [ -f "make/cc.mk" ]; then
+    cat "make/cc.mk" >> "$1"
+  else
+    echo "!!! WARNING !!! Default toolchain flags not found. Configuration might be incomplete."
+  fi
+}
diff --git a/make/uwproj.xsd b/make/uwproj.xsd
new file mode 100644 (file)
index 0000000..f702701
--- /dev/null
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns="http://unixwork.de/uwproj"
+           targetNamespace="http://unixwork.de/uwproj"
+           elementFormDefault="qualified"
+           version="0.2"
+>
+    <xs:element name="project" type="ProjectType"/>
+
+    <xs:complexType name="ProjectType">
+        <xs:annotation>
+            <xs:documentation>
+                The root element of an uwproj project.
+                Consists of an optional <code>config</code> element
+                and an arbitrary number of <code>dependency</code>
+                and <code>target</code> elements.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="config" type="ConfigType" minOccurs="0"/>
+            <xs:element name="dependency" type="DependencyType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="target" type="TargetType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="ConfigType">
+        <xs:annotation>
+            <xs:documentation>
+                The configuration section.
+                Consists of an arbitrary number of <code>var</code> elements.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="var" type="ConfigVarType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="ConfigVarType">
+        <xs:annotation>
+            <xs:documentation>
+                The definition of a configuration variable.
+                <p>
+                    Configuration variables are supposed to be used in the configure script and are also
+                    written to the resulting config file (in contrast to make variables, which are only
+                    written to the config file).
+                    The <code>name</code> attribute is mandatory, the value is defined by the text body of the element.
+                    The optional Boolean <code>exec</code> attribute (false by default) controls, whether the entire
+                    definition is automatically executed under command substitution.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="xs:string" use="required"/>
+                <xs:attribute name="exec" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="PkgConfigType">
+        <xs:annotation>
+            <xs:documentation>
+                Instructs configure to invoke <code>pkg-config</code>, if present on the system, to determine
+                compiler and linker flags. The text body of this element defines the package name to search.
+                To constrain the allowed versions, use the attributes <code>atleast, exact, max</code>.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="atleast" type="xs:string"/>
+                <xs:attribute name="exact" type="xs:string"/>
+                <xs:attribute name="max" type="xs:string"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="LangType">
+        <xs:annotation>
+            <xs:documentation>
+                Requests a compiler for the specified language. Allowed values are
+                c, cpp.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="c"/>
+            <xs:enumeration value="cpp"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="DependencyType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a dependency.
+                <p>
+                    If the optional <code>name</code> attribute is omitted, the dependency is global
+                    and must be satisfied, otherwise configuration shall fail.
+                    A <em>named dependency</em> can be referenced by a target (or is implicitly referenced
+                    by the default target, if no targets are specified).
+                    Multiple declarations for the same named dependency may exist, in which case each declaration
+                    is checked one after another, until one block is satisfied. The result of the first satisfied
+                    dependency declaration is supposed to be applied to the config file.
+                </p>
+                <p>
+                    The optional <code>platform</code> attribute may specify a <em>single</em> platform identifier and
+                    the optional <code>toolchain</code> attribute may specify a <em>single</em> toolchain.
+                    The optional <code>not</code> attribute may specify a comma-separated list of platform and/or
+                    toolchain identifiers.
+                    The configure script shall skip this dependency declaration if the detected platform and toolchain
+                    is not matching the filter specification of these attributes.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="lang" type="LangType"/>
+            <xs:element name="cflags" type="FlagsType"/>
+            <xs:element name="cxxflags" type="FlagsType"/>
+            <xs:element name="ldflags" type="FlagsType"/>
+            <xs:element name="pkgconfig" type="PkgConfigType"/>
+            <xs:element name="test" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        Specifies a custom command that shall be executed to test whether this dependency is satisfied.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="make" type="MakeVarType"/>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string"/>
+        <xs:attribute name="platform" type="xs:string"/>
+        <xs:attribute name="toolchain" type="xs:string"/>
+        <xs:attribute name="not" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="FlagsType">
+        <xs:annotation>
+            <xs:documentation>
+                Instructs configure to append the contents of the element's body to the respective flags variable.
+                If the optional <code>exec</code> flag is set to <code>true</code>, the contents are supposed to be
+                executed under command substitution <em>at configuration time</em> before they are applied.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="exec" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="TargetType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a build target that is supposed to be configured.
+                <p>
+                    If no build target is declared explicitly, an implicit default
+                    target is generated, which has the <code>alldependencies</code>
+                    flag set.
+                </p>
+                <p>
+                    The optional <code>name</code> attribute is also used to generate a prefix
+                    for the compiler and linker flags variables.
+                    Furthermore, a target may consist of an arbitrary number of <code>feature</code>,
+                    <code>option</code>, and <code>define</code> elements.
+                    Named dependencies can be listed (separated by comma) in the <code>dependencies</code>
+                    element. If this target shall use <em>all</em> available named dependencies, the empty
+                    element <code>alldependencies</code> can be used as a shortcut.
+                </p>
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="feature" type="FeatureType"/>
+            <xs:element name="option" type="OptionType"/>
+            <xs:element name="define" type="DefineType"/>
+            <xs:element name="dependencies" type="DependenciesType"/>
+            <xs:element name="alldependencies">
+                <xs:complexType/>
+            </xs:element>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="FeatureType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares an optional feature, that can be enabled during configuration, if all
+                <code>dependencies</code> are satisfied.
+                If a feature is enabled, all <code>define</code> and <code>make</code> definitions are
+                supposed to be applied to the config file.
+                In case the optional <code>default</code> attribute is set to true, the feature is enabled by default
+                and is supposed to be automatically disabled (without error) when the dependencies are not satisfied.
+                The name that is supposed to be used for the --enable and --disable arguments can be optionally
+                specified with the <code>arg</code> attribute. Otherwise, the <code>name</code> is used by default.
+                Optionally, a description for the help text of the resulting configure script can be specified by
+                adding a <code>desc</code> element.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:group ref="TargetDataGroup"/>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="arg" type="xs:string"/>
+        <xs:attribute name="default" type="xs:boolean" default="false"/>
+        <xs:element name="desc" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a configuration option.
+                The option argument name is specified with the <code>arg</code> attribute.
+                Then, the children of this element specify possible <code>values</code> by defining the conditions
+                (in terms of dependencies) and effects (in terms of defines and make variables) of each value.
+                Finally, a set of <code>default</code>s is specified which supposed to automagically select the most
+                appropriate value for a specific platform under the available dependencies (in case the option is not
+                explicitly specified by using the command line argument).
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="value" type="OptionValueType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="default" type="OptionDefaultType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="arg" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionValueType">
+        <xs:annotation>
+            <xs:documentation>
+                Declares a possible value for the option (in the <code>str</code> attribute) and
+                the conditions (<code>dependencies</code>) and effects, the value has.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:group ref="TargetDataGroup"/>
+        </xs:choice>
+        <xs:attribute name="str" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="OptionDefaultType">
+        <xs:annotation>
+            <xs:documentation>
+                Specifies a default value for this option. Multiple default values can be specified, in which case
+                they are checked one after another for availability. With the optional <code>platform</code> attribute,
+                the default value can be constrained to a <em>single</em> specific platform and is supposed to be
+                skipped by configure, when this platform is not detected.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:attribute name="value" type="xs:string" use="required"/>
+        <xs:attribute name="platform" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:group name="TargetDataGroup">
+        <xs:choice>
+            <xs:element name="define" type="DefineType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="dependencies" type="DependenciesType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="make" type="MakeVarType" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:choice>
+    </xs:group>
+
+    <xs:complexType name="DefineType">
+        <xs:annotation>
+            <xs:documentation>
+                Specifies C/C++ pre-processor definitions that are supposed to
+                be appended to the compiler flags, if supported.
+                (Note: for example, Fortran also supports C/C++ style pre-processor definitions under
+                certain circumstances)
+            </xs:documentation>
+        </xs:annotation>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="value" type="xs:string"/>
+    </xs:complexType>
+
+    <xs:simpleType name="DependenciesType">
+        <xs:annotation>
+            <xs:documentation>A comma-separated list of named dependencies.</xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string"/>
+    </xs:simpleType>
+
+    <xs:simpleType name="MakeVarType">
+        <xs:annotation>
+            <xs:documentation>
+                The text contents in the body of this element are supposed to be appended literally
+                to the config file without prior processing.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string"/>
+    </xs:simpleType>
+</xs:schema>
diff --git a/make/vs/idav.sln b/make/vs/idav.sln
new file mode 100644 (file)
index 0000000..d861c89
--- /dev/null
@@ -0,0 +1,85 @@
+\r
+Microsoft Visual Studio Solution File, Format Version 12.00\r
+# Visual Studio Version 17\r
+VisualStudioVersion = 17.5.33530.505\r
+MinimumVisualStudioVersion = 10.0.40219.1\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libidav", "libidav\libidav.vcxproj", "{C29C0378-6548-48E8-9426-31922515212A}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idav", "idav\idav.vcxproj", "{F975D652-779E-4945-BFC2-8C649C6F9938}"\r
+EndProject\r
+Global\r
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
+               Debug|ARM64 = Debug|ARM64\r
+               Debug|x64 = Debug|x64\r
+               Debug|x86 = Debug|x86\r
+               Release|ARM64 = Release|ARM64\r
+               Release|x64 = Release|x64\r
+               Release|x86 = Release|x86\r
+       EndGlobalSection\r
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.ActiveCfg = Debug|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.Build.0 = Debug|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.ActiveCfg = Debug|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.Build.0 = Debug|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.ActiveCfg = Debug|Win32\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.Build.0 = Debug|Win32\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.ActiveCfg = Release|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.Build.0 = Release|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.ActiveCfg = Release|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32\r
+               {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.ActiveCfg = Debug|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.Build.0 = Debug|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.ActiveCfg = Debug|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.Build.0 = Debug|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.ActiveCfg = Debug|Win32\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.Build.0 = Debug|Win32\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.ActiveCfg = Release|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.Build.0 = Release|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.ActiveCfg = Release|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.Build.0 = Release|x64\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.ActiveCfg = Release|Win32\r
+               {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.Build.0 = Release|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.ActiveCfg = Debug|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Build.0 = Debug|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Deploy.0 = Debug|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.ActiveCfg = Debug|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Build.0 = Debug|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Deploy.0 = Debug|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Build.0 = Release|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Deploy.0 = Release|ARM64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.ActiveCfg = Release|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Build.0 = Release|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Deploy.0 = Release|x64\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.ActiveCfg = Release|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Build.0 = Release|Win32\r
+               {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Deploy.0 = Release|Win32\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.ActiveCfg = Debug|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.Build.0 = Debug|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.ActiveCfg = Debug|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.Build.0 = Debug|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.ActiveCfg = Debug|Win32\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.Build.0 = Debug|Win32\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.ActiveCfg = Release|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.Build.0 = Release|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.ActiveCfg = Release|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.Build.0 = Release|x64\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.ActiveCfg = Release|Win32\r
+               {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.Build.0 = Release|Win32\r
+       EndGlobalSection\r
+       GlobalSection(SolutionProperties) = preSolution\r
+               HideSolutionNode = FALSE\r
+       EndGlobalSection\r
+       GlobalSection(ExtensibilityGlobals) = postSolution\r
+               SolutionGuid = {141CA624-F556-4BE7-9218-8D6EEAB95C95}\r
+       EndGlobalSection\r
+EndGlobal\r
diff --git a/make/vs/idav/app.manifest b/make/vs/idav/app.manifest
new file mode 100644 (file)
index 0000000..b204072
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+  <assemblyIdentity version="1.0.0.0" name="test.app"/>\r
+\r
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+    <application>\r
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+      \r
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+    </application>\r
+  </compatibility>\r
+  \r
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+    <windowsSettings>\r
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+    </windowsSettings>\r
+  </application>\r
+</assembly>
\ No newline at end of file
diff --git a/make/vs/idav/idav.vcxproj b/make/vs/idav/idav.vcxproj
new file mode 100644 (file)
index 0000000..f6b6887
--- /dev/null
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <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')" />\r
+  <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')" />\r
+  <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')" />\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <VCProjectVersion>16.0</VCProjectVersion>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{F975D652-779E-4945-BFC2-8C649C6F9938}</ProjectGuid>\r
+    <RootNamespace>testapp</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+    <WindowsPackageType>None</WindowsPackageType>\r
+    <AppxPackage>false</AppxPackage>\r
+    <ProjectName>idav</ProjectName>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="Shared">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\idav\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\idav\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Vcpkg">\r
+    <VcpkgEnableManifest>true</VcpkgEnableManifest>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Windows</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Windows</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">\r
+      <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>\r
+    </ProjectReference>\r
+    <ProjectReference Include="..\libidav\libidav.vcxproj">\r
+      <Project>{c29c0378-6548-48e8-9426-31922515212a}</Project>\r
+    </ProjectReference>\r
+    <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+    </ProjectReference>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\application\application.c" />\r
+    <ClCompile Include="..\..\..\application\config.c" />\r
+    <ClCompile Include="..\..\..\application\davcontroller.c" />\r
+       <ClCompile Include="..\..\..\application\download.c" />\r
+       <ClCompile Include="..\..\..\application\upload.c" />\r
+    <ClCompile Include="..\..\..\application\main.c" />\r
+    <ClCompile Include="..\..\..\application\settings.c" />\r
+    <ClCompile Include="..\..\..\application\system.c" />\r
+    <ClCompile Include="..\..\..\application\window.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\application\application.h" />\r
+    <ClInclude Include="..\..\..\application\config.h" />\r
+    <ClInclude Include="..\..\..\application\davcontroller.h" />\r
+       <ClInclude Include="..\..\..\application\download.h" />\r
+       <ClInclude Include="..\..\..\application\upload.h" />\r
+    <ClInclude Include="..\..\..\application\settings.h" />\r
+    <ClInclude Include="..\..\..\application\system.h" />\r
+    <ClInclude Include="..\..\..\application\window.h" />\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+    <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')" />\r
+    <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')" />\r
+    <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')" />\r
+    <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')" />\r
+  </ImportGroup>\r
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+    <PropertyGroup>\r
+      <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>\r
+    </PropertyGroup>\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+  </Target>\r
+</Project>\r
diff --git a/make/vs/idav/idav.vcxproj.filters b/make/vs/idav/idav.vcxproj.filters
new file mode 100644 (file)
index 0000000..8a5b438
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <Filter Include="Ressourcendateien">\r
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+    </Filter>\r
+    <Filter Include="src">\r
+      <UniqueIdentifier>{46525c70-7cfc-45fe-ae78-54123ad9bd7e}</UniqueIdentifier>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\application\application.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\main.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\window.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\config.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\system.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\davcontroller.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+       <ClCompile Include="..\..\..\application\download.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+       <ClCompile Include="..\..\..\application\upload.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\application\settings.c">\r
+      <Filter>src</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\application\application.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\application\window.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\application\config.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\application\system.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\application\davcontroller.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+       <ClInclude Include="..\..\..\application\download.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+       <ClInclude Include="..\..\..\application\upload.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\application\settings.h">\r
+      <Filter>src</Filter>\r
+    </ClInclude>\r
+  </ItemGroup>\r
+</Project>\r
diff --git a/make/vs/idav/packages.config b/make/vs/idav/packages.config
new file mode 100644 (file)
index 0000000..ece9b57
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />\r
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />\r
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />\r
+  <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
diff --git a/make/vs/libidav/libidav.vcxproj b/make/vs/libidav/libidav.vcxproj
new file mode 100644 (file)
index 0000000..41bebe2
--- /dev/null
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <VCProjectVersion>16.0</VCProjectVersion>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{c29c0378-6548-48e8-9426-31922515212a}</ProjectGuid>\r
+    <RootNamespace>libidav</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>StaticLibrary</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>StaticLibrary</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="Shared">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <TargetExt>.dll</TargetExt>\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Vcpkg">\r
+    <VcpkgEnableManifest>true</VcpkgEnableManifest>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc11</LanguageStandard_C>\r
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include</AdditionalIncludeDirectories>\r
+      <AdditionalOptions>\r
+      </AdditionalOptions>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <AdditionalLibraryDirectories>..\vcpkg_installed\x64-windows\x64-windows\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
+      <AdditionalDependencies>charset.lib;iconv.lib;libcurl.lib;libxml2.lib;lzma.lib;zlib.lib;bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+    </Link>\r
+    <PostBuildEvent>\r
+      <Command>xcopy /y $(SolutionDir)vcpkg_installed\x64-windows\x64-windows\bin\*.dll $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+      <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include</AdditionalIncludeDirectories>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\libidav\config.c" />\r
+    <ClCompile Include="..\..\..\libidav\crypto.c" />\r
+    <ClCompile Include="..\..\..\libidav\davqlexec.c" />\r
+    <ClCompile Include="..\..\..\libidav\davqlparser.c" />\r
+    <ClCompile Include="..\..\..\libidav\methods.c" />\r
+    <ClCompile Include="..\..\..\libidav\pwdstore.c" />\r
+    <ClCompile Include="..\..\..\libidav\resource.c" />\r
+    <ClCompile Include="..\..\..\libidav\session.c" />\r
+    <ClCompile Include="..\..\..\libidav\utils.c" />\r
+    <ClCompile Include="..\..\..\libidav\versioning.c" />\r
+    <ClCompile Include="..\..\..\libidav\webdav.c" />\r
+    <ClCompile Include="..\..\..\libidav\xml.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\libidav\config.h" />\r
+    <ClInclude Include="..\..\..\libidav\crypto.h" />\r
+    <ClInclude Include="..\..\..\libidav\davqlexec.h" />\r
+    <ClInclude Include="..\..\..\libidav\davqlparser.h" />\r
+    <ClInclude Include="..\..\..\libidav\methods.h" />\r
+    <ClInclude Include="..\..\..\libidav\pwdstore.h" />\r
+    <ClInclude Include="..\..\..\libidav\resource.h" />\r
+    <ClInclude Include="..\..\..\libidav\session.h" />\r
+    <ClInclude Include="..\..\..\libidav\utils.h" />\r
+    <ClInclude Include="..\..\..\libidav\versioning.h" />\r
+    <ClInclude Include="..\..\..\libidav\webdav.h" />\r
+    <ClInclude Include="..\..\..\libidav\xml.h" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+    </ProjectReference>\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+  </ImportGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/libidav/libidav.vcxproj.filters b/make/vs/libidav/libidav.vcxproj.filters
new file mode 100644 (file)
index 0000000..8d308cd
--- /dev/null
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <Filter Include="Quelldateien">\r
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+    </Filter>\r
+    <Filter Include="Headerdateien">\r
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+    </Filter>\r
+    <Filter Include="Ressourcendateien">\r
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\libidav\crypto.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\davqlexec.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\davqlparser.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\methods.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\resource.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\session.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\utils.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\versioning.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\webdav.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\xml.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\config.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\libidav\pwdstore.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\libidav\crypto.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\davqlexec.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\davqlparser.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\methods.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\resource.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\session.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\utils.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\versioning.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\webdav.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\xml.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\config.h">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\libidav\pwdstore.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+  </ItemGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/testapp/app.manifest b/make/vs/testapp/app.manifest
new file mode 100644 (file)
index 0000000..b204072
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+  <assemblyIdentity version="1.0.0.0" name="test.app"/>\r
+\r
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+    <application>\r
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+      \r
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+    </application>\r
+  </compatibility>\r
+  \r
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+    <windowsSettings>\r
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+    </windowsSettings>\r
+  </application>\r
+</assembly>
\ No newline at end of file
diff --git a/make/vs/testapp/main.c b/make/vs/testapp/main.c
new file mode 100644 (file)
index 0000000..aac5b3d
--- /dev/null
@@ -0,0 +1,378 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include <Windows.h>\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <stdbool.h>\r
+\r
+#include <ui/ui.h>\r
+\r
+typedef struct WindowData {\r
+    UiInteger* check;\r
+    UiInteger* toggle;\r
+    UiInteger* radio;\r
+    UiString* text;\r
+    UiString* password;\r
+    UiList* list;\r
+    UiString* t1;\r
+    UiString* t2;\r
+    UiString* t3;\r
+    UiString* path;\r
+    UiList* list2;\r
+    UiList* list3;\r
+    UiDouble* progress;\r
+    UiInteger* spinner;\r
+} WindowData;\r
+\r
+static UiIcon* folder_icon;\r
+\r
+void action1(UiEvent* event, void* data) {\r
+    char* action = data;\r
+    \r
+    WindowData* wdata = event->window;\r
+    int64_t is_checked = wdata->check->get(wdata->check);\r
+    int64_t radio = wdata->radio->get(wdata->radio);\r
+\r
+    printf("data: %s %d\n", data, is_checked);\r
+\r
+    double d = wdata->progress->get(wdata->progress);\r
+    wdata->progress->set(wdata->progress, d + 1);\r
+\r
+    int spinner_active = wdata->spinner->get(wdata->spinner);\r
+    wdata->spinner->set(wdata->spinner, !spinner_active);\r
+}\r
+\r
+void action_set_checkbox(UiEvent* event, void* data) {\r
+    char* action = data;\r
+\r
+    WindowData* wdata = event->window;\r
+    wdata->check->set(wdata->check, 1);\r
+}\r
+\r
+void action_onchange(UiEvent* event, void* data) {\r
+    printf("onchange: %d\n", event->intval);\r
+}\r
+\r
+void action_switch(UiEvent* event, void* data) {\r
+    printf("onchange: %d\n", event->intval);\r
+}\r
+\r
+void action_toolbar_button(UiEvent* event, void *data) {\r
+    printf("toolbar action\n");\r
+}\r
+\r
+\r
+void action_listselection_changed(UiEvent* event, void* data) {\r
+    printf("selection changed\n");\r
+    UiListSelection* sel = event->eventdata;\r
+    for (int i = 0; i < sel->count; i++) {\r
+        int row = sel->rows[i];\r
+        printf("row: %d\n", row);\r
+    }\r
+}\r
+\r
+void action_onactivate(UiEvent* event, void* Data) {\r
+    printf("activate\n");\r
+    UiListSelection* sel = event->eventdata;\r
+    for (int i = 0; i < sel->count; i++) {\r
+        int row = sel->rows[i];\r
+        printf("row: %d\n", row);\r
+    }\r
+}\r
+\r
+typedef struct TableData {\r
+    char* col1;\r
+    char* col2;\r
+    char* col3;\r
+} TableData;\r
+\r
+void* table_getvalue(void* data, int i) {\r
+    TableData* t = data;\r
+    switch (i) {\r
+    case 0: return folder_icon;\r
+    case 1: return t->col1;\r
+    case 2: return t->col2;\r
+    case 3: return t->col3;\r
+    }\r
+    return NULL;\r
+}\r
+\r
+void action_add(UiEvent* event, void* data) {\r
+    WindowData* wdata = event->window;\r
+    char* t1 = wdata->t1->get(wdata->t1);\r
+    char* t2 = wdata->t2->get(wdata->t2);\r
+    char* t3 = wdata->t3->get(wdata->t3);\r
+\r
+    TableData* tdat = malloc(sizeof(TableData));\r
+    tdat->col1 = _strdup(t1);\r
+    tdat->col2 = _strdup(t2);\r
+    tdat->col3 = _strdup(t3);\r
+    ui_list_append(wdata->list2, tdat);\r
+    wdata->list2->update(wdata->list2, 0);\r
+\r
+}\r
+\r
+void action_breadcrumb(UiEvent* event, void* data) {\r
+    int i = event->intval;\r
+    char* c = event->eventdata;\r
+    printf("index: %d\n", i);\r
+}\r
+\r
+void dragstart(UiEvent* event, void* data) {\r
+    UiListDnd* ldnd = event->eventdata;\r
+    ui_selection_settext(ldnd->dnd, "Hello World!", -1);\r
+}\r
+\r
+void dragcomplete(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void dragover(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void drop(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void application_startup(UiEvent* event, void* data) {\r
+    UiObject* obj = ui_window("Test", NULL);\r
+    WindowData* wdata = ui_malloc(obj->ctx, sizeof(WindowData));\r
+    obj->window = wdata;\r
+    wdata->check = ui_int_new(obj->ctx, "check");\r
+    wdata->toggle = ui_int_new(obj->ctx, "toggle");\r
+    wdata->radio = ui_int_new(obj->ctx, "radio");\r
+    wdata->text = ui_string_new(obj->ctx, "text");\r
+    wdata->password = ui_string_new(obj->ctx, "password");\r
+    wdata->list = ui_list_new(obj->ctx, "list");\r
+    wdata->list2 = ui_list_new(obj->ctx, "list2");\r
+    wdata->list3 = ui_list_new(obj->ctx, "list3");\r
+    wdata->t1 = ui_string_new(obj->ctx, "t1");\r
+    wdata->t2 = ui_string_new(obj->ctx, "t2");\r
+    wdata->t3 = ui_string_new(obj->ctx, "t3");\r
+    wdata->path = ui_string_new(obj->ctx, "path");\r
+    wdata->progress = ui_double_new(obj->ctx, "progress");\r
+    wdata->spinner = ui_int_new(obj->ctx, "spinner");\r
+\r
+    ui_list_append(wdata->list, "Hello");\r
+    ui_list_append(wdata->list, "World");\r
+    ui_list_append(wdata->list, "Item3");\r
+    ui_list_append(wdata->list, "Item4");\r
+    ui_list_append(wdata->list, "Item5");\r
+    ui_list_append(wdata->list, "Item6");\r
+\r
+    ui_list_append(wdata->list3, "usr");\r
+    ui_list_append(wdata->list3, "share");\r
+    ui_list_append(wdata->list3, "test");\r
+    ui_list_append(wdata->list3, "dir");\r
+\r
+    //folder_icon = ui_icon("Folder", 32);\r
+    folder_icon = ui_foldericon(16);\r
+\r
+    TableData* td1 = malloc(sizeof(TableData));\r
+    TableData* td2 = malloc(sizeof(TableData));\r
+    TableData* td3 = malloc(sizeof(TableData));\r
+    TableData* td4 = malloc(sizeof(TableData));\r
+    TableData* td5 = malloc(sizeof(TableData));\r
+    TableData* td6 = malloc(sizeof(TableData));\r
+    td1->col1 = "a1";\r
+    td1->col2 = "b1";\r
+    td1->col3 = "c1";\r
+    td2->col1 = "a2";\r
+    td2->col2 = "b2";\r
+    td2->col3 = "b3";\r
+    td3->col1 = "a3";\r
+    td3->col2 = "b3";\r
+    td3->col3 = "c3";\r
+    td4->col1 = "a3";\r
+    td4->col2 = "b3";\r
+    td4->col3 = "c3";\r
+    td5->col1 = "a3";\r
+    td5->col2 = "b3";\r
+    td5->col3 = "c3";\r
+    td6->col1 = "a3";\r
+    td6->col2 = "b3";\r
+    td6->col3 = "c3";\r
+\r
+    ui_list_append(wdata->list2, td1);\r
+    ui_list_append(wdata->list2, td2);\r
+    ui_list_append(wdata->list2, td3);\r
+    ui_list_append(wdata->list2, td4);\r
+    ui_list_append(wdata->list2, td5);\r
+    ui_list_append(wdata->list2, td6);\r
+\r
+    ui_scrolledwindow0(obj) {\r
+        ui_grid(obj, .margin = 10, .columnspacing = 5, .rowspacing = 20) {\r
+            ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+            ui_button(obj, .label = "Button2", .onclick = action1, .onclickdata = "action2");\r
+            ui_button(obj, .label = "Button3", .onclick = action1, .onclickdata = "action3", .hexpand = true);\r
+            ui_newline(obj);\r
+\r
+            ui_button(obj, .label = "Button4", .onclick = action1, .onclickdata = "action4");\r
+            ui_button(obj, .label = "Button5", .onclick = action1, .onclickdata = "action5", .colspan = 2);\r
+            ui_newline(obj);\r
+\r
+            ui_button(obj, .label = "Very Long Button Label Text ____________ Test", .onclick = action_set_checkbox);\r
+            ui_newline(obj);\r
+\r
+            ui_checkbox(obj, .label = "Option 1", .value = wdata->check, .onchange = action_onchange);\r
+            ui_togglebutton(obj, .label = "Option 2", .value = wdata->toggle);\r
+            ui_newline(obj);\r
+\r
+            ui_label(obj, .label = "Progress");\r
+            ui_progressspinner(obj, .value = wdata->spinner);\r
+            ui_newline(obj);\r
+            \r
+            ui_hbox(obj, .colspan = 3) {\r
+                ui_radiobutton(obj, .label = "Radio 1", .value = wdata->radio);\r
+                ui_radiobutton(obj, .label = "Radio 2", .value = wdata->radio);\r
+                ui_radiobutton(obj, .label = "Radio 3", .value = wdata->radio);\r
+            }\r
+            ui_newline(obj);\r
+            ui_radiobutton(obj, .label = "Radio 4", .value = wdata->radio);\r
+            ui_switch(obj, .label = "test", .onchange = action_switch);\r
+            ui_newline(obj);\r
+\r
+            //ui_breadcrumbbar(obj, .list = wdata->list3, .onactivate=action_breadcrumb);\r
+            ui_textfield(obj, .varname = "newtext");\r
+            ui_path_textfield(obj, .colspan = 2, .value=wdata->path, .onactivate = action_breadcrumb);\r
+            ui_newline(obj);\r
+            wdata->path->set(wdata->path, "/usr/path/test");\r
+            \r
+            ui_textfield(obj, .value = wdata->text);\r
+            ui_passwordfield(obj, .value = wdata->password);\r
+            ui_newline(obj);\r
+\r
+            ui_frame(obj, .label = "Test", .colspan = 3) {\r
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+            }\r
+            ui_newline(obj);\r
+            \r
+            ui_expander(obj, .label = "Expand", .colspan = 3, .margin = 10, .spacing = 5, .isexpanded = false) {\r
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+                ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+            }\r
+            ui_newline(obj);\r
+\r
+            ui_combobox(obj, .list = wdata->list, .onselection= action_listselection_changed, .onactivate= action_onactivate);\r
+            ui_newline(obj);\r
+\r
+            ui_tabview(obj, .colspan = 3, .vexpand = true, .hexpand = true, .tabview = UI_TABVIEW_NAVIGATION_SIDE) {\r
+                ui_tab(obj, "Tab 1") {\r
+                    ui_button(obj, .label = "Tab 1 Button");\r
+                }\r
+                ui_tab(obj, "Tab 2") {\r
+                    ui_button(obj, .label = "Tab 2 Button");\r
+                }\r
+                ui_tab(obj, "Tab 3") {\r
+\r
+                }\r
+            }\r
+            ui_newline(obj);\r
+\r
+            ui_label(obj, .label = "Test Label");\r
+            ui_progressbar(obj, .value = wdata->progress, .colspan = 2);\r
+            ui_newline(obj);\r
+\r
+            ui_newline(obj);\r
+            ui_textfield(obj, .value = wdata->t1);\r
+            ui_textfield(obj, .value = wdata->t2);\r
+            ui_textfield(obj, .value = wdata->t3);\r
+            ui_newline(obj);\r
+            ui_button(obj, .label = "Add", .onclick = action_add);\r
+            ui_newline(obj);\r
+\r
+\r
+            ui_newline(obj);\r
+\r
+            UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", UI_STRING, "Col 3", -1);\r
+            model->getvalue = table_getvalue;\r
+            ui_table(obj,   .colspan = 3, .model = model, .list = wdata->list2, .onactivate = action_onactivate,\r
+                            .onselection = action_listselection_changed,\r
+                            .ondragstart = dragstart, .ondragcomplete = dragcomplete, .ondrop = drop);\r
+            ui_model_free(obj->ctx, model);\r
+        }\r
+    }   \r
+\r
+    ui_show(obj);\r
+}\r
+\r
+\r
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)\r
+{ \r
+    ui_init("app1", 0, NULL);\r
+    ui_onstartup(application_startup, NULL);\r
+    \r
+    ui_menu("File") {\r
+        ui_menuitem(.label = "Item 1");\r
+        ui_menuitem(.label = "Item 2");\r
+        ui_menuseparator();\r
+        ui_menu("File Sub") {\r
+            ui_menuitem(.label = "Sub Item");\r
+        }\r
+\r
+        ui_menuitem(.label = "Exit");\r
+    }\r
+\r
+    ui_toolbar_item("Test", .label = "Home", .icon = "Home", .onclick = action_toolbar_button);\r
+    ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);\r
+    ui_toolbar_toggleitem("Toggle2", .label = "Toggle2", .onchange = action_toolbar_button);\r
+    ui_toolbar_toggleitem("Toggle3", .label = "Toggle3", .onchange = action_toolbar_button);\r
+\r
+    ui_toolbar_menu("Menu", .label = "Menu") {\r
+        \r
+        ui_menuitem(.label = "x", NULL, NULL);\r
+        ui_menuitem(.label = "x", NULL, NULL);\r
+        ui_menuitem(.label = "x", NULL, NULL);\r
+        ui_menuitem(.label = "x", NULL, NULL);\r
+        ui_menuitem(.label = "x", NULL, NULL);\r
+        ui_menu("TB Sub") {\r
+            ui_menuitem("TB subitem", NULL, NULL);\r
+        }\r
+    }\r
+\r
+    ui_toolbar_menu(NULL, .label = "Menu") {\r
+        ui_menuitem("Secondary Test", NULL, NULL);\r
+        ui_menu("Secondary Sub") {\r
+            ui_menuitem("Secondary subitem", NULL, NULL);\r
+        }\r
+    }\r
+\r
+    ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);\r
+    ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);\r
+    ui_toolbar_add_default("Toggle2", UI_TOOLBAR_CENTER);\r
+    ui_toolbar_add_default("Toggle3", UI_TOOLBAR_CENTER);\r
+    ui_toolbar_add_default("Menu", UI_TOOLBAR_RIGHT);\r
+\r
+    ui_main();\r
+\r
+    return (EXIT_SUCCESS);\r
+}\r
diff --git a/make/vs/testapp/packages.config b/make/vs/testapp/packages.config
new file mode 100644 (file)
index 0000000..ece9b57
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />\r
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />\r
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />\r
+  <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
diff --git a/make/vs/testapp/testapp.vcxproj b/make/vs/testapp/testapp.vcxproj
new file mode 100644 (file)
index 0000000..386f861
--- /dev/null
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <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')" />\r
+  <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')" />\r
+  <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')" />\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+       \r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <VCProjectVersion>16.0</VCProjectVersion>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{3541f08b-e6cc-4c23-a0d3-51983aab33c6}</ProjectGuid>\r
+    <RootNamespace>testapp</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+    <WindowsPackageType>None</WindowsPackageType>\r
+    <AppxPackage>false</AppxPackage>\r
+    <ProjectName>idav</ProjectName>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="Shared">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Windows</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="main.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">\r
+      <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>\r
+    </ProjectReference>\r
+    <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+    </ProjectReference>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+    <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')" />\r
+    <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')" />\r
+    <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')" />\r
+    <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')" />\r
+  </ImportGroup>\r
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+    <PropertyGroup>\r
+      <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>\r
+    </PropertyGroup>\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+    <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'))" />\r
+  </Target>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/testapp/testapp.vcxproj.filters b/make/vs/testapp/testapp.vcxproj.filters
new file mode 100644 (file)
index 0000000..5ed7d83
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <Filter Include="Quelldateien">\r
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+    </Filter>\r
+    <Filter Include="Headerdateien">\r
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+    </Filter>\r
+    <Filter Include="Ressourcendateien">\r
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="main.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/ucx/ucx.vcxproj b/make/vs/ucx/ucx.vcxproj
new file mode 100644 (file)
index 0000000..eadb7c2
--- /dev/null
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <VCProjectVersion>16.0</VCProjectVersion>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{27da0164-3475-43e2-a1a4-a5d07d305749}</ProjectGuid>\r
+    <RootNamespace>ucx</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>StaticLibrary</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>StaticLibrary</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="Shared">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\ucx\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\ucx\allocator.c" />\r
+    <ClCompile Include="..\..\..\ucx\array_list.c" />\r
+    <ClCompile Include="..\..\..\ucx\buffer.c" />\r
+    <ClCompile Include="..\..\..\ucx\compare.c" />\r
+    <ClCompile Include="..\..\..\ucx\hash_key.c" />\r
+    <ClCompile Include="..\..\..\ucx\hash_map.c" />\r
+    <ClCompile Include="..\..\..\ucx\linked_list.c" />\r
+    <ClCompile Include="..\..\..\ucx\list.c" />\r
+    <ClCompile Include="..\..\..\ucx\map.c" />\r
+    <ClCompile Include="..\..\..\ucx\mempool.c" />\r
+    <ClCompile Include="..\..\..\ucx\printf.c" />\r
+    <ClCompile Include="..\..\..\ucx\string.c" />\r
+    <ClCompile Include="..\..\..\ucx\utils.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\collection.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\common.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\compare.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\list.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\map.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\printf.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\string.h" />\r
+    <ClInclude Include="..\..\..\ucx\cx\utils.h" />\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+  </ImportGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/ucx/ucx.vcxproj.filters b/make/vs/ucx/ucx.vcxproj.filters
new file mode 100644 (file)
index 0000000..52256da
--- /dev/null
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <Filter Include="Quelldateien">\r
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+    </Filter>\r
+    <Filter Include="Headerdateien">\r
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+    </Filter>\r
+    <Filter Include="Ressourcendateien">\r
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\ucx\allocator.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\array_list.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\buffer.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\compare.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\hash_key.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\hash_map.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\linked_list.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\list.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\map.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\printf.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\string.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\utils.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ucx\mempool.c">\r
+      <Filter>Quelldateien</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\ucx\cx\allocator.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\array_list.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\buffer.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\collection.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\common.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\compare.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\hash_key.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\hash_map.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\iterator.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\linked_list.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\list.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\map.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\mempool.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\printf.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\string.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ucx\cx\utils.h">\r
+      <Filter>Headerdateien</Filter>\r
+    </ClInclude>\r
+  </ItemGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/uicommon/uicommon.vcxproj b/make/vs/uicommon/uicommon.vcxproj
new file mode 100644 (file)
index 0000000..6f8b494
--- /dev/null
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <VCProjectVersion>17.0</VCProjectVersion>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{8b88698e-c185-4383-99fe-0c34d6deed2e}</ProjectGuid>\r
+    <RootNamespace>uicommon</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>StaticLibrary</ConfigurationType>\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="Shared">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\..\build\vs\uicommon\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <SDLCheck>false</SDLCheck>\r
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+      <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <ClCompile>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <IntrinsicFunctions>true</IntrinsicFunctions>\r
+      <SDLCheck>true</SDLCheck>\r
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <ConformanceMode>true</ConformanceMode>\r
+    </ClCompile>\r
+    <Link>\r
+      <SubSystem>Console</SubSystem>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\ui\common\context.c" />\r
+    <ClCompile Include="..\..\..\ui\common\document.c" />\r
+    <ClCompile Include="..\..\..\ui\common\menu.c" />\r
+    <ClCompile Include="..\..\..\ui\common\object.c" />\r
+    <ClCompile Include="..\..\..\ui\common\properties.c" />\r
+    <ClCompile Include="..\..\..\ui\common\toolbar.c" />\r
+    <ClCompile Include="..\..\..\ui\common\types.c" />\r
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\ui\common\context.h" />\r
+    <ClInclude Include="..\..\..\ui\common\document.h" />\r
+    <ClInclude Include="..\..\..\ui\common\menu.h" />\r
+    <ClInclude Include="..\..\..\ui\common\object.h" />\r
+    <ClInclude Include="..\..\..\ui\common\properties.h" />\r
+    <ClInclude Include="..\..\..\ui\common\toolbar.h" />\r
+    <ClInclude Include="..\..\..\ui\common\types.h" />\r
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h" />\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+  </ImportGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/uicommon/uicommon.vcxproj.filters b/make/vs/uicommon/uicommon.vcxproj.filters
new file mode 100644 (file)
index 0000000..3322c9f
--- /dev/null
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <Filter Include="Ressourcendateien">\r
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+    </Filter>\r
+    <Filter Include="Ressourcendateien\Source">\r
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\..\..\ui\common\context.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\document.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\menu.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\object.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\properties.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\toolbar.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\types.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\..\..\ui\common\ucx_properties.c">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\..\..\ui\common\context.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\document.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\menu.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\object.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\properties.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\toolbar.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\types.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\..\..\ui\common\ucx_properties.h">\r
+      <Filter>Ressourcendateien\Source</Filter>\r
+    </ClInclude>\r
+  </ItemGroup>\r
+</Project>
\ No newline at end of file
diff --git a/make/vs/vcpkg.json b/make/vs/vcpkg.json
new file mode 100644 (file)
index 0000000..e6408b6
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+  "name": "libidav",
+  "dependencies": [
+    "libxml2",
+    "curl",
+    "pcre"
+  ],
+  "builtin-baseline": "2c401863dd54a640aeb26ed736c55489c079323b"
+}
diff --git a/resource/.DS_Store b/resource/.DS_Store
new file mode 100644 (file)
index 0000000..2bdfb30
Binary files /dev/null and b/resource/.DS_Store differ
diff --git a/resource/locales/de_DE.properties b/resource/locales/de_DE.properties
new file mode 100644 (file)
index 0000000..7ab93f2
--- /dev/null
@@ -0,0 +1 @@
+hello = HALLO WELT!
diff --git a/resource/locales/en_EN.properties b/resource/locales/en_EN.properties
new file mode 100644 (file)
index 0000000..3e033cf
--- /dev/null
@@ -0,0 +1 @@
+hello = HELLO WORLD!
diff --git a/resource/template.app/Contents/Info.plist b/resource/template.app/Contents/Info.plist
new file mode 100644 (file)
index 0000000..d23a9e1
--- /dev/null
@@ -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>
diff --git a/resource/template.app/Contents/PkgInfo b/resource/template.app/Contents/PkgInfo
new file mode 100644 (file)
index 0000000..bd04210
--- /dev/null
@@ -0,0 +1 @@
+APPL????
\ No newline at end of file
diff --git a/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings
new file mode 100644 (file)
index 0000000..dea12de
Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings differ
diff --git a/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib
new file mode 100644 (file)
index 0000000..69e866a
Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib differ
diff --git a/ucx/Makefile b/ucx/Makefile
new file mode 100644 (file)
index 0000000..3259fc8
--- /dev/null
@@ -0,0 +1,63 @@
+#
+# 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 += 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
+SRC += tree.c
+SRC += iterator.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 $<
+
diff --git a/ucx/allocator.c b/ucx/allocator.c
new file mode 100644 (file)
index 0000000..f5f37c9
--- /dev/null
@@ -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(
+        const CxAllocator *allocator,
+        size_t n
+) {
+    return allocator->cl->malloc(allocator->data, n);
+}
+
+void *cxRealloc(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t n
+) {
+    return allocator->cl->realloc(allocator->data, mem, n);
+}
+
+int cxReallocate(
+        const CxAllocator *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(
+        const CxAllocator *allocator,
+        size_t nelem,
+        size_t n
+) {
+    return allocator->cl->calloc(allocator->data, nelem, n);
+}
+
+void cxFree(
+        const CxAllocator *allocator,
+        void *mem
+) {
+    allocator->cl->free(allocator->data, mem);
+}
diff --git a/ucx/array_list.c b/ucx/array_list.c
new file mode 100644 (file)
index 0000000..2a0823b
--- /dev/null
@@ -0,0 +1,767 @@
+/*
+ * 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 "cx/compare.h"
+#include <assert.h>
+#include <string.h>
+
+// Default array reallocator
+
+static void *cx_array_default_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        __attribute__((__unused__)) struct cx_array_reallocator_s *alloc
+) {
+    return realloc(array, capacity * elem_size);
+}
+
+struct cx_array_reallocator_s cx_array_default_reallocator_impl = {
+        cx_array_default_realloc, NULL, NULL, 0, 0
+};
+
+struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+
+// LOW LEVEL ARRAY LIST FUNCTIONS
+
+enum cx_array_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        const void *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_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_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_SUCCESS;
+}
+
+enum cx_array_result cx_array_insert_sorted(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        cx_compare_func cmp_func,
+        const void *sorted_data,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) {
+    // assert pointers
+    assert(target != NULL);
+    assert(size != NULL);
+    assert(capacity != NULL);
+    assert(cmp_func != NULL);
+    assert(sorted_data != NULL);
+    assert(reallocator != NULL);
+
+    // corner case
+    if (elem_count == 0) return 0;
+
+    // store some counts
+    size_t old_size = *size;
+    size_t needed_capacity = old_size + elem_count;
+
+    // if we need more than we have, try a reallocation
+    if (needed_capacity > *capacity) {
+        size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16;
+        void *new_mem = reallocator->realloc(
+                *target, new_capacity, elem_size, reallocator
+        );
+        if (new_mem == NULL) {
+            // give it up right away, there is no contract
+            // that requires us to insert as much as we can
+            return CX_ARRAY_REALLOC_FAILED;
+        }
+        *target = new_mem;
+        *capacity = new_capacity;
+    }
+
+    // now we have guaranteed that we can insert everything
+    size_t new_size = old_size + elem_count;
+    *size = new_size;
+
+    // declare the source and destination indices/pointers
+    size_t si = 0, di = 0;
+    const char *src = sorted_data;
+    char *dest = *target;
+
+    // find the first insertion point
+    di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
+    dest += di * elem_size;
+
+    // move the remaining elements in the array completely to the right
+    // we will call it the "buffer" for parked elements
+    size_t buf_size = old_size - di;
+    size_t bi = new_size - buf_size;
+    char *bptr = ((char *) *target) + bi * elem_size;
+    memmove(bptr, dest, buf_size * elem_size);
+
+    // while there are both source and buffered elements left,
+    // copy them interleaving
+    while (si < elem_count && bi < new_size) {
+        // determine how many source elements can be inserted
+        size_t copy_len, bytes_copied;
+        copy_len = cx_array_binary_search_sup(
+                src,
+                elem_count - si,
+                elem_size,
+                bptr,
+                cmp_func
+        );
+
+        // copy the source elements
+        bytes_copied = copy_len * elem_size;
+        memcpy(dest, src, bytes_copied);
+        dest += bytes_copied;
+        src += bytes_copied;
+        si += copy_len;
+
+        // when all source elements are in place, we are done
+        if (si >= elem_count) break;
+
+        // determine how many buffered elements need to be restored
+        copy_len = cx_array_binary_search_sup(
+                bptr,
+                new_size - bi,
+                elem_size,
+                src,
+                cmp_func
+        );
+
+        // restore the buffered elements
+        bytes_copied = copy_len * elem_size;
+        memmove(dest, bptr, bytes_copied);
+        dest += bytes_copied;
+        bptr += bytes_copied;
+        bi += copy_len;
+    }
+
+    // still source elements left? simply append them
+    if (si < elem_count) {
+        memcpy(dest, src, elem_size * (elem_count - si));
+    }
+
+    // still buffer elements left?
+    // don't worry, we already moved them to the correct place
+
+    return CX_ARRAY_SUCCESS;
+}
+
+size_t cx_array_binary_search_inf(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+) {
+    // special case: empty array
+    if (size == 0) return 0;
+
+    // declare a variable that will contain the compare results
+    int result;
+
+    // cast the array pointer to something we can use offsets with
+    const char *array = arr;
+
+    // check the first array element
+    result = cmp_func(elem, array);
+    if (result < 0) {
+        return size;
+    } else if (result == 0) {
+        return 0;
+    }
+
+    // check the last array element
+    result = cmp_func(elem, array + elem_size * (size - 1));
+    if (result >= 0) {
+        return size - 1;
+    }
+
+    // the element is now guaranteed to be somewhere in the list
+    // so start the binary search
+    size_t left_index = 1;
+    size_t right_index = size - 1;
+    size_t pivot_index;
+
+    while (left_index <= right_index) {
+        pivot_index = left_index + (right_index - left_index) / 2;
+        const char *arr_elem = array + pivot_index * elem_size;
+        result = cmp_func(elem, arr_elem);
+        if (result == 0) {
+            // found it!
+            return pivot_index;
+        } else if (result < 0) {
+            // element is smaller than pivot, continue search left
+            right_index = pivot_index - 1;
+        } else {
+            // element is larger than pivot, continue search right
+            left_index = pivot_index + 1;
+        }
+    }
+
+    // report the largest upper bound
+    return result < 0 ? (pivot_index - 1) : pivot_index;
+}
+
+#ifndef CX_ARRAY_SWAP_SBO_SIZE
+#define CX_ARRAY_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
+
+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
+    const CxAllocator *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->collection.simple_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+    if (list->collection.advanced_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+
+    cxFree(list->collection.allocator, arl->data);
+    cxFree(list->collection.allocator, list);
+}
+
+static size_t cx_arl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    // out of bounds and special case check
+    if (index > list->collection.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->collection.size) {
+        const char *first_to_move = (const char *) arl->data;
+        first_to_move += index * list->collection.elem_size;
+        size_t elems_to_move = list->collection.size - index;
+        size_t start_of_moved = index + n;
+
+        if (CX_ARRAY_SUCCESS != cx_array_copy(
+                &arl->data,
+                &list->collection.size,
+                &arl->capacity,
+                start_of_moved,
+                first_to_move,
+                list->collection.elem_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_SUCCESS == cx_array_copy(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            index,
+            array,
+            list->collection.elem_size,
+            n,
+            &arl->reallocator
+    )) {
+        return n;
+    } else {
+        // array list implementation is "all or nothing"
+        return 0;
+    }
+}
+
+static size_t cx_arl_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    // get a correctly typed pointer to the list
+    cx_array_list *arl = (cx_array_list *) list;
+
+    if (CX_ARRAY_SUCCESS == cx_array_insert_sorted(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            list->collection.cmpfunc,
+            sorted_data,
+            list->collection.elem_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,
+        const void *element
+) {
+    return 1 != cx_arl_insert_array(list, index, element, 1);
+}
+
+static int cx_arl_insert_iter(
+        struct cx_iterator_s *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    if (iter->index < list->collection.size) {
+        int result = cx_arl_insert_element(
+                list,
+                iter->index + 1 - prepend,
+                elem
+        );
+        if (result == 0) {
+            iter->elem_count++;
+            if (prepend != 0) {
+                iter->index++;
+                iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
+            }
+        }
+        return result;
+    } else {
+        int result = cx_arl_insert_element(list, list->collection.size, elem);
+        if (result == 0) {
+            iter->elem_count++;
+            iter->index = list->collection.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->collection.size) {
+        return 1;
+    }
+
+    // content destruction
+    cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size);
+
+    // short-circuit removal of last element
+    if (index == list->collection.size - 1) {
+        list->collection.size--;
+        return 0;
+    }
+
+    // just move the elements starting at index to the left
+    int result = cx_array_copy(
+            &arl->data,
+            &list->collection.size,
+            &arl->capacity,
+            index,
+            ((char *) arl->data) + (index + 1) * list->collection.elem_size,
+            list->collection.elem_size,
+            list->collection.size - index - 1,
+            &arl->reallocator
+    );
+
+    // cx_array_copy cannot fail, array cannot grow
+    assert(result == 0);
+
+    // decrease the size
+    list->collection.size--;
+
+    return 0;
+}
+
+static void cx_arl_clear(struct cx_list_s *list) {
+    if (list->collection.size == 0) return;
+
+    cx_array_list *arl = (cx_array_list *) list;
+    char *ptr = arl->data;
+
+    if (list->collection.simple_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+    if (list->collection.advanced_destructor) {
+        for (size_t i = 0; i < list->collection.size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->collection.elem_size;
+        }
+    }
+
+    memset(arl->data, 0, list->collection.size * list->collection.elem_size);
+    list->collection.size = 0;
+}
+
+static int cx_arl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->collection.size || j >= list->collection.size) return 1;
+    cx_array_list *arl = (cx_array_list *) list;
+    cx_array_swap(arl->data, list->collection.elem_size, i, j);
+    return 0;
+}
+
+static void *cx_arl_at(
+        const struct cx_list_s *list,
+        size_t index
+) {
+    if (index < list->collection.size) {
+        const cx_array_list *arl = (const cx_array_list *) list;
+        char *space = arl->data;
+        return space + index * list->collection.elem_size;
+    } else {
+        return NULL;
+    }
+}
+
+static ssize_t cx_arl_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    assert(list->collection.cmpfunc != NULL);
+    assert(list->collection.size < SIZE_MAX / 2);
+    char *cur = ((const cx_array_list *) list)->data;
+
+    for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) {
+        if (0 == list->collection.cmpfunc(elem, cur)) {
+            if (remove) {
+                if (0 == cx_arl_remove(list, i)) {
+                    return i;
+                } else {
+                    return -1;
+                }
+            } else {
+                return i;
+            }
+        }
+        cur += list->collection.elem_size;
+    }
+
+    return -1;
+}
+
+static void cx_arl_sort(struct cx_list_s *list) {
+    assert(list->collection.cmpfunc != NULL);
+    qsort(((cx_array_list *) list)->data,
+          list->collection.size,
+          list->collection.elem_size,
+          list->collection.cmpfunc
+    );
+}
+
+static int cx_arl_compare(
+        const struct cx_list_s *list,
+        const struct cx_list_s *other
+) {
+    assert(list->collection.cmpfunc != NULL);
+    if (list->collection.size == other->collection.size) {
+        const char *left = ((const cx_array_list *) list)->data;
+        const char *right = ((const cx_array_list *) other)->data;
+        for (size_t i = 0; i < list->collection.size; i++) {
+            int d = list->collection.cmpfunc(left, right);
+            if (d != 0) {
+                return d;
+            }
+            left += list->collection.elem_size;
+            right += other->collection.elem_size;
+        }
+        return 0;
+    } else {
+        return list->collection.size < other->collection.size ? -1 : 1;
+    }
+}
+
+static void cx_arl_reverse(struct cx_list_s *list) {
+    if (list->collection.size < 2) return;
+    void *data = ((const cx_array_list *) list)->data;
+    size_t half = list->collection.size / 2;
+    for (size_t i = 0; i < half; i++) {
+        cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i);
+    }
+}
+
+static bool cx_arl_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    const struct cx_list_s *list = iter->src_handle.c;
+    return iter->index < list->collection.size;
+}
+
+static void *cx_arl_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_arl_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        cx_arl_remove(iter->src_handle.m, iter->index);
+    } else {
+        iter->index++;
+        iter->elem_handle =
+                ((char *) iter->elem_handle)
+                + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
+    }
+}
+
+static void cx_arl_iter_prev(void *it) {
+    struct cx_iterator_s *iter = it;
+    const cx_array_list *list = iter->src_handle.c;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        cx_arl_remove(iter->src_handle.m, iter->index);
+    }
+    iter->index--;
+    if (iter->index < list->base.collection.size) {
+        iter->elem_handle = ((char *) list->data)
+                            + iter->index * list->base.collection.elem_size;
+    }
+}
+
+
+static struct cx_iterator_s cx_arl_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter;
+
+    iter.index = index;
+    iter.src_handle.c = list;
+    iter.elem_handle = cx_arl_at(list, index);
+    iter.elem_size = list->collection.elem_size;
+    iter.elem_count = list->collection.size;
+    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.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_sorted,
+        cx_arl_insert_iter,
+        cx_arl_remove,
+        cx_arl_clear,
+        cx_arl_swap,
+        cx_arl_at,
+        cx_arl_find_remove,
+        cx_arl_sort,
+        cx_arl_compare,
+        cx_arl_reverse,
+        cx_arl_iterator,
+};
+
+CxList *cxArrayListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_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.collection.allocator = allocator;
+    list->capacity = initial_capacity;
+
+    if (elem_size > 0) {
+        list->base.collection.elem_size = elem_size;
+        list->base.collection.cmpfunc = comparator;
+    } else {
+        elem_size = sizeof(void *);
+        list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+        cxListStorePointers((CxList *) list);
+    }
+
+    // allocate the array after the real elem_size is known
+    list->data = cxCalloc(allocator, initial_capacity, elem_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;
+}
diff --git a/ucx/buffer.c b/ucx/buffer.c
new file mode 100644 (file)
index 0000000..f03e513
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * 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,
+        const CxAllocator *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,
+        const CxAllocator *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;
+}
+
+void cxBufferReset(CxBuffer *buffer) {
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
+int cxBufferEof(const CxBuffer *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,
+        const unsigned char *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(
+        const void *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
+                const unsigned char *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;
+    }
+}
diff --git a/ucx/compare.c b/ucx/compare.c
new file mode 100644 (file)
index 0000000..8d2e4f0
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *f1, const void *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(
+        const void *d1,
+        const void *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(
+        const void *ptr1,
+        const void *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(
+        const void *ptr1,
+        const void *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;
+    }
+}
+
+int cx_cmp_ptr(
+        const void *ptr1,
+        const void *ptr2
+) {
+    uintptr_t p1 = (uintptr_t) ptr1;
+    uintptr_t p2 = (uintptr_t) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h
new file mode 100644 (file)
index 0000000..aef3e18
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * 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.
+     */
+    __attribute__((__warn_unused_result__))
+    void *(*realloc)(
+            void *data,
+            void *mem,
+            size_t n
+    );
+
+    /**
+     * The allocator's calloc() implementation.
+     */
+    void *(*calloc)(
+            void *data,
+            size_t nelem,
+            size_t n
+    );
+
+    /**
+     * The allocator's free() implementation.
+     */
+    __attribute__((__nonnull__))
+    void (*free)(
+            void *data,
+            void *mem
+    );
+} 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
+  */
+__attribute__((__nonnull__))
+typedef void (*cx_destructor_func)(void *memory);
+
+/**
+ * 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
+  */
+__attribute__((__nonnull__(2)))
+typedef void (*cx_destructor_func2)(
+        void *data,
+        void *memory
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+int cx_reallocate(
+        void **mem,
+        size_t n
+);
+
+/**
+ * Allocate \p n bytes of memory.
+ *
+ * @param allocator the allocator
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2)))
+void *cxMalloc(
+        const CxAllocator *allocator,
+        size_t n
+);
+
+/**
+ * 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
+ */
+__attribute__((__warn_unused_result__))
+__attribute__((__alloc_size__(3)))
+void *cxRealloc(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t n
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+int cxReallocate(
+        const CxAllocator *allocator,
+        void **mem,
+        size_t n
+);
+
+/**
+ * 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
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2, 3)))
+void *cxCalloc(
+        const CxAllocator *allocator,
+        size_t nelem,
+        size_t n
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+void cxFree(
+        const CxAllocator *allocator,
+        void *mem
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ALLOCATOR_H
diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h
new file mode 100644 (file)
index 0000000..89cbacf
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * 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
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_ARRAY_LIST_H
+#define UCX_ARRAY_LIST_H
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum item size in an array list that fits into stack buffer when swapped.
+ */
+extern unsigned cx_array_swap_sbo_size;
+
+/**
+ * Declares variables for an array that can be used with the convenience macros.
+ *
+ * @see cx_array_simple_add()
+ * @see cx_array_simple_copy()
+ * @see cx_array_initialize()
+ * @see cx_array_simple_add_sorted()
+ * @see cx_array_simple_insert_sorted()
+ */
+#define CX_ARRAY_DECLARE(type, name) \
+    type * name;                     \
+    size_t name##_size;              \
+    size_t name##_capacity
+
+/**
+ * Initializes an array declared with CX_ARRAY_DECLARE().
+ *
+ * The memory for the array is allocated with stdlib malloc().
+ * @param array the array
+ * @param capacity the initial capacity
+ */
+#define cx_array_initialize(array, capacity) \
+        array##_capacity = capacity; \
+        array##_size = 0; \
+        array = malloc(sizeof(array[0]) * capacity)
+
+/**
+ * Defines a reallocation mechanism for arrays.
+ */
+struct cx_array_reallocator_s {
+    /**
+     * Reallocates space for the given array.
+     *
+     * Implementations are not required to free the original array.
+     * This allows reallocation of static memory by allocating heap memory
+     * and copying the array contents. The information in the custom fields of
+     * the referenced allocator can be used to track the state of the memory
+     * or to transport other additional data.
+     *
+     * @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;
+};
+
+/**
+ * A default stdlib-based array reallocator.
+ */
+extern struct cx_array_reallocator_s *cx_array_default_reallocator;
+
+/**
+ * Return codes for array functions.
+ */
+enum cx_array_result {
+    CX_ARRAY_SUCCESS,
+    CX_ARRAY_REALLOC_NOT_SUPPORTED,
+    CX_ARRAY_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 \p reallocator is set to \c NULL, in which case
+ * this function ultimately returns a failure.
+ *
+ * @param target a pointer to 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 (reallocations
+ * will NOT be supported in that case)
+ * @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 reallocator to use, or \c NULL
+ * if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__(1, 2, 5)))
+enum cx_array_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        const void *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param count the number of elements to copy
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_copy(array, index, src, count) \
+    cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
+    index, src, sizeof((array)[0]), count, cx_array_default_reallocator)
+
+/**
+ * Adds an element to an array with the possibility of allocating more space.
+ *
+ * The element \p elem is added to the end of the \p target array which containing
+ * \p size elements, already. The \p capacity must not be \c NULL and point a
+ * variable holding the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, and the optional
+ * \p reallocator is not \c NULL, an attempt increase the \p capacity is made
+ * and the new capacity is written back.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity - must not be \c NULL
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
+    cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro that uses cx_array_add() with a default layout and
+ * the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add(array, elem) \
+    cx_array_simple_copy(array, array##_size, &(elem), 1)
+
+
+/**
+ * Inserts a sorted array into another sorted array.
+ *
+ * If either the target or the source array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param cmp_func the compare function for the elements
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to insert
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__))
+enum cx_array_result cx_array_insert_sorted(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        cx_compare_func cmp_func,
+        const void *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Inserts an element into a sorted array.
+ *
+ * If the target array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
+    cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro for cx_array_add_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add_sorted(array, elem, cmp_func) \
+    cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
+        sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator)
+
+/**
+ * Convenience macro for cx_array_insert_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param src pointer to the source array
+ * @param n number of elements in the source array
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
+    cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
+        cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator)
+
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * in \p arr that is less or equal to \p elem with respect to \p cmp_func.
+ * When no such element exists, \p size is returned.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the largest lower bound, or \p size
+ */
+__attribute__((__nonnull__))
+size_t cx_array_binary_search_inf(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+);
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the element in the array, or \p size if the element
+ * cannot be found
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+) {
+    size_t index = cx_array_binary_search_inf(
+            arr, size, elem_size, elem, cmp_func
+    );
+    if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
+        return index;
+    } else {
+        return size;
+    }
+}
+
+/**
+ * Searches the smallest upper bound in a sorted array.
+ *
+ * In other words, this function returns the index of the smallest element
+ * in \p arr that is greater or equal to \p elem with respect to \p cmp_func.
+ * When no such element exists, \p size is returned.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the smallest upper bound, or \p size
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search_sup(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+) {
+    size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func);
+    if (inf == size) {
+        // no infimum means, first element is supremum
+        return 0;
+    } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
+        return inf;
+    } else {
+        return inf + 1;
+    }
+}
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @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, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_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(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size,
+        size_t initial_capacity
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_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 elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_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(elem_size, initial_capacity) \
+    cxArrayListCreate(NULL, NULL, elem_size, initial_capacity)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ARRAY_LIST_H
diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h
new file mode 100644 (file)
index 0000000..3dbbc87
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * 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
+ * \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. */
+    const CxAllocator *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,
+        const CxAllocator *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,
+        const CxAllocator *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().
+ * If you do not need that, you can use the faster cxBufferReset().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferReset()
+ */
+__attribute__((__nonnull__))
+void cxBufferClear(CxBuffer *buffer);
+
+/**
+ * Resets the buffer by resetting the position and size to zero.
+ *
+ * The data in the buffer is not deleted. If you need a safe
+ * reset of the buffer, use cxBufferClear().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferClear()
+ */
+__attribute__((__nonnull__))
+void cxBufferReset(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer size.
+ *
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * byte of the buffer's contents.
+ */
+__attribute__((__nonnull__))
+int cxBufferEof(const CxBuffer *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(
+        const void *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
diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h
new file mode 100644 (file)
index 0000000..7c8ae4b
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * 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
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COLLECTION_H
+#define UCX_COLLECTION_H
+
+#include "allocator.h"
+#include "iterator.h"
+#include "compare.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Special constant used for creating collections that are storing pointers.
+ */
+#define CX_STORE_POINTERS 0
+
+/**
+ * Base attributes of a collection.
+ */
+struct cx_collection_s {
+    /**
+     * The allocator to use.
+     */
+    const CxAllocator *allocator;
+    /**
+     * The comparator function for the elements.
+     */
+    cx_compare_func cmpfunc;
+    /**
+     * The size of each element.
+     */
+    size_t elem_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 list is supposed to store pointers
+     * instead of copies of the actual objects.
+     */
+    bool store_pointer;
+};
+
+/**
+ * Use this macro to declare common members for a collection structure.
+ */
+#define CX_COLLECTION_BASE struct cx_collection_s collection
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineDestructor(c, destr) \
+    (c)->collection.simple_destructor = (cx_destructor_func) destr
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineAdvancedDestructor(c, destr, data) \
+    (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \
+    (c)->collection.destructor_data = data
+
+/**
+ * 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)->collection.simple_destructor((c)->collection.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)->collection.advanced_destructor((c)->collection.destructor_data, \
+    (c)->collection.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)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
+    if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_COLLECTION_H
diff --git a/ucx/cx/common.h b/ucx/cx/common.h
new file mode 100644 (file)
index 0000000..754dfc6
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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
+ * \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   1
+
+/** 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>
+
+#ifndef UCX_TEST_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        const void *,
+        size_t,
+        size_t,
+        void *
+);
+#endif // UCX_TEST_H
+
+/**
+ * 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
diff --git a/ucx/cx/common.h.orig b/ucx/cx/common.h.orig
new file mode 100644 (file)
index 0000000..e6ce33b
--- /dev/null
@@ -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
diff --git a/ucx/cx/compare.h b/ucx/cx/compare.h
new file mode 100644 (file)
index 0000000..652bb0e
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COMPARE_H
+#define UCX_COMPARE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef CX_COMPARE_FUNC_DEFINED
+#define CX_COMPARE_FUNC_DEFINED
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+        const void *left,
+        const void *right
+);
+#endif // CX_COMPARE_FUNC_DEFINED
+
+/**
+ * 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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *i1, const void *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(const void *f1, const void *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(
+        const void *d1,
+        const void *d2
+);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const intptr_t*)
+ * @param ptr2 pointer to pointer two (const intptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_intptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const uintptr_t*)
+ * @param ptr2 pointer to pointer two (const uintptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_uintptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+/**
+ * Compares the pointers specified in the arguments without de-referencing.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int cx_cmp_ptr(
+        const void *ptr1,
+        const void *ptr2
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_COMPARE_H
diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h
new file mode 100644 (file)
index 0000000..9c48342
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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
+ * \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. */
+    const void *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(const char *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(
+        const unsigned char *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(
+        const void *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
diff --git a/ucx/cx/hash_map.h b/ucx/cx/hash_map.h
new file mode 100644 (file)
index 0000000..c1cfc9f
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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
+ * \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 elem_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(
+        const CxAllocator *allocator,
+        size_t itemsize,
+        size_t buckets
+);
+
+/**
+ * Creates a new hash map with a default number of buckets.
+ *
+ * If \p elem_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
diff --git a/ucx/cx/iterator.h b/ucx/cx/iterator.h
new file mode 100644 (file)
index 0000000..c9fb73e
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * 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
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_ITERATOR_H
+#define UCX_ITERATOR_H
+
+#include "common.h"
+
+struct cx_iterator_base_s {
+    /**
+     * True iff the iterator points to valid data.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*valid)(const void *);
+
+    /**
+     * Returns a pointer to the current element.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current)(const void *);
+
+    /**
+     * Original implementation in case the function needs to be wrapped.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current_impl)(const void *);
+
+    /**
+     * Advances the iterator.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void (*next)(void *);
+    /**
+     * Indicates whether this iterator may remove elements.
+     */
+    bool mutating;
+    /**
+     * Internal flag for removing the current element when advancing.
+     */
+    bool remove;
+};
+
+/**
+ * Declares base attributes for an iterator.
+ * Must be the first member of an iterator structure.
+ */
+#define CX_ITERATOR_BASE struct cx_iterator_base_s base
+
+/**
+ * Internal iterator struct - use CxIterator.
+ */
+struct cx_iterator_s {
+    CX_ITERATOR_BASE;
+
+    /**
+     * Handle for the current element.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    union {
+        /**
+         * Access for mutating iterators.
+         */
+        void *m;
+        /**
+         * Access for normal iterators.
+         */
+        const void *c;
+    } 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.
+         */
+        const void *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;
+
+    /**
+     * The size of an individual element.
+     */
+    size_t elem_size;
+
+    /**
+     * May contain the total number of elements, if known.
+     * Shall be set to \c SIZE_MAX when the total number is unknown during iteration.
+     */
+    size_t elem_count;
+};
+
+/**
+ * Iterator 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,
+ * any concurrent mutation of the collection other than by this iterator makes this iterator invalid
+ * and it must not be used anymore.
+ */
+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, if this iterator is mutating.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
+
+/**
+ * Obtains a reference to an arbitrary iterator.
+ *
+ * This is useful for APIs that expect some iterator as an argument.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorRef(iter) &((iter).base)
+
+/**
+ * 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))
+
+
+/**
+ * Creates an iterator for the specified plain array.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxIterator(
+        const void *array,
+        size_t elem_size,
+        size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain array.
+ *
+ * While the iterator is in use, the array may only be altered by removing
+ * elements through #cxIteratorFlagRemoval(). Every other change to the array
+ * will bring this iterator to an undefined state.
+ *
+ * When \p remove_keeps_order is set to \c false, removing an element will only
+ * move the last element to the position of the removed element, instead of
+ * moving all subsequent elements by one. Usually, when the order of elements is
+ * not important, this parameter should be set to \c false.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order \c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxMutIterator(
+        void *array,
+        size_t elem_size,
+        size_t elem_count,
+        bool remove_keeps_order
+);
+
+#endif // UCX_ITERATOR_H
diff --git a/ucx/cx/linked_list.h b/ucx/cx/linked_list.h
new file mode 100644 (file)
index 0000000..23b37a7
--- /dev/null
@@ -0,0 +1,506 @@
+/*
+ * 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
+ * \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
+
+/**
+ * The maximum item size that uses SBO swap instead of relinking.
+ */
+extern unsigned cx_linked_list_swap_sbo_size;
+
+/**
+ * Allocates a linked list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @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, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+CxList *cxLinkedListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_size
+);
+
+/**
+ * Allocates a linked list for storing elements with \p elem_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 elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+#define cxLinkedListCreateSimple(elem_size) \
+    cxLinkedListCreate(NULL, NULL, elem_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
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_at(
+        const void *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find(
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+);
+
+/**
+ * Finds the node containing an element within a linked list.
+ *
+ * @param result a pointer to the memory where the node pointer (or \c NULL if the element
+ * could not be found) shall be stored to
+ * @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
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find_node(
+        void **result,
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_first(
+        const void *node,
+        ptrdiff_t loc_prev
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_last(
+        const void *node,
+        ptrdiff_t loc_next
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_prev(
+        const void *begin,
+        ptrdiff_t loc_next,
+        const void *node
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+);
+
+/**
+ * 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)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * 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)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * 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 inserted
+ */
+__attribute__((__nonnull__(6)))
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+);
+
+/**
+ * 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)
+ */
+__attribute__((__nonnull__(6)))
+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
+);
+
+/**
+ * Inserts a node into a sorted linked list.
+ * The new node must not be part of any list already.
+ *
+ * If the list starting with the node pointed to by \p begin is not sorted
+ * already, the behavior is undefined.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @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 inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node,
+        cx_compare_func cmp_func
+);
+
+/**
+ * Inserts a chain of nodes into a sorted linked list.
+ * The chain must not be part of any list already.
+ *
+ * If either the list starting with the node pointed to by \p begin or the list
+ * starting with \p insert_begin is not sorted, the behavior is undefined.
+ *
+ * \attention In contrast to cx_linked_list_insert_chain(), the source chain
+ * will be broken and inserted into the target list so that the resulting list
+ * will be sorted according to \p cmp_func. That means, each node in the source
+ * chain may be re-linked with nodes from the target list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @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 insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *insert_begin,
+        cx_compare_func cmp_func
+);
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+);
+
+
+/**
+ * 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(
+        const void *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
+ */
+__attribute__((__nonnull__(1, 6)))
+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
+);
+
+
+/**
+ * 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.
+ */
+__attribute__((__nonnull__(5)))
+int cx_linked_list_compare(
+        const void *begin_left,
+        const void *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+);
+
+/**
+ * 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)
+ */
+__attribute__((__nonnull__(1)))
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LINKED_LIST_H
diff --git a/ucx/cx/list.h b/ucx/cx/list.h
new file mode 100644 (file)
index 0000000..4807624
--- /dev/null
@@ -0,0 +1,800 @@
+/*
+ * 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
+ * \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_BASE;
+    /**
+     * The list class definition.
+     */
+    const cx_list_class *cl;
+    /**
+     * The actual implementation in case the list class is delegating.
+     */
+    const cx_list_class *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.
+     */
+    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,
+            const void *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     * @see cx_list_default_insert_array()
+     */
+    size_t (*insert_array)(
+            struct cx_list_s *list,
+            size_t index,
+            const void *data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting sorted elements into a sorted list.
+     *
+     * @see cx_list_default_insert_sorted()
+     */
+    size_t (*insert_sorted)(
+            struct cx_list_s *list,
+            const void *sorted_data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting an element relative to an iterator position.
+     */
+    int (*insert_iter)(
+            struct cx_iterator_s *iter,
+            const void *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.
+     * @see cx_list_default_swap()
+     */
+    int (*swap)(
+            struct cx_list_s *list,
+            size_t i,
+            size_t j
+    );
+
+    /**
+     * Member function for element lookup.
+     */
+    void *(*at)(
+            const struct cx_list_s *list,
+            size_t index
+    );
+
+    /**
+     * Member function for finding and optionally removing an element.
+     */
+    ssize_t (*find_remove)(
+            struct cx_list_s *list,
+            const void *elem,
+            bool remove
+    );
+
+    /**
+     * Member function for sorting the list in-place.
+     * @see cx_list_default_sort()
+     */
+    void (*sort)(struct cx_list_s *list);
+
+    /**
+     * Optional member function for comparing this list
+     * to another list of the same type.
+     * If set to \c NULL, comparison won't be optimized.
+     */
+    int (*compare)(
+            const struct cx_list_s *list,
+            const struct cx_list_s *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)(
+            const struct cx_list_s *list,
+            size_t index,
+            bool backward
+    );
+};
+
+/**
+ * Default implementation of an array insert.
+ *
+ * This function uses the element insert function for each element of the array.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param index the index where to insert the data
+ * @param data a pointer to the array of data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *data,
+        size_t n
+);
+
+/**
+ * Default implementation of a sorted insert.
+ *
+ * This function uses the array insert function to insert consecutive groups
+ * of sorted data.
+ *
+ * The source data \em must already be sorted wrt. the list's compare function.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param sorted_data a pointer to the array of pre-sorted data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+);
+
+/**
+ * Default unoptimized sort implementation.
+ *
+ * This function will copy all data to an array, sort the array with standard
+ * qsort, and then copy the data back to the list memory.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list that shall be sorted
+ */
+__attribute__((__nonnull__))
+void cx_list_default_sort(struct cx_list_s *list);
+
+/**
+ * Default unoptimized swap implementation.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list in which to swap
+ * @param i index of one element
+ * @param j index of the other element
+ * @return zero on success, non-zero when indices are out of bounds or memory
+ * allocation for the temporary buffer fails
+ */
+__attribute__((__nonnull__))
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+
+/**
+ * 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(const CxList *list) {
+    return list->collection.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(const CxList *list) {
+    return list->collection.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,
+        const void *elem
+) {
+    return list->cl->insert_element(list, list->collection.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,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, list->collection.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,
+        const void *elem
+) {
+    return list->cl->insert_element(list, index, elem);
+}
+
+/**
+ * Inserts an item into a sorted list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertSorted(
+        CxList *list,
+        const void *elem
+) {
+    const void *data = list->collection.store_pointer ? &elem : elem;
+    return list->cl->insert_sorted(list, data, 1) == 0;
+}
+
+/**
+ * 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,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, index, array, n);
+}
+
+/**
+ * Inserts a sorted array into a sorted list.
+ *
+ * This method is usually more efficient than inserting each element separately,
+ * because consecutive chunks of sorted data are inserted in one pass.
+ *
+ * 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 cxListInsertSortedArray(
+        CxList *list,
+        const void *array,
+        size_t n
+) {
+    return list->cl->insert_sorted(list, 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(
+        CxIterator *iter,
+        const void *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle.m)->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(
+        CxIterator *iter,
+        const void *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle.m)->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(
+        const CxList *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(
+        const CxList *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__))
+CxIterator 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__))
+CxIterator 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(const CxList *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 CxIterator 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(const CxList *list) {
+    return list->cl->iterator(list, list->collection.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 CxIterator cxListMutBackwardsIterator(CxList *list) {
+    return cxListMutBackwardsIteratorAt(list, list->collection.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(
+        const CxList *list,
+        const void *elem
+) {
+    return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+/**
+ * Removes and 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 and remove
+ * @return the index of the now removed element or a negative
+ * value when the element is not found or could not be removed
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFindRemove(
+        CxList *list,
+        const void *elem
+) {
+    return list->cl->find_remove(list, elem, true);
+}
+
+/**
+ * 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(
+        const CxList *list,
+        const CxList *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
diff --git a/ucx/cx/map.h b/ucx/cx/map.h
new file mode 100644 (file)
index 0000000..2fde575
--- /dev/null
@@ -0,0 +1,1052 @@
+/*
+ * 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
+ * \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 {
+    /**
+     * Base attributes.
+     */
+    CX_COLLECTION_BASE;
+    /** 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)(
+            const CxMap *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)(const CxMap *map, enum cx_map_iterator_type type);
+};
+
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+    /**
+     * A pointer to the key.
+     */
+    const CxHashKey *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->collection.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->collection.store_pointer = true;
+    map->collection.elem_size = sizeof(void *);
+}
+
+/**
+ * Returns true, if this map is storing pointers instead of the actual data.
+ *
+ * @param map
+ * @return true, if this map is storing pointers
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxMapIsStoringPointers(const CxMap *map) {
+    return map->collection.store_pointer;
+}
+
+/**
+ * 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);
+}
+
+/**
+ * Returns the number of elements in this map.
+ *
+ * @param map the map
+ * @return the number of stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxMapSize(const CxMap *map) {
+    return map->collection.size;
+}
+
+
+// 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(const CxMap *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(const CxMap *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(const CxMap *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__))
+CxIterator 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__))
+CxIterator 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__))
+CxIterator 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,
+        const char *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(
+        const CxMap *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(
+        const CxMap *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(
+        const CxMap *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(
+        const CxMap *map,
+        const char *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,
+        const char *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,
+        const char *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+
+
+#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,
+        const char *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,                        \
+    const char*: 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(
+        const CxMap *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(
+        const CxMap *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(
+        const CxMap *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(
+        const CxMap *map,
+        const char *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,                 \
+    const char*: 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,
+        const char *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,                 \
+    const char*: 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,
+        const char *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,                 \
+    const char*: 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->collection.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->collection.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->collection.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,
+        const char *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->collection.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,               \
+    const char*: cx_map_remove_and_get_str)         \
+    (map, key)
+
+#endif // __cplusplus
+
+#endif // UCX_MAP_H
diff --git a/ucx/cx/mempool.h b/ucx/cx/mempool.h
new file mode 100644 (file)
index 0000000..6214d8a
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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
+ * \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. */
+    const CxAllocator *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
diff --git a/ucx/cx/printf.h b/ucx/cx/printf.h
new file mode 100644 (file)
index 0000000..fad4a7e
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * 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
+ * \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
+
+
+/**
+ * The maximum string length that fits into stack memory.
+ */
+extern unsigned const cx_printf_sbo_size;
+
+/**
+ * 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,
+        const char *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,
+        const char *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(
+        const CxAllocator *allocator,
+        const char *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(
+        const CxAllocator *allocator,
+        const char *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__)
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5)))
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... );
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap);
+
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * 
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... );
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_PRINTF_H
diff --git a/ucx/cx/string.h b/ucx/cx/string.h
new file mode 100644 (file)
index 0000000..a55e6a7
--- /dev/null
@@ -0,0 +1,1082 @@
+/*
+ * 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
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STRING_H
+#define UCX_STRING_H
+
+#include "common.h"
+#include "allocator.h"
+
+/**
+ * The maximum length of the "needle" in cx_strstr() that can use SBO.
+ */
+extern unsigned const cx_strstr_sbo_size;
+
+/**
+ * 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.
+     */
+    const char *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.
+     */
+    const cxstring *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(const 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 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(
+        const char *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 <code>const char*</code> 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 <code>const char*</code> 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(
+        const CxAllocator *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(
+        const CxAllocator *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(
+        const CxAllocator *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(
+        const CxAllocator *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(
+        const void *s1,
+        const void *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(
+        const void *s1,
+        const void *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(
+        const CxAllocator *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(
+        const CxAllocator *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,
+        const cxstring *delim,
+        size_t count
+);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_STRING_H
diff --git a/ucx/cx/test.h b/ucx/cx/test.h
new file mode 100644 (file)
index 0000000..62f11ad
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * 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: test.h
+ * 
+ * UCX Test Framework.
+ * 
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with CX_TEST_ASSERT()
+ * }
+ * 
+ * CX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #CX_TEST_DO {
+ *     // tests with CX_TEST_ASSERT() and/or
+ *     // calls with CX_TEST_CALL_SUBROUTINE() here
+ *   }
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ * 
+ * @attention Do not call own functions within a test, that use
+ * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define        UCX_TEST_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+//
+#if !defined(__clang__) && __GNUC__ > 3
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+
+#ifndef UCX_COMMON_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        const void *,
+        size_t,
+        size_t,
+        void *
+);
+#endif // UCX_COMMON_H
+
+/** Type for the CxTestSuite. */
+typedef struct CxTestSuite CxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func);
+
+/** Type for the internal list of test cases. */
+typedef struct CxTestSet CxTestSet;
+
+/** Structure for the internal list of test cases. */
+struct CxTestSet {
+    
+    /** Test case. */
+    CxTest test;
+    
+    /** Pointer to the next list element. */
+    CxTestSet *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct CxTestSuite {
+    
+    /** The number of successful tests after the suite has been run. */
+    unsigned int success;
+    
+    /** The number of failed tests after the suite has been run. */
+    unsigned int failure;
+
+    /** The optional name of this test suite. */
+    const char *name;
+    
+    /**
+     * Internal list of test cases.
+     * Use cx_test_register() to add tests to this list.
+     */
+    CxTestSet *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @param name optional name of the suite
+ * @return a new test suite
+ */
+static inline CxTestSuite* cx_test_suite_new(const char *name) {
+    CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
+    if (suite != NULL) {
+        suite->name = name;
+        suite->success = 0;
+        suite->failure = 0;
+        suite->tests = NULL;
+    }
+
+    return suite;
+}
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+static inline void cx_test_suite_free(CxTestSuite* suite) {
+    CxTestSet *l = suite->tests;
+    while (l != NULL) {
+        CxTestSet *e = l;
+        l = l->next;
+        free(e);
+    }
+    free(suite);
+}
+
+/**
+ * Registers a test function with the specified test suite.
+ * 
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return zero on success or non-zero on failure
+ */
+static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+    CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
+    if (t) {
+        t->test = test;
+        t->next = NULL;
+        if (suite->tests == NULL) {
+            suite->tests = t;
+        } else {
+            CxTestSet *last = suite->tests;
+            while (last->next) {
+                last = last->next;
+            }
+            last->next = t;
+        }
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param out_target the target buffer or file to write the output to
+ * @param out_writer the write function writing to \p out_target
+ */
+static inline void cx_test_run(CxTestSuite *suite,
+                               void *out_target, cx_write_func out_writer) {
+    if (suite->name == NULL) {
+        out_writer("*** Test Suite ***\n", 1, 19, out_target);
+    } else {
+        out_writer("*** Test Suite : ", 1, 17, out_target);
+        out_writer(suite->name, 1, strlen(suite->name), out_target);
+        out_writer(" ***\n", 1, 5, out_target);
+    }
+    suite->success = 0;
+    suite->failure = 0;
+    for (CxTestSet *elem = suite->tests; elem; elem = elem->next) {
+        elem->test(suite, out_target, out_writer);
+    }
+    out_writer("\nAll test completed.\n", 1, 21, out_target);
+    char total[80];
+    int len = snprintf(
+            total, 80,
+            "  Total:   %u\n  Success: %u\n  Failure: %u\n\n",
+            suite->success + suite->failure, suite->success, suite->failure
+    );
+    out_writer(total, 1, len, out_target);
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified FILE stream.
+ * @param suite the test suite to run
+ * @param file the target file to write the output to
+ */
+#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
+
+/**
+ * Runs a test suite and writes the test log to stdout.
+ * @param suite the test suite to run
+ */
+#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
+
+/**
+ * Macro for a #CxTest function header.
+ * 
+ * Use this macro to declare and/or define a #CxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_)
+
+/**
+ * Defines the scope of a test.
+ * @attention Any CX_TEST_ASSERT() calls must be performed in scope of
+ * #CX_TEST_DO.
+ */
+#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
+        _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        _writefnc_("... ", 1, 4, _output_);\
+        jmp_buf _env_;\
+        for (unsigned int _cx_test_loop_ = 0 ;\
+             _cx_test_loop_ == 0 && !setjmp(_env_);\
+             _writefnc_("success.\n", 1, 9, _output_),\
+             _suite_->success++, _cx_test_loop_++)
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
+        const char *_assert_msg_ = message; \
+        _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \
+        _writefnc_(".\n", 1, 2, _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    } (void) 0
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ */
+#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * CX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see CX_TEST_CALL_SUBROUTINE()
+ */
+#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\
+        void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see CX_TEST_SUBROUTINE()
+ */
+#define CX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_TEST_H */
+
diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h
new file mode 100644 (file)
index 0000000..7abb6fc
--- /dev/null
@@ -0,0 +1,1254 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 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 tree.h
+ * \brief Interface for tree implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_TREE_H
+#define UCX_TREE_H
+
+#include "common.h"
+
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A depth-first tree iterator.
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_iterator_s {
+    /**
+     * Base members.
+     */
+    CX_ITERATOR_BASE;
+    /**
+     * Indicates whether the subtree below the current node shall be skipped.
+     */
+    bool skip;
+    /**
+     * Set to true, when the iterator shall visit a node again
+     * when all it's children have been processed.
+     */
+    bool visit_on_exit;
+    /**
+     * True, if this iterator is currently leaving the node.
+     */
+    bool exiting;
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+    /**
+     * Offset in the node struct for the next pointer.
+     */
+    ptrdiff_t loc_next;
+    /**
+     * The total number of distinct nodes that have been passed so far.
+     */
+    size_t counter;
+    /**
+     * The currently observed node.
+     *
+     * This is the same what cxIteratorCurrent() would return.
+     */
+    void *node;
+    /**
+     * Stores a copy of the next pointer of the visited node.
+     * Allows freeing a node on exit without corrupting the iteration.
+     */
+    void *node_next;
+    /**
+     * Internal stack.
+     * Will be automatically freed once the iterator becomes invalid.
+     *
+     * If you want to discard the iterator before, you need to manually
+     * call cxTreeIteratorDispose().
+     */
+    void **stack;
+    /**
+     * Internal capacity of the stack.
+     */
+    size_t stack_capacity;
+    union {
+        /**
+         * Internal stack size.
+         */
+        size_t stack_size;
+        /**
+         * The current depth in the tree.
+         */
+        size_t depth;
+    };
+} CxTreeIterator;
+
+/**
+ * An element in a visitor queue.
+ */
+struct cx_tree_visitor_queue_s {
+    /**
+     * The tree node to visit.
+     */
+    void *node;
+    /**
+     * The depth of the node.
+     */
+    size_t depth;
+    /**
+     * The next element in the queue or \c NULL.
+     */
+    struct cx_tree_visitor_queue_s *next;
+};
+
+/**
+ * A breadth-first tree iterator.
+ *
+ * This iterator needs to maintain a visitor queue that will be automatically
+ * freed once the iterator becomes invalid.
+ * If you want to discard the iterator before, you MUST manually call
+ * cxTreeVisitorDispose().
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_visitor_s {
+    /**
+     * Base members.
+     */
+    CX_ITERATOR_BASE;
+    /**
+     * Indicates whether the subtree below the current node shall be skipped.
+     */
+    bool skip;
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+    /**
+     * Offset in the node struct for the next pointer.
+     */
+    ptrdiff_t loc_next;
+    /**
+     * The total number of distinct nodes that have been passed so far.
+     */
+    size_t counter;
+    /**
+     * The currently observed node.
+     *
+     * This is the same what cxIteratorCurrent() would return.
+     */
+    void *node;
+    /**
+     * The current depth in the tree.
+     */
+    size_t depth;
+    /**
+     * The next element in the visitor queue.
+     */
+    struct cx_tree_visitor_queue_s *queue_next;
+    /**
+     * The last element in the visitor queue.
+     */
+    struct cx_tree_visitor_queue_s *queue_last;
+} CxTreeVisitor;
+
+/**
+ * Releases internal memory of the given tree iterator.
+ * @param iter the iterator
+ */
+ __attribute__((__nonnull__))
+static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
+    free(iter->stack);
+    iter->stack = NULL;
+}
+
+/**
+ * Releases internal memory of the given tree visitor.
+ * @param visitor the visitor
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
+    struct cx_tree_visitor_queue_s *q = visitor->queue_next;
+    while (q != NULL) {
+        struct cx_tree_visitor_queue_s *next = q->next;
+        free(q);
+        q = next;
+    }
+}
+
+/**
+ * Advises the iterator to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param iterator the iterator
+ */
+#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue
+
+/**
+ * Advises the visitor to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param visitor the visitor
+ */
+#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor)
+
+/**
+ * Links a node to a (new) parent.
+ *
+ * If the node has already a parent, it is unlinked, first.
+ * If the parent has children already, the node is \em appended to the list
+ * of all currently existing children.
+ *
+ * @param parent the parent node
+ * @param node the node that shall be linked
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_unlink()
+ */
+__attribute__((__nonnull__))
+void cx_tree_link(
+        void *restrict parent,
+        void *restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks a node from its parent.
+ *
+ * If the node has no parent, this function does nothing.
+ *
+ * @param node the node that shall be unlinked from its parent
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_link()
+ */
+__attribute__((__nonnull__))
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the given \p data or if one of the children might contain
+ * the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param data the data that is searched for
+ *
+ * @return 0 if the node contains the data,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
+
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the same \p data as \p new_node or if one of the children might
+ * contain the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param new_node a new node with the information which is searched
+ *
+ * @return 0 if \p node contains the same data as \p new_node,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
+
+/**
+ * Searches for data in a tree.
+ *
+ * When the data cannot be found exactly, the search function might return a
+ * closest result which might be a good starting point for adding a new node
+ * to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param data the data to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search_data(
+        const void *root,
+        const void *data,
+        cx_tree_search_data_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Searches for a node in a tree.
+ *
+ * When no node with the same data can be found, the search function might
+ * return a closest result which might be a good starting point for adding the
+ * new node to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param node the node to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search(
+        const void *root,
+        const void *node,
+        cx_tree_search_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a depth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree iterator needs to maintain a stack of visited nodes, which is
+ * allocated using stdlib malloc().
+ * When the iterator becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the iterator becomes
+ * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param visit_on_exit set to true, when the iterator shall visit a node again
+ * after processing all children
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree iterator
+ * @see cxTreeIteratorDispose()
+ */
+CxTreeIterator cx_tree_iterator(
+        void *root,
+        bool visit_on_exit,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a breadth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree visitor needs to maintain a queue of to be visited nodes, which
+ * is allocated using stdlib malloc().
+ * When the visitor becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the visitor becomes
+ * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree visitor
+ * @see cxTreeVisitorDispose()
+ */
+CxTreeVisitor cx_tree_visitor(
+        void *root,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Describes a function that creates a tree node from the specified data.
+ * The first argument points to the data the node shall contain and
+ * the second argument may be used for additional data (e.g. an allocator).
+ * Functions of this type shall either return a new pointer to a newly
+ * created node or \c NULL when allocation fails.
+ *
+ * \note the function may leave the node pointers in the struct uninitialized.
+ * The caller is responsible to set them according to the intended use case.
+ */
+typedef void *(*cx_tree_node_create_func)(const void *, void *);
+
+/**
+ * The local search depth for a new subtree when adding multiple elements.
+ * The default value is 3.
+ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to
+ * implement optimized insertion of multiple elements into a tree.
+ */
+extern unsigned int cx_tree_add_look_around_depth;
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, leaving
+ * the iterator in a valid state pointing to the element that could not be
+ * added.
+ * Also, the pointer of the created node will be stored to \p failed.
+ * The integer returned by this function denotes the number of elements obtained
+ * from the \p iter that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param iter a pointer to an arbitrary iterator
+ * @param num the maximum number of elements to obtain from the iterator
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param root the root node of the tree
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of nodes created and added
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 3, 4, 6, 7)))
+size_t cx_tree_add_iter(
+        struct cx_iterator_base_s *iter,
+        size_t num,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, storing
+ * the pointer of the created node to \p failed.
+ * The integer returned by this function denotes the number of elements from
+ * the \p src array that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param src a pointer to the source data array
+ * @param num the number of elements in the \p src array
+ * @param elem_size the size of each element in the \p src array
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of array elements successfully processed
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 4, 5, 7, 8)))
+size_t cx_tree_add_array(
+        const void *src,
+        size_t num,
+        size_t elem_size,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Adds data to a tree.
+ *
+ * An adequate location where to add the new tree node is searched with the
+ * specified \p sfunc.
+ *
+ * When a location is found, the \p cfunc will be invoked with \p cdata.
+ *
+ * The node returned by \p cfunc will be linked into the tree.
+ * When \p sfunc returned a positive integer, the new node will be linked as a
+ * child. The other children (now siblings of the new node) are then checked
+ * with \p sfunc, whether they could be children of the new node and re-linked
+ * accordingly.
+ *
+ * When \p sfunc returned zero and the found node has a parent, the new
+ * node will be added as sibling - otherwise, the new node will be added
+ * as a child.
+ *
+ * When \p sfunc returned a negative value, the new node will not be added to
+ * the tree and this function returns a non-zero value.
+ * The caller should check if \p cnode contains a node pointer and deal with the
+ * node that could not be added.
+ *
+ * This function also returns a non-zero value when \p cfunc tries to allocate
+ * a new node but fails to do so. In that case, the pointer stored to \p cnode
+ * will be \c NULL.
+ *
+ * Multiple elements can be added more efficiently with
+ * #cx_tree_add_array() or #cx_tree_add_iter().
+ *
+ * @param src a pointer to the data
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param cnode the location where a pointer to the new node is stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero when a new node was created and added to the tree,
+ * non-zero otherwise
+ */
+__attribute__((__nonnull__(1, 2, 3, 5, 6)))
+int cx_tree_add(
+        const void *src,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **cnode,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+
+/**
+ * Tree class type.
+ */
+typedef struct cx_tree_class_s cx_tree_class;
+
+/**
+ * Base structure that can be used for tree nodes in a #CxTree.
+ */
+struct cx_tree_node_base_s {
+    /**
+     * Pointer to the parent.
+     */
+    struct cx_tree_node_base_s *parent;
+    /**
+     * Pointer to the first child.
+     */
+    struct cx_tree_node_base_s *children;
+    /**
+     * Pointer to the last child.
+     */
+    struct cx_tree_node_base_s *last_child;
+    /**
+     * Pointer to the previous sibling.
+     */
+    struct cx_tree_node_base_s *prev;
+    /**
+     * Pointer to the next sibling.
+     */
+    struct cx_tree_node_base_s *next;
+};
+
+/**
+ * Structure for holding the base data of a tree.
+ */
+struct cx_tree_s {
+    /**
+     * The tree class definition.
+     */
+    const cx_tree_class *cl;
+
+    /**
+     * Allocator to allocate new nodes.
+     */
+    const CxAllocator *allocator;
+
+    /**
+     * A pointer to the root node.
+     *
+     * Will be \c NULL when \c size is 0.
+     */
+    void *root;
+
+    /**
+     * A function to create new nodes.
+     *
+     * Invocations to this function will receive a pointer to this tree
+     * structure as second argument.
+     *
+     * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to.
+     */
+    cx_tree_node_create_func node_create;
+
+    /**
+     * An optional simple destructor for the tree nodes.
+     */
+    cx_destructor_func simple_destructor;
+
+    /**
+     * An optional advanced destructor for the tree nodes.
+     */
+    cx_destructor_func2 advanced_destructor;
+
+    /**
+     * The pointer to additional data that is passed to the advanced destructor.
+     */
+    void *destructor_data;
+
+    /**
+     * A function to compare two nodes.
+     */
+    cx_tree_search_func search;
+
+    /**
+     * A function to compare a node with data.
+     */
+    cx_tree_search_data_func search_data;
+
+    /**
+     * The number of currently stored elements.
+     */
+    size_t size;
+
+    /**
+     * Offset in the node struct for the parent pointer.
+     */
+    ptrdiff_t loc_parent;
+
+    /**
+     * Offset in the node struct for the children linked list.
+     */
+    ptrdiff_t loc_children;
+
+    /**
+     * Optional offset in the node struct for the pointer to the last child
+     * in the linked list (negative if there is no such pointer).
+     */
+    ptrdiff_t loc_last_child;
+
+    /**
+     * Offset in the node struct for the previous sibling pointer.
+     */
+    ptrdiff_t loc_prev;
+
+    /**
+     * Offset in the node struct for the next sibling pointer.
+     */
+    ptrdiff_t loc_next;
+};
+
+/**
+ * Macro to roll out the #cx_tree_node_base_s structure with a custom
+ * node type.
+ */
+#define CX_TREE_NODE_BASE(type) \
+    type *parent; \
+    type *children;\
+    type *last_child;\
+    type *prev;\
+    type *next
+
+/**
+ * Macro for specifying the layout of a base node tree.
+ */
+#define cx_tree_node_base_layout \
+    offsetof(struct cx_tree_node_base_s, parent),\
+    offsetof(struct cx_tree_node_base_s, children),\
+    offsetof(struct cx_tree_node_base_s, last_child),\
+    offsetof(struct cx_tree_node_base_s, prev),  \
+    offsetof(struct cx_tree_node_base_s, next)
+
+/**
+ * Macro for obtaining the node pointer layout for a specific tree.
+ */
+#define cx_tree_node_layout(tree) \
+    (tree)->loc_parent,\
+    (tree)->loc_children,\
+    (tree)->loc_last_child,\
+    (tree)->loc_prev,  \
+    (tree)->loc_next
+
+/**
+ * The class definition for arbitrary trees.
+ */
+struct cx_tree_class_s {
+    /**
+     * Destructor function.
+     *
+     * Implementations SHALL invoke the node destructor functions if provided
+     * and SHALL deallocate the tree memory.
+     */
+    void (*destructor)(struct cx_tree_s *);
+
+    /**
+     * Member function for inserting a single element.
+     *
+     * Implementations SHALL NOT simply invoke \p insert_many as this comes
+     * with too much overhead.
+     */
+    int (*insert_element)(
+            struct cx_tree_s *tree,
+            const void *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     *
+     * Implementations SHALL avoid to perform a full search in the tree for
+     * every element even though the source data MAY be unsorted.
+     */
+    size_t (*insert_many)(
+            struct cx_tree_s *tree,
+            struct cx_iterator_base_s *iter,
+            size_t n
+    );
+
+    /**
+     * Member function for finding a node.
+     */
+    void *(*find)(
+            struct cx_tree_s *tree,
+            const void *subtree,
+            const void *data
+    );
+
+    /**
+     * Member function for creating an iterator for the tree.
+     */
+    CxTreeIterator (*iterator)(
+            struct cx_tree_s *tree,
+            bool visit_on_exit
+    );
+
+    /**
+     * Member function for creating a visitor for the tree.
+     */
+    CxTreeVisitor (*visitor)(struct cx_tree_s *tree);
+};
+
+/**
+ * Common type for all tree implementations.
+ */
+typedef struct cx_tree_s CxTree;
+
+/**
+ * Creates a new tree structure based on the specified layout.
+ *
+ * The specified \p allocator will be used for creating the tree struct
+ * and SHALL be used by \p create_func to allocate memory for the nodes.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreateSimple()
+ * @see cxTreeCreateWrapped()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreate(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Creates a new tree structure based on a default layout.
+ *
+ * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first
+ * member (or at least respect the default offsets specified in the tree
+ * struct) and they MUST be allocated with the specified allocator.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTree *cxTreeCreateSimple(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func
+) {
+    return cxTreeCreate(
+            allocator,
+            create_func,
+            search_func,
+            search_data_func,
+            cx_tree_node_base_layout
+    );
+}
+
+/**
+ * Creates a new tree structure based on an existing tree.
+ *
+ * The specified \p allocator will be used for creating the tree struct.
+ *
+ * \attention This function will create an incompletely defined tree structure
+ * where neither the create function, the search function, nor a destructor
+ * will be set. If you wish to use any of this functionality for the wrapped
+ * tree, you need to specify those functions afterwards.
+ *
+ * @param root the root node of the tree that shall be wrapped
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreateWrapped(
+        const CxAllocator *allocator,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Destroys the tree structure.
+ *
+ * \attention This function will only invoke the destructor functions
+ * on the nodes, if specified.
+ * It will NOT additionally free the nodes with the tree's allocator, because
+ * that would cause a double-free in most scenarios.
+ *
+ * @param tree the tree to destroy
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeDestroy(CxTree *tree) {
+    tree->cl->destructor(tree);
+}
+
+/**
+ * Inserts data into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to insert
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxTreeInsert(
+        CxTree *tree,
+        const void *data
+) {
+    return tree->cl->insert_element(tree, data);
+}
+
+/**
+ * Inserts elements provided by an iterator efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param iter the iterator providing the elements
+ * @param n the maximum number of elements to insert
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertIter(
+        CxTree *tree,
+        struct cx_iterator_base_s *iter,
+        size_t n
+) {
+    return tree->cl->insert_many(tree, iter, n);
+}
+
+/**
+ * Inserts an array of data efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the array of data to insert
+ * @param elem_size the size of each element in the array
+ * @param n the number of elements in the array
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertArray(
+        CxTree *tree,
+        const void *data,
+        size_t elem_size,
+        size_t n
+) {
+    if (n == 0) return 0;
+    if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
+    CxIterator iter = cxIterator(data, elem_size, n);
+    return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
+}
+
+/**
+ * Searches the data in the specified tree.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be available wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFind(
+        CxTree *tree,
+        const void *data
+) {
+    return tree->cl->find(tree, tree->root, data);
+}
+
+/**
+ * Searches the data in the specified subtree.
+ *
+ * \note When \p subtree_root is not part of the \p tree, the behavior is
+ * undefined.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be the case for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @param subtree_root the node where to start
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFindInSubtree(
+        CxTree *tree,
+        const void *data,
+        void *subtree_root
+) {
+    return tree->cl->find(tree, subtree_root, data);
+}
+
+/**
+ * Determines the size of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the number of nodes in the specified subtree
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the tree depth including the \p subtree_root
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the entire tree.
+ *
+ * @param tree the tree
+ * @return the tree depth, counting the root as one
+ */
+__attribute__((__nonnull__))
+size_t cxTreeDepth(CxTree *tree);
+
+/**
+ * Creates a depth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the sub-tree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisitor()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeIterator cxTreeIterator(
+        CxTree *tree,
+        bool visit_on_exit
+) {
+    return tree->cl->iterator(tree, visit_on_exit);
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterator()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) {
+    return tree->cl->visitor(tree);
+}
+
+/**
+ * Adds a new node to the tree.
+ *
+ * \attention The node may be externally created, but MUST obey the same rules
+ * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use
+ * the same allocator).
+ *
+ * @param tree the tree
+ * @param parent the parent of the node to add
+ * @param child the node to add
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeAddChildNode(
+        CxTree *tree,
+        void *parent,
+        void *child) {
+    cx_tree_link(parent, child, cx_tree_node_layout(tree));
+    tree->size++;
+}
+
+/**
+ * Creates a new node and adds it to the tree.
+ *
+ * With this function you can decide where exactly the new node shall be added.
+ * If you specified an appropriate search function, you may want to consider
+ * leaving this task to the tree by using #cxTreeInsert().
+ *
+ * Be aware that adding nodes at arbitrary locations in the tree might cause
+ * wrong or undesired results when subsequently invoking #cxTreeInsert() and
+ * the invariant imposed by the search function does not hold any longer.
+ *
+ * @param tree the tree
+ * @param parent the parent node of the new node
+ * @param data the data that will be submitted to the create function
+ * @return zero when the new node was created, non-zero on allocation failure
+ * @see cxTreeInsert()
+ */
+__attribute__((__nonnull__))
+int cxTreeAddChild(
+        CxTree *tree,
+        void *parent,
+        const void *data
+);
+
+/**
+ * A function that is invoked when a node needs to be re-linked to a new parent.
+ *
+ * When a node is re-linked, sometimes the contents need to be updated.
+ * This callback is invoked by #cxTreeRemove() so that those updates can be
+ * applied when re-linking the children of the removed node.
+ *
+ * @param node the affected node
+ * @param old_parent the old parent of the node
+ * @param new_parent the new parent of the node
+ */
+typedef void (*cx_tree_relink_func)(
+        void *node,
+        const void *old_parent,
+        const void *new_parent
+);
+
+/**
+ * Removes a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed node by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if \p node is the root node of the tree
+ */
+__attribute__((__nonnull__(1,2)))
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+);
+
+/**
+ * Removes a node and it's subtree from the tree.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ */
+__attribute__((__nonnull__))
+void cxTreeRemoveSubtree(CxTree *tree, void *node);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_TREE_H
diff --git a/ucx/cx/utils.h b/ucx/cx/utils.h
new file mode 100644 (file)
index 0000000..be359ea
--- /dev/null
@@ -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 utils.h
+ *
+ * \brief General purpose utility functions.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \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
diff --git a/ucx/hash_key.c b/ucx/hash_key.c
new file mode 100644 (file)
index 0000000..a2840b4
--- /dev/null
@@ -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) {
+    const unsigned char *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(const char *str) {
+    CxHashKey key;
+    key.data = str;
+    key.len = str == NULL ? 0 : strlen(str);
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key_bytes(
+        const unsigned char *bytes,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = bytes;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key(
+        const void *obj,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = obj;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
diff --git a/ucx/hash_map.c b/ucx/hash_map.c
new file mode 100644 (file)
index 0000000..f5608a7
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * 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->collection.allocator, (void *) elem->key.data);
+                // free the node
+                cxFree(map->collection.allocator, elem);
+                // proceed
+                elem = next;
+            } while (elem != NULL);
+
+            // do not leave a dangling pointer
+            hash_map->buckets[i] = NULL;
+        }
+    }
+    map->collection.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->collection.allocator, hash_map->buckets);
+
+    // free the map structure
+    cxFree(map->collection.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;
+    const CxAllocator *allocator = map->collection.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->collection.store_pointer) {
+            memcpy(elm->data, &value, sizeof(void *));
+        } else {
+            memcpy(elm->data, value, map->collection.elem_size);
+        }
+    } else {
+        // allocate new element
+        struct cx_hash_map_element_s *e = cxMalloc(
+                allocator,
+                sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
+        );
+        if (e == NULL) {
+            return -1;
+        }
+
+        // write the value
+        if (map->collection.store_pointer) {
+            memcpy(e->data, &value, sizeof(void *));
+        } else {
+            memcpy(e->data, value, map->collection.elem_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->collection.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.collection.allocator, (void *) elm->key.data);
+    cxFree(hash_map->base.collection.allocator, elm);
+    // decrease size
+    hash_map->base.collection.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->collection.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(
+        const CxMap *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(const void *it) {
+    const struct cx_iterator_s *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(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    return &elm->key;
+}
+
+static void *cx_hash_map_iter_current_value(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    const struct cx_hash_map_s *map = iter->src_handle.c;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    if (map->base.collection.store_pointer) {
+        return *(void **) elm->data;
+    } else {
+        return elm->data;
+    }
+}
+
+static bool cx_hash_map_iter_valid(const void *it) {
+    const struct cx_iterator_s *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;
+    struct cx_hash_map_s *map = iter->src_handle.m;
+
+    // remove current element, if asked
+    if (iter->base.remove) {
+
+        // 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
+    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.collection.store_pointer) {
+            iter->kv_data.value = *(void **) elm->data;
+        } else {
+            iter->kv_data.value = elm->data;
+        }
+    }
+}
+
+static CxIterator cx_hash_map_iterator(
+        const CxMap *map,
+        enum cx_map_iterator_type type
+) {
+    CxIterator iter;
+
+    iter.src_handle.c = map;
+    iter.elem_count = map->collection.size;
+
+    switch (type) {
+        case CX_MAP_ITERATOR_PAIRS:
+            iter.elem_size = sizeof(CxMapEntry);
+            iter.base.current = cx_hash_map_iter_current_entry;
+            break;
+        case CX_MAP_ITERATOR_KEYS:
+            iter.elem_size = sizeof(CxHashKey);
+            iter.base.current = cx_hash_map_iter_current_key;
+            break;
+        case CX_MAP_ITERATOR_VALUES:
+            iter.elem_size = map->collection.elem_size;
+            iter.base.current = cx_hash_map_iter_current_value;
+            break;
+        default:
+            assert(false);
+    }
+
+    iter.base.valid = cx_hash_map_iter_valid;
+    iter.base.next = cx_hash_map_iter_next;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    iter.slot = 0;
+    iter.index = 0;
+
+    if (map->collection.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->collection.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(
+        const CxAllocator *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.collection.allocator = allocator;
+
+    if (itemsize > 0) {
+        map->base.collection.store_pointer = false;
+        map->base.collection.elem_size = itemsize;
+    } else {
+        map->base.collection.store_pointer = true;
+        map->base.collection.elem_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->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
+
+        size_t new_bucket_count = (map->collection.size * 5) >> 1;
+        struct cx_hash_map_element_s **new_buckets = cxCalloc(
+                map->collection.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->collection.allocator, hash_map->buckets);
+        hash_map->buckets = new_buckets;
+    }
+    return 0;
+}
diff --git a/ucx/iterator.c b/ucx/iterator.c
new file mode 100644 (file)
index 0000000..0dae173
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 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/iterator.h"
+
+#include <string.h>
+
+static bool cx_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->index < iter->elem_count;
+}
+
+static void *cx_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_iter_next_fast(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        iter->elem_count--;
+        // only move the last element when we are not currently aiming
+        // at the last element already
+        if (iter->index < iter->elem_count) {
+            void *last = ((char *) iter->src_handle.m)
+                         + iter->elem_count * iter->elem_size;
+            memcpy(iter->elem_handle, last, iter->elem_size);
+        }
+    } else {
+        iter->index++;
+        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+    }
+}
+
+static void cx_iter_next_slow(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        iter->elem_count--;
+
+        // number of elements to move
+        size_t remaining = iter->elem_count - iter->index;
+        if (remaining > 0) {
+            memmove(
+                    iter->elem_handle,
+                    ((char *) iter->elem_handle) + iter->elem_size,
+                    remaining * iter->elem_size
+            );
+        }
+    } else {
+        iter->index++;
+        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+    }
+}
+
+CxIterator cxMutIterator(
+        void *array,
+        size_t elem_size,
+        size_t elem_count,
+        bool remove_keeps_order
+) {
+    CxIterator iter;
+
+    iter.index = 0;
+    iter.src_handle.m = array;
+    iter.elem_handle = array;
+    iter.elem_size = elem_size;
+    iter.elem_count = array == NULL ? 0 : elem_count;
+    iter.base.valid = cx_iter_valid;
+    iter.base.current = cx_iter_current;
+    iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
+    iter.base.remove = false;
+    iter.base.mutating = true;
+
+    return iter;
+}
+
+CxIterator cxIterator(
+        const void *array,
+        size_t elem_size,
+        size_t elem_count
+) {
+    CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false);
+    iter.base.mutating = false;
+    return iter;
+}
diff --git a/ucx/linked_list.c b/ucx/linked_list.c
new file mode 100644 (file)
index 0000000..5bc1cb1
--- /dev/null
@@ -0,0 +1,1117 @@
+/*
+ * 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 "cx/compare.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(
+        const void *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    size_t i = start_index;
+    const void *cur = start;
+    while (i != index && cur != NULL) {
+        cur = ll_advance(cur);
+        i < index ? i++ : i--;
+    }
+    return (void *) cur;
+}
+
+ssize_t cx_linked_list_find(
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+) {
+    void *dummy;
+    return cx_linked_list_find_node(
+            &dummy, start,
+            loc_advance, loc_data,
+            cmp_func, elem
+    );
+}
+
+ssize_t cx_linked_list_find_node(
+        void **result,
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        const void *elem
+) {
+    assert(result != NULL);
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    const void *node = start;
+    ssize_t index = 0;
+    do {
+        void *current = ll_data(node);
+        if (cmp_func(current, elem) == 0) {
+            *result = (void*) node;
+            return index;
+        }
+        node = ll_advance(node);
+        index++;
+    } while (node != NULL);
+    *result = NULL;
+    return -1;
+}
+
+void *cx_linked_list_first(
+        const void *node,
+        ptrdiff_t loc_prev
+) {
+    return cx_linked_list_last(node, loc_prev);
+}
+
+void *cx_linked_list_last(
+        const void *node,
+        ptrdiff_t loc_next
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+
+    const void *cur = node;
+    const void *last;
+    do {
+        last = cur;
+    } while ((cur = ll_next(cur)) != NULL);
+
+    return (void *) last;
+}
+
+void *cx_linked_list_prev(
+        const void *begin,
+        ptrdiff_t loc_next,
+        const void *node
+) {
+    assert(begin != NULL);
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    if (begin == node) return NULL;
+    const void *cur = begin;
+    const void *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_insert_sorted(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node,
+        cx_compare_func cmp_func
+) {
+    assert(ll_next(new_node) == NULL);
+    cx_linked_list_insert_sorted_chain(
+            begin, end, loc_prev, loc_next, new_node, cmp_func);
+}
+
+void cx_linked_list_insert_sorted_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *insert_begin,
+        cx_compare_func cmp_func
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+    assert(insert_begin != NULL);
+
+    // track currently observed nodes
+    void *dest_prev = NULL;
+    void *dest = *begin;
+    void *src = insert_begin;
+
+    // special case: list is empty
+    if (dest == NULL) {
+        *begin = src;
+        if (end != NULL) {
+            *end = cx_linked_list_last(src, loc_next);
+        }
+        return;
+    }
+
+    // search the list for insertion points
+    while (dest != NULL && src != NULL) {
+        // compare current list node with source node
+        // if less or equal, skip
+        if (cmp_func(dest, src) <= 0) {
+            dest_prev = dest;
+            dest = ll_next(dest);
+            continue;
+        }
+
+        // determine chain of elements that can be inserted
+        void *end_of_chain = src;
+        void *next_in_chain = ll_next(src);
+        while (next_in_chain != NULL) {
+            // once we become larger than the list elem, break
+            if (cmp_func(dest, next_in_chain) <= 0) {
+                break;
+            }
+            // otherwise, we can insert one more
+            end_of_chain = next_in_chain;
+            next_in_chain = ll_next(next_in_chain);
+        }
+
+        // insert the elements
+        if (dest_prev == NULL) {
+            // new begin
+            *begin = src;
+        } else {
+            cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+        }
+        cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next);
+
+        // continue with next
+        src = next_in_chain;
+        dest_prev = dest;
+        dest = ll_next(dest);
+    }
+
+    // insert remaining items
+    if (src != NULL) {
+        cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+    }
+
+    // determine new end of list, if requested
+    if (end != NULL) {
+        *end = cx_linked_list_last(
+                dest != NULL ? dest : dest_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(
+        const void *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(
+        const void *begin_left,
+        const void *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    const void *left = begin_left, *right = begin_right;
+
+    while (left != NULL && right != NULL) {
+        const void *left_data = ll_data(left);
+        const void *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
+
+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(
+        const cx_linked_list *list,
+        size_t index
+) {
+    if (index >= list->base.collection.size) {
+        return NULL;
+    } else if (index > list->base.collection.size / 2) {
+        return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index);
+    } else {
+        return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
+    }
+}
+
+static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) {
+    return cxMalloc(list->collection.allocator,
+                    sizeof(cx_linked_list_node) + list->collection.elem_size);
+}
+
+static int cx_ll_insert_at(
+        struct cx_list_s *list,
+        cx_linked_list_node *node,
+        const void *elem
+) {
+
+    // create the new new_node
+    cx_linked_list_node *new_node = cx_ll_malloc_node(list);
+
+    // 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->collection.elem_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->collection.size++;
+    return 0;
+}
+
+static size_t cx_ll_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    // out-of bounds and corner case check
+    if (index > list->collection.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 immediately advance to the inserted node
+    const char *source = array;
+    for (size_t i = 1; i < n; i++) {
+        source += list->collection.elem_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,
+        const void *element
+) {
+    return 1 != cx_ll_insert_array(list, index, element, 1);
+}
+
+static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
+
+static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
+    const cx_linked_list_node *left = l;
+    const cx_linked_list_node *right = r;
+    return cx_ll_insert_sorted_cmp_func(left->payload, right->payload);
+}
+
+static size_t cx_ll_insert_sorted(
+        struct cx_list_s *list,
+        const void *array,
+        size_t n
+) {
+    // special case
+    if (n == 0) return 0;
+
+    // create a new chain of nodes
+    cx_linked_list_node *chain = cx_ll_malloc_node(list);
+    if (chain == NULL) return 0;
+
+    memcpy(chain->payload, array, list->collection.elem_size);
+    chain->prev = NULL;
+    chain->next = NULL;
+
+    // add all elements from the array to that chain
+    cx_linked_list_node *prev = chain;
+    const char *src = array;
+    size_t inserted = 1;
+    for (; inserted < n; inserted++) {
+        cx_linked_list_node *next = cx_ll_malloc_node(list);
+        if (next == NULL) break;
+        src += list->collection.elem_size;
+        memcpy(next->payload, src, list->collection.elem_size);
+        prev->next = next;
+        next->prev = prev;
+        prev = next;
+    }
+    prev->next = NULL;
+
+    // invoke the low level function
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
+    cx_linked_list_insert_sorted_chain(
+            (void **) &ll->begin,
+            (void **) &ll->end,
+            CX_LL_LOC_PREV,
+            CX_LL_LOC_NEXT,
+            chain,
+            cx_ll_insert_sorted_cmp_helper
+    );
+
+    // adjust the list metadata
+    list->collection.size += inserted;
+
+    return inserted;
+}
+
+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->collection.size--;
+
+    // free and return
+    cxFree(list->collection.allocator, node);
+
+    return 0;
+}
+
+static void cx_ll_clear(struct cx_list_s *list) {
+    if (list->collection.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->collection.allocator, node);
+        node = next;
+    }
+    ll->begin = ll->end = NULL;
+    list->collection.size = 0;
+}
+
+#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
+#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
+
+static int cx_ll_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->collection.size || j >= list->collection.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->collection.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);
+        assert(nleft != NULL);
+        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);
+        assert(nright != NULL);
+        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->collection.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->collection.elem_size > CX_LINKED_LIST_SWAP_SBO_SIZE) {
+        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->collection.elem_size);
+        memcpy(nleft->payload, nright->payload, list->collection.elem_size);
+        memcpy(nright->payload, buf, list->collection.elem_size);
+    }
+
+    return 0;
+}
+
+static void *cx_ll_at(
+        const 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);
+    return node == NULL ? NULL : node->payload;
+}
+
+static ssize_t cx_ll_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    if (remove) {
+        cx_linked_list *ll = ((cx_linked_list *) list);
+        cx_linked_list_node *node;
+        ssize_t index = cx_linked_list_find_node(
+                (void **) &node,
+                ll->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->collection.cmpfunc, elem
+        );
+        if (node != NULL) {
+            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->collection.size--;
+            cxFree(list->collection.allocator, node);
+        }
+        return index;
+    } else {
+        return cx_linked_list_find(
+                ((cx_linked_list *) list)->begin,
+                CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                list->collection.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->collection.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(
+        const struct cx_list_s *list,
+        const struct cx_list_s *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->collection.cmpfunc);
+}
+
+static bool cx_ll_iter_valid(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_ll_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        struct cx_list_s *list = iter->src_handle.m;
+        cx_linked_list *ll = iter->src_handle.m;
+        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->collection.size--;
+        cxFree(list->collection.allocator, node);
+    } else {
+        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_s *iter = it;
+    if (iter->base.remove) {
+        iter->base.remove = false;
+        struct cx_list_s *list = iter->src_handle.m;
+        cx_linked_list *ll = iter->src_handle.m;
+        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->collection.size--;
+        cxFree(list->collection.allocator, node);
+    } else {
+        iter->index--;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+    }
+}
+
+static void *cx_ll_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    cx_linked_list_node *node = iter->elem_handle;
+    return node->payload;
+}
+
+static CxIterator cx_ll_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        bool backwards
+) {
+    CxIterator iter;
+    iter.index = index;
+    iter.src_handle.c = list;
+    iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
+    iter.elem_size = list->collection.elem_size;
+    iter.elem_count = list->collection.size;
+    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.mutating = false;
+    iter.base.remove = false;
+    return iter;
+}
+
+static int cx_ll_insert_iter(
+        CxIterator *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    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);
+        if (result == 0) {
+            iter->elem_count++;
+            if (prepend) {
+                iter->index++;
+            }
+        }
+        return result;
+    } else {
+        int result = cx_ll_insert_element(list, list->collection.size, elem);
+        if (result == 0) {
+            iter->elem_count++;
+            iter->index = list->collection.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->collection.allocator, node);
+        node = next;
+    }
+
+    cxFree(list->collection.allocator, list);
+}
+
+static cx_list_class cx_linked_list_class = {
+        cx_ll_destructor,
+        cx_ll_insert_element,
+        cx_ll_insert_array,
+        cx_ll_insert_sorted,
+        cx_ll_insert_iter,
+        cx_ll_remove,
+        cx_ll_clear,
+        cx_ll_swap,
+        cx_ll_at,
+        cx_ll_find_remove,
+        cx_ll_sort,
+        cx_ll_compare,
+        cx_ll_reverse,
+        cx_ll_iterator,
+};
+
+CxList *cxLinkedListCreate(
+        const CxAllocator *allocator,
+        cx_compare_func comparator,
+        size_t elem_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.collection.allocator = allocator;
+
+    if (elem_size > 0) {
+        list->base.collection.elem_size = elem_size;
+        list->base.collection.cmpfunc = comparator;
+    } else {
+        list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+        cxListStorePointers((CxList *) list);
+    }
+
+    return (CxList *) list;
+}
diff --git a/ucx/list.c b/ucx/list.c
new file mode 100644 (file)
index 0000000..6e3d4bb
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * 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(
+        const void *l,
+        const void *r
+) {
+    void *const *lptr = l;
+    void *const *rptr = r;
+    const void *left = lptr == NULL ? NULL : *lptr;
+    const void *right = rptr == NULL ? NULL : *rptr;
+    return cx_pl_cmpfunc_impl(left, right);
+}
+
+static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) {
+    // cast away const - this is the hacky thing
+    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+    cx_pl_cmpfunc_impl = l->cmpfunc;
+    l->cmpfunc = cx_pl_cmpfunc;
+}
+
+static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) {
+    // cast away const - this is the hacky thing
+    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+    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,
+        const void *element
+) {
+    return list->climpl->insert_element(list, index, &element);
+}
+
+static size_t cx_pl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *array,
+        size_t n
+) {
+    return list->climpl->insert_array(list, index, array, n);
+}
+
+static size_t cx_pl_insert_sorted(
+        struct cx_list_s *list,
+        const void *array,
+        size_t n
+) {
+    cx_pl_hack_cmpfunc(list);
+    size_t result = list->climpl->insert_sorted(list, array, n);
+    cx_pl_unhack_cmpfunc(list);
+    return result;
+}
+
+static int cx_pl_insert_iter(
+        struct cx_iterator_s *iter,
+        const void *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle.m;
+    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(
+        const struct cx_list_s *list,
+        size_t index
+) {
+    void **ptr = list->climpl->at(list, index);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static ssize_t cx_pl_find_remove(
+        struct cx_list_s *list,
+        const void *elem,
+        bool remove
+) {
+    cx_pl_hack_cmpfunc(list);
+    ssize_t ret = list->climpl->find_remove(list, &elem, remove);
+    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(
+        const struct cx_list_s *list,
+        const struct cx_list_s *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(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    void **ptr = iter->base.current_impl(it);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static struct cx_iterator_s cx_pl_iterator(
+        const struct cx_list_s *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_sorted,
+        cx_pl_insert_iter,
+        cx_pl_remove,
+        cx_pl_clear,
+        cx_pl_swap,
+        cx_pl_at,
+        cx_pl_find_remove,
+        cx_pl_sort,
+        cx_pl_compare,
+        cx_pl_reverse,
+        cx_pl_iterator,
+};
+
+void cxListStoreObjects(CxList *list) {
+    list->collection.store_pointer = false;
+    if (list->climpl != NULL) {
+        list->cl = list->climpl;
+        list->climpl = NULL;
+    }
+}
+
+void cxListStorePointers(CxList *list) {
+    list->collection.elem_size = sizeof(void *);
+    list->collection.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__)) const struct cx_list_s *list,
+        __attribute__((__unused__)) size_t index
+) {
+    return NULL;
+}
+
+static ssize_t cx_emptyl_find_remove(
+        __attribute__((__unused__)) struct cx_list_s *list,
+        __attribute__((__unused__)) const void *elem,
+        __attribute__((__unused__)) bool remove
+) {
+    return -1;
+}
+
+static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) {
+    return false;
+}
+
+static CxIterator cx_emptyl_iterator(
+        const struct cx_list_s *list,
+        size_t index,
+        __attribute__((__unused__)) bool backwards
+) {
+    CxIterator iter = {0};
+    iter.src_handle.c = 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,
+        NULL,
+        cx_emptyl_noop,
+        NULL,
+        cx_emptyl_at,
+        cx_emptyl_find_remove,
+        cx_emptyl_noop,
+        NULL,
+        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>
+
+#define invoke_list_func(name, list, ...) \
+    ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
+    (list, __VA_ARGS__)
+
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        const void *data,
+        size_t n
+) {
+    size_t elem_size = list->collection.elem_size;
+    const char *src = data;
+    size_t i = 0;
+    for (; i < n; i++) {
+        if (0 != invoke_list_func(insert_element,
+                                  list, index + i, src + (i * elem_size))) {
+            return i;
+        }
+    }
+    return i;
+}
+
+size_t cx_list_default_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    // corner case
+    if (n == 0) return 0;
+
+    size_t elem_size = list->collection.elem_size;
+    cx_compare_func cmp = list->collection.cmpfunc;
+    const char *src = sorted_data;
+
+    // track indices and number of inserted items
+    size_t di = 0, si = 0, inserted = 0;
+
+    // search the list for insertion points
+    for (; di < list->collection.size; di++) {
+        const void *list_elm = invoke_list_func(at, list, di);
+
+        // compare current list element with first source element
+        // if less or equal, skip
+        if (cmp(list_elm, src) <= 0) {
+            continue;
+        }
+
+        // determine number of consecutive elements that can be inserted
+        size_t ins = 1;
+        const char *next = src;
+        while (++si < n) {
+            next += elem_size;
+            // once we become larger than the list elem, break
+            if (cmp(list_elm, next) <= 0) {
+                break;
+            }
+            // otherwise, we can insert one more
+            ins++;
+        }
+
+        // insert the elements at location si
+        if (ins == 1) {
+            if (0 != invoke_list_func(insert_element,
+                                      list, di, src))
+                return inserted;
+        } else {
+            size_t r = invoke_list_func(insert_array, list, di, src, ins);
+            if (r < ins) return inserted + r;
+        }
+        inserted += ins;
+        di += ins;
+
+        // everything inserted?
+        if (inserted == n) return inserted;
+        src = next;
+    }
+
+    // insert remaining items
+    if (si < n) {
+        inserted += invoke_list_func(insert_array, list, di, src, n - si);
+    }
+
+    return inserted;
+}
+
+void cx_list_default_sort(struct cx_list_s *list) {
+    size_t elem_size = list->collection.elem_size;
+    size_t list_size = list->collection.size;
+    void *tmp = malloc(elem_size * list_size);
+    if (tmp == NULL) abort();
+
+    // copy elements from source array
+    char *loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *src = invoke_list_func(at, list, i);
+        memcpy(loc, src, elem_size);
+        loc += elem_size;
+    }
+
+    // qsort
+    qsort(tmp, list_size, elem_size,
+          list->collection.cmpfunc);
+
+    // copy elements back
+    loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *dest = invoke_list_func(at, list, i);
+        memcpy(dest, loc, elem_size);
+        loc += elem_size;
+    }
+
+    free(tmp);
+}
+
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
+    if (i == j) return 0;
+    if (i >= list->collection.size) return 1;
+    if (j >= list->collection.size) return 1;
+
+    size_t elem_size = list->collection.elem_size;
+
+    void *tmp = malloc(elem_size);
+    if (tmp == NULL) return 1;
+
+    void *ip = invoke_list_func(at, list, i);
+    void *jp = invoke_list_func(at, list, j);
+
+    memcpy(tmp, ip, elem_size);
+    memcpy(ip, jp, elem_size);
+    memcpy(jp, tmp, elem_size);
+
+    free(tmp);
+
+    return 0;
+}
+
+void cxListDestroy(CxList *list) {
+    list->cl->destructor(list);
+}
+
+int cxListCompare(
+        const CxList *list,
+        const CxList *other
+) {
+    bool cannot_optimize = false;
+
+    // if one is storing pointers but the other is not
+    cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
+
+    // if one class is wrapped but the other is not
+    cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
+
+    // if the compare functions do not match or both are NULL
+    if (!cannot_optimize) {
+        cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
+                                                      list->climpl->compare : list->cl->compare);
+        cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
+                                                       other->climpl->compare : other->cl->compare);
+        cannot_optimize |= list_cmp != other_cmp;
+        cannot_optimize |= list_cmp == NULL;
+    }
+
+    if (cannot_optimize) {
+        // lists are definitely different - cannot use internal compare function
+        if (list->collection.size == other->collection.size) {
+            CxIterator left = list->cl->iterator(list, 0, false);
+            CxIterator right = other->cl->iterator(other, 0, false);
+            for (size_t i = 0; i < list->collection.size; i++) {
+                void *leftValue = cxIteratorCurrent(left);
+                void *rightValue = cxIteratorCurrent(right);
+                int d = list->collection.cmpfunc(leftValue, rightValue);
+                if (d != 0) {
+                    return d;
+                }
+                cxIteratorNext(left);
+                cxIteratorNext(right);
+            }
+            return 0;
+        } else {
+            return list->collection.size < other->collection.size ? -1 : 1;
+        }
+    } else {
+        // lists are compatible
+        return list->cl->compare(list, other);
+    }
+}
+
+CxIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, false);
+    it.base.mutating = true;
+    return it;
+}
+
+CxIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, true);
+    it.base.mutating = true;
+    return it;
+}
diff --git a/ucx/map.c b/ucx/map.c
new file mode 100644 (file)
index 0000000..dc2dc9d
--- /dev/null
+++ b/ucx/map.c
@@ -0,0 +1,102 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "cx/map.h"\r
+#include <string.h>\r
+\r
+// <editor-fold desc="empty map implementation">\r
+\r
+static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {\r
+    // this is a noop, but MUST be implemented\r
+}\r
+\r
+static void *cx_empty_map_get(\r
+        __attribute__((__unused__)) const CxMap *map,\r
+        __attribute__((__unused__)) CxHashKey key\r
+) {\r
+    return NULL;\r
+}\r
+\r
+static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) {\r
+    return false;\r
+}\r
+\r
+static CxIterator cx_empty_map_iterator(\r
+        const struct cx_map_s *map,\r
+        __attribute__((__unused__)) enum cx_map_iterator_type type\r
+) {\r
+    CxIterator iter = {0};\r
+    iter.src_handle.c = map;\r
+    iter.base.valid = cx_empty_map_iter_valid;\r
+    return iter;\r
+}\r
+\r
+static struct cx_map_class_s cx_empty_map_class = {\r
+        cx_empty_map_noop,\r
+        cx_empty_map_noop,\r
+        NULL,\r
+        cx_empty_map_get,\r
+        NULL,\r
+        cx_empty_map_iterator\r
+};\r
+\r
+CxMap cx_empty_map = {\r
+        {\r
+                NULL,\r
+                NULL,\r
+                0,\r
+                0,\r
+                NULL,\r
+                NULL,\r
+                NULL,\r
+                false\r
+        },\r
+        &cx_empty_map_class\r
+};\r
+\r
+CxMap *const cxEmptyMap = &cx_empty_map;\r
+\r
+// </editor-fold>\r
+\r
+CxIterator cxMapMutIteratorValues(CxMap *map) {\r
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);\r
+    it.base.mutating = true;\r
+    return it;\r
+}\r
+\r
+CxIterator cxMapMutIteratorKeys(CxMap *map) {\r
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);\r
+    it.base.mutating = true;\r
+    return it;\r
+}\r
+\r
+CxIterator cxMapMutIterator(CxMap *map) {\r
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);\r
+    it.base.mutating = true;\r
+    return it;\r
+}\r
diff --git a/ucx/mempool.c b/ucx/mempool.c
new file mode 100644 (file)
index 0000000..972a487
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * 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
+) {
+    if (!ptr) return;
+    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;
+}
diff --git a/ucx/printf.c b/ucx/printf.c
new file mode 100644 (file)
index 0000000..dd77e52
--- /dev/null
@@ -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.
+ */
+
+#include "cx/printf.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef CX_PRINTF_SBO_SIZE
+#define CX_PRINTF_SBO_SIZE 512
+#endif
+unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
+
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        const char *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,
+        const char *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) {
+        va_end(ap2);
+        return ret;
+    } else if (ret < CX_PRINTF_SBO_SIZE) {
+        va_end(ap2);
+        return (int) wfc(buf, 1, ret, stream);
+    } else {
+        int len = ret + 1;
+        char *newbuf = malloc(len);
+        if (!newbuf) {
+            va_end(ap2);
+            return -1;
+        }
+
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        va_end(ap2);
+        if (ret > 0) {
+            ret = (int) wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+}
+
+cxmutstr cx_asprintf_a(
+        const CxAllocator *allocator,
+        const char *fmt,
+        ...
+) {
+    va_list ap;
+    va_start(ap, fmt);
+    cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+cxmutstr cx_vasprintf_a(
+        const CxAllocator *a,
+        const char *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;
+            }
+        }
+    }
+    va_end(ap2);
+    return s;
+}
+
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(*str, *len, fmt, ap);
+    if ((unsigned) ret >= *len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxRealloc(alloc, *str, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *len = newlen;
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
+
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, *len, fmt, ap);
+    *str = buf;
+    if ((unsigned) ret >= *len) {
+        unsigned newlen = ret + 1;
+        char *ptr = cxMalloc(alloc, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+            } else {
+                *len = newlen;
+                *str = ptr;
+                ret = newret;
+            }
+        }
+    }
+    va_end(ap2);
+    return ret;
+}
diff --git a/ucx/string.c b/ucx/string.c
new file mode 100644 (file)
index 0000000..532a40b
--- /dev/null
@@ -0,0 +1,786 @@
+/*
+ * 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(
+        const CxAllocator *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(
+        const CxAllocator *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
+unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
+
+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(
+        const CxAllocator *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(
+        const CxAllocator *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(
+        const void *s1,
+        const void *s2
+) {
+    const cxstring *left = s1;
+    const cxstring *right = s2;
+    return cx_strcmp(*left, *right);
+}
+
+int cx_strcasecmp_p(
+        const void *s1,
+        const void *s2
+) {
+    const cxstring *left = s1;
+    const cxstring *right = s2;
+    return cx_strcasecmp(*left, *right);
+}
+
+cxmutstr cx_strdup_a(
+        const CxAllocator *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(
+        const CxAllocator *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,
+        const cxstring *delim,
+        size_t count
+) {
+    ctx->delim_more = delim;
+    ctx->delim_more_count = count;
+}
diff --git a/ucx/szmul.c b/ucx/szmul.c
new file mode 100644 (file)
index 0000000..64c6a65
--- /dev/null
@@ -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;
+    }
+}
diff --git a/ucx/tree.c b/ucx/tree.c
new file mode 100644 (file)
index 0000000..150cf57
--- /dev/null
@@ -0,0 +1,958 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 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/tree.h"
+
+#include "cx/array_list.h"
+
+#include <assert.h>
+
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
+#define tree_children(node) CX_TREE_PTR(node, loc_children)
+#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child)
+#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
+#define tree_next(node) CX_TREE_PTR(node, loc_next)
+
+#define cx_tree_ptr_locations \
+    loc_parent, loc_children, loc_last_child, loc_prev, loc_next
+
+static void cx_tree_zero_pointers(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    tree_parent(node) = NULL;
+    tree_prev(node) = NULL;
+    tree_next(node) = NULL;
+    tree_children(node) = NULL;
+    if (loc_last_child >= 0) {
+        tree_last_child(node) = NULL;
+    }
+}
+
+void cx_tree_link(
+        void *restrict parent,
+        void *restrict node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    void *current_parent = tree_parent(node);
+    if (current_parent == parent) return;
+    if (current_parent != NULL) {
+        cx_tree_unlink(node, cx_tree_ptr_locations);
+    }
+
+    if (tree_children(parent) == NULL) {
+        tree_children(parent) = node;
+        if (loc_last_child >= 0) {
+            tree_last_child(parent) = node;
+        }
+    } else {
+        if (loc_last_child >= 0) {
+            void *child = tree_last_child(parent);
+            tree_prev(node) = child;
+            tree_next(child) = node;
+            tree_last_child(parent) = node;
+        } else {
+            void *child = tree_children(parent);
+            void *next;
+            while ((next = tree_next(child)) != NULL) {
+                child = next;
+            }
+            tree_prev(node) = child;
+            tree_next(child) = node;
+        }
+    }
+    tree_parent(node) = parent;
+}
+
+void cx_tree_unlink(
+        void *node,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    if (tree_parent(node) == NULL) return;
+
+    void *left = tree_prev(node);
+    void *right = tree_next(node);
+    void *parent = tree_parent(node);
+    assert(left == NULL || tree_children(parent) != node);
+    assert(right == NULL || loc_last_child < 0 ||
+           tree_last_child(parent) != node);
+
+    if (left == NULL) {
+        tree_children(parent) = right;
+    } else {
+        tree_next(left) = right;
+    }
+    if (right == NULL) {
+        if (loc_last_child >= 0) {
+            tree_last_child(parent) = left;
+        }
+    } else {
+        tree_prev(right) = left;
+    }
+
+    tree_parent(node) = NULL;
+    tree_prev(node) = NULL;
+    tree_next(node) = NULL;
+}
+
+int cx_tree_search(
+        const void *root,
+        const void *node,
+        cx_tree_search_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    int ret;
+    *result = NULL;
+
+    // shortcut: compare root before doing anything else
+    ret = sfunc(root, node);
+    if (ret < 0) {
+        return ret;
+    } else if (ret == 0 || tree_children(root) == NULL) {
+        *result = (void*)root;
+        return ret;
+    }
+
+    // create a working stack
+    CX_ARRAY_DECLARE(const void *, work);
+    cx_array_initialize(work, 32);
+
+    // add the children of root to the working stack
+    {
+        void *c = tree_children(root);
+        while (c != NULL) {
+            cx_array_simple_add(work, c);
+            c = tree_next(c);
+        }
+    }
+
+    // remember a candidate for adding the data
+    // also remember the exact return code from sfunc
+    void *candidate = (void *) root;
+    int ret_candidate = ret;
+
+    // process the working stack
+    while (work_size > 0) {
+        // pop element
+        const void *elem = work[--work_size];
+
+        // apply the search function
+        ret = sfunc(elem, node);
+
+        if (ret == 0) {
+            // if found, exit the search
+            *result = (void *) elem;
+            work_size = 0;
+            break;
+        } else if (ret > 0) {
+            // if children might contain the data, add them to the stack
+            void *c = tree_children(elem);
+            while (c != NULL) {
+                cx_array_simple_add(work, c);
+                c = tree_next(c);
+            }
+
+            // remember this node in case no child is suitable
+            if (ret < ret_candidate) {
+                candidate = (void *) elem;
+                ret_candidate = ret;
+            }
+        }
+    }
+
+    // not found, but was there a candidate?
+    if (ret != 0 && candidate != NULL) {
+        ret = ret_candidate;
+        *result = candidate;
+    }
+
+    // free the working queue and return
+    free(work);
+    return ret;
+}
+
+int cx_tree_search_data(
+        const void *root,
+        const void *data,
+        cx_tree_search_data_func sfunc,
+        void **result,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    // it is basically the same implementation
+    return cx_tree_search(
+            root, data,
+            (cx_tree_search_func) sfunc,
+            result,
+            loc_children, loc_next);
+}
+
+static bool cx_tree_iter_valid(const void *it) {
+    const struct cx_tree_iterator_s *iter = it;
+    return iter->node != NULL;
+}
+
+static void *cx_tree_iter_current(const void *it) {
+    const struct cx_tree_iterator_s *iter = it;
+    return iter->node;
+}
+
+static void cx_tree_iter_next(void *it) {
+    struct cx_tree_iterator_s *iter = it;
+    ptrdiff_t const loc_next = iter->loc_next;
+    ptrdiff_t const loc_children = iter->loc_children;
+    // protect us from misuse
+    if (!iter->base.valid(iter)) return;
+
+    void *children;
+
+    // check if we are currently exiting or entering nodes
+    if (iter->exiting) {
+        children = NULL;
+        // skipping on exit is pointless, just clear the flag
+        iter->skip = false;
+    } else {
+        if (iter->skip) {
+            // skip flag is set, pretend that there are no children
+            iter->skip = false;
+            children = NULL;
+        } else {
+            // try to enter the children (if any)
+            children = tree_children(iter->node);
+        }
+    }
+
+    if (children == NULL) {
+        // search for the next node
+        void *next;
+        cx_tree_iter_search_next:
+        // check if there is a sibling
+        if (iter->exiting) {
+            next = iter->node_next;
+        } else {
+            next = tree_next(iter->node);
+            iter->node_next = next;
+        }
+        if (next == NULL) {
+            // no sibling, we are done with this node and exit
+            if (iter->visit_on_exit && !iter->exiting) {
+                // iter is supposed to visit the node again
+                iter->exiting = true;
+            } else {
+                iter->exiting = false;
+                if (iter->depth == 1) {
+                    // there is no parent - we have iterated the entire tree
+                    // invalidate the iterator and free the node stack
+                    iter->node = iter->node_next = NULL;
+                    iter->stack_capacity = iter->depth = 0;
+                    free(iter->stack);
+                    iter->stack = NULL;
+                } else {
+                    // the parent node can be obtained from the top of stack
+                    // this way we can avoid the loc_parent in the iterator
+                    iter->depth--;
+                    iter->node = iter->stack[iter->depth - 1];
+                    // retry with the parent node to find a sibling
+                    goto cx_tree_iter_search_next;
+                }
+            }
+        } else {
+            if (iter->visit_on_exit && !iter->exiting) {
+                // iter is supposed to visit the node again
+                iter->exiting = true;
+            } else {
+                iter->exiting = false;
+                // move to the sibling
+                iter->counter++;
+                iter->node = next;
+                // new top of stack is the sibling
+                iter->stack[iter->depth - 1] = next;
+            }
+        }
+    } else {
+        // node has children, push the first child onto the stack and enter it
+        cx_array_simple_add(iter->stack, children);
+        iter->node = children;
+        iter->counter++;
+    }
+}
+
+CxTreeIterator cx_tree_iterator(
+        void *root,
+        bool visit_on_exit,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    CxTreeIterator iter;
+    iter.loc_children = loc_children;
+    iter.loc_next = loc_next;
+    iter.visit_on_exit = visit_on_exit;
+
+    // initialize members
+    iter.node_next = NULL;
+    iter.exiting = false;
+    iter.skip = false;
+
+    // assign base iterator functions
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    iter.base.current_impl = NULL;
+    iter.base.valid = cx_tree_iter_valid;
+    iter.base.next = cx_tree_iter_next;
+    iter.base.current = cx_tree_iter_current;
+
+    // visit the root node
+    iter.node = root;
+    if (root != NULL) {
+        iter.stack_capacity = 16;
+        iter.stack = malloc(sizeof(void *) * 16);
+        iter.stack[0] = root;
+        iter.counter = 1;
+        iter.depth = 1;
+    } else {
+        iter.stack_capacity = 0;
+        iter.stack = NULL;
+        iter.counter = 0;
+        iter.depth = 0;
+    }
+
+    return iter;
+}
+
+static bool cx_tree_visitor_valid(const void *it) {
+    const struct cx_tree_visitor_s *iter = it;
+    return iter->node != NULL;
+}
+
+static void *cx_tree_visitor_current(const void *it) {
+    const struct cx_tree_visitor_s *iter = it;
+    return iter->node;
+}
+
+__attribute__((__nonnull__))
+static void cx_tree_visitor_enqueue_siblings(
+        struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
+    node = tree_next(node);
+    while (node != NULL) {
+        struct cx_tree_visitor_queue_s *q;
+        q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+        q->depth = iter->queue_last->depth;
+        q->node = node;
+        iter->queue_last->next = q;
+        iter->queue_last = q;
+        node = tree_next(node);
+    }
+    iter->queue_last->next = NULL;
+}
+
+static void cx_tree_visitor_next(void *it) {
+    struct cx_tree_visitor_s *iter = it;
+    // protect us from misuse
+    if (!iter->base.valid(iter)) return;
+
+    ptrdiff_t const loc_next = iter->loc_next;
+    ptrdiff_t const loc_children = iter->loc_children;
+
+    // add the children of the current node to the queue
+    // unless the skip flag is set
+    void *children;
+    if (iter->skip) {
+        iter->skip = false;
+        children = NULL;
+    } else {
+        children = tree_children(iter->node);
+    }
+    if (children != NULL) {
+        struct cx_tree_visitor_queue_s *q;
+        q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+        q->depth = iter->depth + 1;
+        q->node = children;
+        if (iter->queue_last == NULL) {
+            assert(iter->queue_next == NULL);
+            iter->queue_next = q;
+        } else {
+            iter->queue_last->next = q;
+        }
+        iter->queue_last = q;
+        cx_tree_visitor_enqueue_siblings(iter, children, loc_next);
+    }
+
+    // check if there is a next node
+    if (iter->queue_next == NULL) {
+        iter->node = NULL;
+        return;
+    }
+
+    // dequeue the next node
+    iter->node = iter->queue_next->node;
+    iter->depth = iter->queue_next->depth;
+    {
+        struct cx_tree_visitor_queue_s *q = iter->queue_next;
+        iter->queue_next = q->next;
+        if (iter->queue_next == NULL) {
+            assert(iter->queue_last == q);
+            iter->queue_last = NULL;
+        }
+        free(q);
+    }
+
+    // increment the node counter
+    iter->counter++;
+}
+
+CxTreeVisitor cx_tree_visitor(
+        void *root,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_next
+) {
+    CxTreeVisitor iter;
+    iter.loc_children = loc_children;
+    iter.loc_next = loc_next;
+
+    // initialize members
+    iter.skip = false;
+    iter.queue_next = NULL;
+    iter.queue_last = NULL;
+
+    // assign base iterator functions
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    iter.base.current_impl = NULL;
+    iter.base.valid = cx_tree_visitor_valid;
+    iter.base.next = cx_tree_visitor_next;
+    iter.base.current = cx_tree_visitor_current;
+
+    // visit the root node
+    iter.node = root;
+    if (root != NULL) {
+        iter.counter = 1;
+        iter.depth = 1;
+    } else {
+        iter.counter = 0;
+        iter.depth = 0;
+    }
+
+    return iter;
+}
+
+static void cx_tree_add_link_duplicate(
+        void *original, void *duplicate,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+    void *shared_parent = tree_parent(original);
+    if (shared_parent == NULL) {
+        cx_tree_link(original, duplicate, cx_tree_ptr_locations);
+    } else {
+        cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations);
+    }
+}
+
+static void cx_tree_add_link_new(
+        void *parent, void *node, cx_tree_search_func sfunc,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+    // check the current children one by one,
+    // if they could be children of the new node
+    void *child = tree_children(parent);
+    while (child != NULL) {
+        void *next = tree_next(child);
+
+        if (sfunc(node, child) > 0) {
+            // the sibling could be a child -> re-link
+            cx_tree_link(node, child, cx_tree_ptr_locations);
+        }
+
+        child = next;
+    }
+
+    // add new node as new child
+    cx_tree_link(parent, node, cx_tree_ptr_locations);
+}
+
+int cx_tree_add(
+        const void *src,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **cnode,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    *cnode = cfunc(src, cdata);
+    if (*cnode == NULL) return 1;
+    cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);
+
+    void *match = NULL;
+    int result = cx_tree_search(
+            root,
+            *cnode,
+            sfunc,
+            &match,
+            loc_children,
+            loc_next
+    );
+
+    if (result < 0) {
+        // node does not fit into the tree - return non-zero value
+        return 1;
+    } else if (result == 0) {
+        // data already found in the tree, link duplicate
+        cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations);
+    } else {
+        // closest match found, add new node
+        cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations);
+    }
+
+    return 0;
+}
+
+unsigned int cx_tree_add_look_around_depth = 3;
+
+size_t cx_tree_add_iter(
+        struct cx_iterator_base_s *iter,
+        size_t num,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    // erase the failed pointer
+    *failed = NULL;
+
+    // iter not valid? cancel...
+    if (!iter->valid(iter)) return 0;
+
+    size_t processed = 0;
+    void *current_node = root;
+    const void *elem;
+
+    for (void **eptr; processed < num &&
+         iter->valid(iter) && (eptr = iter->current(iter)) != NULL;
+         iter->next(iter)) {
+        elem = *eptr;
+
+        // create the new node
+        void *new_node = cfunc(elem, cdata);
+        if (new_node == NULL) return processed;
+        cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);
+
+        // start searching from current node
+        void *match;
+        int result;
+        unsigned int look_around_retries = cx_tree_add_look_around_depth;
+        cx_tree_add_look_around_retry:
+        result = cx_tree_search(
+                current_node,
+                new_node,
+                sfunc,
+                &match,
+                loc_children,
+                loc_next
+        );
+
+        if (result < 0) {
+            // traverse upwards and try to find better parents
+            void *parent = tree_parent(current_node);
+            if (parent != NULL) {
+                if (look_around_retries > 0) {
+                    look_around_retries--;
+                    current_node = parent;
+                } else {
+                    // look around retries exhausted, start from the root
+                    current_node = root;
+                }
+                goto cx_tree_add_look_around_retry;
+            } else {
+                // no parents. so we failed
+                *failed = new_node;
+                return processed;
+            }
+        } else if (result == 0) {
+            // data already found in the tree, link duplicate
+            cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations);
+            // but stick with the original match, in case we needed a new root
+            current_node = match;
+        } else {
+            // closest match found, add new node as child
+            cx_tree_add_link_new(match, new_node, sfunc,
+                                 cx_tree_ptr_locations);
+            current_node = match;
+        }
+
+        processed++;
+    }
+    return processed;
+}
+
+size_t cx_tree_add_array(
+        const void *src,
+        size_t num,
+        size_t elem_size,
+        cx_tree_search_func sfunc,
+        cx_tree_node_create_func cfunc,
+        void *cdata,
+        void **failed,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    // erase failed pointer
+    *failed = NULL;
+
+    // super special case: zero elements
+    if (num == 0) {
+        return 0;
+    }
+
+    // special case: one element does not need an iterator
+    if (num == 1) {
+        void *node;
+        if (0 == cx_tree_add(
+                src, sfunc, cfunc, cdata, &node, root,
+                loc_parent, loc_children, loc_last_child,
+                loc_prev, loc_next)) {
+            return 1;
+        } else {
+            *failed = node;
+            return 0;
+        }
+    }
+
+    // otherwise, create iterator and hand over to other function
+    CxIterator iter = cxIterator(src, elem_size, num);
+    return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
+                            cfunc, cdata, failed, root,
+                            loc_parent, loc_children, loc_last_child,
+                            loc_prev, loc_next);
+}
+
+static void cx_tree_default_destructor(CxTree *tree) {
+    if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) {
+        CxTreeIterator iter = tree->cl->iterator(tree, true);
+        cx_foreach(void *, node, iter) {
+            if (iter.exiting) {
+                if (tree->simple_destructor) {
+                    tree->simple_destructor(node);
+                }
+                if (tree->advanced_destructor) {
+                    tree->advanced_destructor(tree->destructor_data, node);
+                }
+            }
+        }
+    }
+    cxFree(tree->allocator, tree);
+}
+
+static CxTreeIterator cx_tree_default_iterator(
+        CxTree *tree,
+        bool visit_on_exit
+) {
+    return cx_tree_iterator(
+            tree->root, visit_on_exit,
+            tree->loc_children, tree->loc_next
+    );
+}
+
+static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) {
+    return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next);
+}
+
+static int cx_tree_default_insert_element(
+        CxTree *tree,
+        const void *data
+) {
+    void *node;
+    if (tree->root == NULL) {
+        node = tree->node_create(data, tree);
+        if (node == NULL) return 1;
+        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+        tree->root = node;
+        tree->size = 1;
+        return 0;
+    }
+    int result = cx_tree_add(data, tree->search, tree->node_create,
+                tree, &node, tree->root, cx_tree_node_layout(tree));
+    if (0 == result) {
+        tree->size++;
+    } else {
+        cxFree(tree->allocator, node);
+    }
+    return result;
+}
+
+static size_t cx_tree_default_insert_many(
+        CxTree *tree,
+        struct cx_iterator_base_s *iter,
+        size_t n
+) {
+    size_t ins = 0;
+    if (!iter->valid(iter)) return 0;
+    if (tree->root == NULL) {
+        // use the first element from the iter to create the root node
+        void **eptr = iter->current(iter);
+        void *node = tree->node_create(*eptr, tree);
+        if (node == NULL) return 0;
+        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+        tree->root = node;
+        ins = 1;
+        iter->next(iter);
+    }
+    void *failed;
+    ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create,
+                                  tree, &failed, tree->root, cx_tree_node_layout(tree));
+    tree->size += ins;
+    if (ins < n) {
+        cxFree(tree->allocator, failed);
+    }
+    return ins;
+}
+
+static void *cx_tree_default_find(
+        CxTree *tree,
+        const void *subtree,
+        const void *data
+) {
+    if (tree->root == NULL) return NULL;
+
+    void *found;
+    if (0 == cx_tree_search_data(
+            subtree,
+            data,
+            tree->search_data,
+            &found,
+            tree->loc_children,
+            tree->loc_next
+    )) {
+        return found;
+    } else {
+        return NULL;
+    }
+}
+
+static cx_tree_class cx_tree_default_class = {
+        cx_tree_default_destructor,
+        cx_tree_default_insert_element,
+        cx_tree_default_insert_many,
+        cx_tree_default_find,
+        cx_tree_default_iterator,
+        cx_tree_default_visitor
+};
+
+CxTree *cxTreeCreate(
+        const CxAllocator *allocator,
+        cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func,
+        cx_tree_search_data_func search_data_func,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+    if (tree == NULL) return NULL;
+
+    tree->cl = &cx_tree_default_class;
+    tree->allocator = allocator;
+    tree->node_create = create_func;
+    tree->search = search_func;
+    tree->search_data = search_data_func;
+    tree->advanced_destructor = (cx_destructor_func2) cxFree;
+    tree->destructor_data = (void *) allocator;
+    tree->loc_parent = loc_parent;
+    tree->loc_children = loc_children;
+    tree->loc_last_child = loc_last_child;
+    tree->loc_prev = loc_prev;
+    tree->loc_next = loc_next;
+    tree->root = NULL;
+    tree->size = 0;
+
+    return tree;
+}
+
+CxTree *cxTreeCreateWrapped(
+        const CxAllocator *allocator,
+        void *root,
+        ptrdiff_t loc_parent,
+        ptrdiff_t loc_children,
+        ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+    if (tree == NULL) return NULL;
+
+    tree->cl = &cx_tree_default_class;
+    // set the allocator anyway, just in case...
+    tree->allocator = allocator;
+    tree->node_create = NULL;
+    tree->search = NULL;
+    tree->search_data = NULL;
+    tree->simple_destructor = NULL;
+    tree->advanced_destructor = NULL;
+    tree->destructor_data = NULL;
+    tree->loc_parent = loc_parent;
+    tree->loc_children = loc_children;
+    tree->loc_last_child = loc_last_child;
+    tree->loc_prev = loc_prev;
+    tree->loc_next = loc_next;
+    tree->root = root;
+    tree->size = cxTreeSubtreeSize(tree, root);
+    return tree;
+}
+
+int cxTreeAddChild(
+        CxTree *tree,
+        void *parent,
+        const void *data) {
+    void *node = tree->node_create(data, tree);
+    if (node == NULL) return 1;
+    cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+    cx_tree_link(parent, node, cx_tree_node_layout(tree));
+    tree->size++;
+    return 0;
+}
+
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
+    CxTreeVisitor visitor = cx_tree_visitor(
+            subtree_root,
+            tree->loc_children,
+            tree->loc_next
+    );
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.counter;
+}
+
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) {
+    CxTreeVisitor visitor = cx_tree_visitor(
+            subtree_root,
+            tree->loc_children,
+            tree->loc_next
+    );
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.depth;
+}
+
+size_t cxTreeDepth(CxTree *tree) {
+    CxTreeVisitor visitor = tree->cl->visitor(tree);
+    while (cxIteratorValid(visitor)) {
+        cxIteratorNext(visitor);
+    }
+    return visitor.depth;
+}
+
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+) {
+    if (node == tree->root) return 1;
+
+    // determine the new parent
+    ptrdiff_t loc_parent = tree->loc_parent;
+    void *new_parent = tree_parent(node);
+
+    // first, unlink from the parent
+    cx_tree_unlink(node, cx_tree_node_layout(tree));
+
+    // then relink each child
+    ptrdiff_t loc_children = tree->loc_children;
+    ptrdiff_t loc_next = tree->loc_next;
+    void *child = tree_children(node);
+    while (child != NULL) {
+        // forcibly set the parent to NULL - we do not use the unlink function
+        // because that would unnecessarily modify the children linked list
+        tree_parent(child) = NULL;
+
+        // update contents, if required
+        if (relink_func != NULL) {
+            relink_func(child, node, new_parent);
+        }
+
+        // link to new parent
+        cx_tree_link(new_parent, child, cx_tree_node_layout(tree));
+
+        // proceed to next child
+        child = tree_next(child);
+    }
+
+    // clear the linked list of the removed node
+    tree_children(node) = NULL;
+    ptrdiff_t loc_last_child = tree->loc_last_child;
+    if (loc_last_child >= 0) tree_last_child(node) = NULL;
+
+    // the tree now has one member less
+    tree->size--;
+
+    return 0;
+}
+
+void cxTreeRemoveSubtree(CxTree *tree, void *node) {
+    if (node == tree->root) {
+        tree->root = NULL;
+        tree->size = 0;
+        return;
+    }
+    size_t subtree_size = cxTreeSubtreeSize(tree, node);
+    cx_tree_unlink(node, cx_tree_node_layout(tree));
+    tree->size -= subtree_size;
+}
diff --git a/ucx/utils.c b/ucx/utils.c
new file mode 100644 (file)
index 0000000..c01979b
--- /dev/null
@@ -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
diff --git a/ui/Makefile b/ui/Makefile
new file mode 100644 (file)
index 0000000..a0d1148
--- /dev/null
@@ -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) $<
+
diff --git a/ui/cocoa/Makefile b/ui/cocoa/Makefile
new file mode 100644 (file)
index 0000000..8c13a4f
--- /dev/null
@@ -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)
+
diff --git a/ui/cocoa/container.h b/ui/cocoa/container.h
new file mode 100644 (file)
index 0000000..1289b63
--- /dev/null
@@ -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);
+
diff --git a/ui/cocoa/container.m b/ui/cocoa/container.m
new file mode 100644 (file)
index 0000000..dd6697d
--- /dev/null
@@ -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];
+}
diff --git a/ui/cocoa/graphics.h b/ui/cocoa/graphics.h
new file mode 100644 (file)
index 0000000..3f593d7
--- /dev/null
@@ -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
+
diff --git a/ui/cocoa/graphics.m b/ui/cocoa/graphics.m
new file mode 100644 (file)
index 0000000..062163b
--- /dev/null
@@ -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);
+    }
+}
+
+
+
diff --git a/ui/cocoa/menu.h b/ui/cocoa/menu.h
new file mode 100644 (file)
index 0000000..0f7cb8e
--- /dev/null
@@ -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);
diff --git a/ui/cocoa/menu.m b/ui/cocoa/menu.m
new file mode 100644 (file)
index 0000000..c286ef7
--- /dev/null
@@ -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;
+}
diff --git a/ui/cocoa/objs.mk b/ui/cocoa/objs.mk
new file mode 100644 (file)
index 0000000..0a35acd
--- /dev/null
@@ -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)
diff --git a/ui/cocoa/resource.h b/ui/cocoa/resource.h
new file mode 100644 (file)
index 0000000..0dd0c66
--- /dev/null
@@ -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"
+
+
diff --git a/ui/cocoa/resource.m b/ui/cocoa/resource.m
new file mode 100644 (file)
index 0000000..afb9410
--- /dev/null
@@ -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
diff --git a/ui/cocoa/stock.h b/ui/cocoa/stock.h
new file mode 100644 (file)
index 0000000..2568aaf
--- /dev/null
@@ -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);
diff --git a/ui/cocoa/stock.m b/ui/cocoa/stock.m
new file mode 100644 (file)
index 0000000..227e3bc
--- /dev/null
@@ -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;
+}
diff --git a/ui/cocoa/text.h b/ui/cocoa/text.h
new file mode 100644 (file)
index 0000000..ba0f1c5
--- /dev/null
@@ -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);
diff --git a/ui/cocoa/text.m b/ui/cocoa/text.m
new file mode 100644 (file)
index 0000000..bc92f1a
--- /dev/null
@@ -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];
+}
+
diff --git a/ui/cocoa/toolbar.h b/ui/cocoa/toolbar.h
new file mode 100644 (file)
index 0000000..95fd967
--- /dev/null
@@ -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);
diff --git a/ui/cocoa/toolbar.m b/ui/cocoa/toolbar.m
new file mode 100644 (file)
index 0000000..4a044ac
--- /dev/null
@@ -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;
+}
+
diff --git a/ui/cocoa/toolkit.h b/ui/cocoa/toolkit.h
new file mode 100644 (file)
index 0000000..a4c73b8
--- /dev/null
@@ -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
+
+
diff --git a/ui/cocoa/toolkit.m b/ui/cocoa/toolkit.m
new file mode 100644 (file)
index 0000000..c9f2a72
--- /dev/null
@@ -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
+
+
diff --git a/ui/cocoa/tree.h b/ui/cocoa/tree.h
new file mode 100644 (file)
index 0000000..66ee899
--- /dev/null
@@ -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);
+
diff --git a/ui/cocoa/tree.m b/ui/cocoa/tree.m
new file mode 100644 (file)
index 0000000..63a6525
--- /dev/null
@@ -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;
+}
diff --git a/ui/cocoa/window.h b/ui/cocoa/window.h
new file mode 100644 (file)
index 0000000..d1cfe46
--- /dev/null
@@ -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
diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m
new file mode 100644 (file)
index 0000000..e7c5ecd
--- /dev/null
@@ -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;
+}
diff --git a/ui/common/condvar.c b/ui/common/condvar.c
new file mode 100644 (file)
index 0000000..8b405c9
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+    UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar));
+    var->var.data = NULL;
+    var->var.intdata = 0;
+    var->set = 0;
+    pthread_mutex_init(&var->lock, NULL);
+    pthread_cond_init(&var->cond, NULL);
+    return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    if(!p->set) {
+        pthread_cond_wait(&p->cond, &p->lock);
+    }
+    p->set = 0;
+    pthread_mutex_unlock(&p->lock);
+    
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    p->var.data = data;
+    p->var.intdata = intdata;
+    p->set = 1;
+    pthread_cond_signal(&p->cond);
+    pthread_mutex_unlock(&p->lock);
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_destroy(&p->lock);
+    pthread_cond_destroy(&p->cond);
+    free(p);
+    
+}
diff --git a/ui/common/condvar.h b/ui/common/condvar.h
new file mode 100644 (file)
index 0000000..bf4b8e4
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_CONDVAR_H
+#define UIC_CONDVAR_H
+
+#include "../ui/toolkit.h"
+
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiPosixCondVar {
+    UiCondVar var;
+    int set;
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+} UiPosixCondVar;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONDVAR_H */
+
diff --git a/ui/common/context.c b/ui/common/context.c
new file mode 100644 (file)
index 0000000..3480620
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * 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, cx_cmp_ptr, 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;
+    
+#if UI_GTK2 || UI_GTK3
+    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_prepare_close(UiContext *ctx) {
+    cxListClear(ctx->groups);
+    cxListClear(ctx->group_widgets);
+}
+
+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 &&  cxMapSize(var_ctx->vars_unbound) > 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 && ctx->documents) {
+        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;
+
+    cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var);
+
+    if(!ctx->vars_unbound) {
+        ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
+    }
+    cxMapPut(ctx->vars_unbound, name, var);
+    
+    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 = ctx;
+    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;
+        }
+        case UI_VAR_GENERIC: {
+            val = ui_generic_new(ctx, NULL);
+        }
+    }
+    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: {
+            // TODO: not sure how correct this is
+
+            UiList *f = from->value;
+            UiList *t = to->value;
+            if (f->obj) {
+                t->obj = f->obj;
+                t->update = f->update;
+                t->getselection = f->getselection;
+                t->setselection = f->setselection;
+            }
+
+            UiVar tmp = *from;
+            *from = *to;
+            *to = tmp;
+
+            UiList* t2 = to->value;
+            ui_notify(t2->observers, NULL);
+            
+            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;
+        }
+        case UI_VAR_GENERIC: {
+            UiGeneric *f = fromvalue;
+            UiGeneric *t = to->value;
+            if(!f->obj) break;
+            uic_generic_copy(f, t);
+            t->set(t, t->value, t->type);
+            break;
+        }
+    }
+}
+
+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;
+        case UI_VAR_GENERIC: uic_generic_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;
+        case UI_VAR_GENERIC: uic_generic_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 = cxMapSize(ctx->vars);
+    cxMapPut(ctx->vars, name, var);
+    if(cxMapSize(ctx->vars) != 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;
+}
+
+UIEXPORT void ui_context_destroy(UiContext *ctx) {
+    cxMempoolDestroy(ctx->mp);
+}
+
+
+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 = cxListSize(ctx->groups);
+    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);
+}
+
+size_t uic_group_array_size(const int *groups) {
+    int i;
+    for(i=0;groups[i] >= 0;i++) { }
+    return i;
+}
+
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) {
+    uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups));
+}
+
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) {
+    const CxAllocator *a = ctx->allocator;
+    UiGroupWidget gw;
+    
+    gw.widget = widget;
+    gw.enable = enable;
+    gw.numgroups = numgroups;
+    gw.groups = cxCalloc(a, numgroups, sizeof(int));
+    
+    // copy groups
+    if(groups) {
+        memcpy(gw.groups, groups, gw.numgroups * sizeof(int));
+    }
+    
+    cxListAdd(ctx->group_widgets, &gw);
+}
+
+void uic_remove_group_widget(UiContext *ctx, void *widget) {
+    (void)cxListFindRemove(ctx->group_widgets, widget);
+}
+
+UIEXPORT void *ui_allocator(UiContext *ctx) {
+    return (void*)ctx->allocator;
+}
+
+void* ui_cx_mempool(UiContext *ctx) {
+    return ctx->mp;
+}
+
+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 && ptr) {
+        cxFree(ctx->allocator, ptr);
+    }
+}
+
+void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
+    return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
+}
+
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) {
+    if(!ctx) {
+        return NULL;
+    }
+    cxstring s = cx_str(str);
+    cxmutstr d = cx_strdup_a(ctx->allocator, s);
+    return d.ptr;
+}
diff --git a/ui/common/context.h b/ui/common/context.h
new file mode 100644 (file)
index 0000000..fdcd6a8
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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,
+    UI_VAR_GENERIC
+};
+
+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
+#if GTK_CHECK_VERSION(4, 0, 0)
+    GActionMap *action_map;
+#elif UI_GTK2 || UI_GTK3
+    GtkAccelGroup *accel_group;
+#endif 
+#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_prepare_close(UiContext *ctx);
+
+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);
+
+size_t uic_group_array_size(const int *groups);
+void uic_check_group_widgets(UiContext *ctx);
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups);
+void uic_remove_group_widget(UiContext *ctx, void *widget);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONTEXT_H */
+
diff --git a/ui/common/document.c b/ui/common/document.c
new file mode 100644 (file)
index 0000000..237b16f
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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() {
+    if (!documents) {
+        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 = cxMempoolCreate(256, NULL);
+    const CxAllocator *a = mp->allocator;
+    UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
+    ctx->mp = mp;
+    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, size, 1);
+    cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
+    return document;
+}
+
+void ui_document_destroy(void *doc) {
+    UiContext *ctx = ui_document_context(doc);
+    if(ctx) {
+        UiEvent ev;
+        ev.window = NULL;
+        ev.document = doc;
+        ev.obj = NULL;
+        ev.eventdata = NULL;
+        ev.intval = 0;
+
+        if(ctx->close_callback) {
+            ctx->close_callback(&ev, ctx->close_data);
+        }
+        cxMempoolDestroy(ctx->mp);
+    }
+}
+
+UiContext* ui_document_context(void *doc) {
+    if(doc) {
+        return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
+    } else {
+        return NULL;
+    }
+}
diff --git a/ui/common/document.h b/ui/common/document.h
new file mode 100644 (file)
index 0000000..0f439d5
--- /dev/null
@@ -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 */
+
diff --git a/ui/common/menu.c b/ui/common/menu.c
new file mode 100644 (file)
index 0000000..eb47d58
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * 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 UiMenuBuilder *current_builder;
+static UiMenuBuilder global_builder;
+
+static int menu_item_counter = 0;
+
+void uic_menu_init(void) {
+    global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = &global_builder;
+}
+
+static void add_menu(UiMenu *menu) {
+    cx_linked_list_add(
+            (void**)&current_builder->menus_begin,
+            (void**)&current_builder->menus_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            menu);
+}
+
+static void add_item(UiMenuItemI *item) {
+    UiMenu *menu = cxListAt(current_builder->current, 0);
+    cx_linked_list_add(
+            (void**)&menu->items_begin,
+            (void**)&menu->items_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            item);
+}
+
+static void mitem_set_id(UiMenuItemI *item) {
+    snprintf(item->id, 8, "%x", menu_item_counter++);
+}
+
+static char* nl_strdup(const char* s) {
+    return s ? strdup(s) : NULL;
+}
+
+int* uic_copy_groups(const 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+1, sizeof(int));
+        memcpy(newarray, groups, n * sizeof(int));
+        newarray[n] = -1;
+        *ngroups = n;
+        return newarray;
+    }
+    return NULL;
+}
+
+void ui_menu_create(const char *label) {   
+    // create menu
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    mitem_set_id(&menu->item);
+    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 (cxListSize(current_builder->current) == 0) {
+        add_menu(menu);
+    }
+    else {
+        add_item((UiMenuItemI*)menu);
+    }
+    uic_add_menu_to_stack(menu);
+}
+
+UIEXPORT void ui_menu_end(void) {
+    cxListRemove(current_builder->current, 0);
+    if(cxListSize(current_builder->current) == 0) {
+        current_builder = &global_builder;
+    }
+}
+
+
+
+void ui_menuitem_create(UiMenuItemArgs args) {
+    UiMenuItem* item = malloc(sizeof(UiMenuItem));
+    mitem_set_id(&item->item);
+    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 = uic_copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menuseparator() {
+    UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->id[0] = 0;
+    item->prev = NULL;
+    item->next = NULL;
+    item->type = UI_MENU_SEPARATOR;
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) {
+    UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem));
+    mitem_set_id(&item->item);
+    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 = uic_copy_groups(args.groups, &item->ngroups);
+    
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitem_create(UiMenuToggleItemArgs args) {
+    UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem));
+    mitem_set_id(&item->item);
+    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 = uic_copy_groups(args.groups, &item->ngroups);
+
+    add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_itemlist_create(UiMenuItemListArgs args) {
+    UiMenuItemList*item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_ITEM_LIST;
+    item->getvalue = args.getvalue;
+    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) {
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_CHECKITEM_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) {
+    UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+    mitem_set_id(&item->item);
+    item->item.prev = NULL;
+    item->item.next = NULL;
+    item->item.type = UI_MENU_RADIOITEM_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_builder->current, 0, menu);
+}
+
+UiMenu* uic_get_menu_list(void) {
+    return current_builder->menus_begin;
+}
+
+UIEXPORT void ui_menu_close(void) {
+    UiMenu* menu = cxListAt(current_builder->current, 0);
+    menu->end = 1;
+}
+
+UIEXPORT int ui_menu_is_open(void) {
+    UiMenu* menu = cxListAt(current_builder->current, 0);
+    if (menu->end) {
+        ui_menu_end();
+        return 0;
+    }
+    return 1;
+}
+
+
+void ui_contextmenu_builder(UiMenuBuilder **out_builder) {
+    UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder));
+    builder->menus_begin = NULL;
+    builder->menus_end = NULL;
+    builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = builder;
+    *out_builder = builder;
+    
+    ui_menu_create(NULL);
+}
+
+
+
+static void free_menuitem(UiMenuItemI *item) {
+    switch(item->type) {
+        default: break;
+        case UI_MENU: {
+            UiMenu *menu = (UiMenu*)item;
+            UiMenuItemI *m = menu->items_begin;
+            while(m) {
+                UiMenuItemI *next = m->next;
+                free_menuitem(m);
+                m = next;
+            }
+            break;
+        }
+        case UI_MENU_ITEM: {
+            UiMenuItem *i = (UiMenuItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            break;
+        }
+        case UI_MENU_CHECK_ITEM: {
+            UiMenuCheckItem *i = (UiMenuCheckItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            free(i->varname);
+            break;
+        }
+        case UI_MENU_RADIO_ITEM: {
+            UiMenuRadioItem *i = (UiMenuRadioItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            //free(i->varname);
+            break;
+        }
+        case UI_MENU_ITEM_LIST: {
+            break;
+        }
+        case UI_MENU_CHECKITEM_LIST: {
+            break;
+        }
+        case UI_MENU_RADIOITEM_LIST: {
+            break;
+        }
+    }
+    
+    free(item);
+}
+
+void ui_menubuilder_free(UiMenuBuilder *builder) {
+    UiMenuItemI *m = &builder->menus_begin->item;
+    while(m) {
+        UiMenuItemI *next = m->next;
+        free_menuitem(m);
+        m = next;
+    }
+    cxListDestroy(builder->current);
+    free(builder);
+}
diff --git a/ui/common/menu.h b/ui/common/menu.h
new file mode 100644 (file)
index 0000000..63b577d
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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;
+    char           id[8];
+};
+
+struct UiMenu {
+    UiMenuItemI    item;
+    const char     *label;
+    UiMenuItemI    *items_begin;
+    UiMenuItemI    *items_end;
+    UiMenu         *parent;
+    int            end;
+};
+
+struct UiMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuCheckItem {
+    UiMenuItemI    item;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    char           *varname;
+    ui_callback    callback;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuRadioItem {
+    UiMenuItemI    item;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    ui_callback    callback;
+    void           *userdata;
+    int            *groups;
+    size_t         ngroups;
+};
+
+struct UiMenuItemList {
+    UiMenuItemI     item;
+    ui_getvaluefunc getvalue;
+    ui_callback     callback;
+    void            *userdata;
+    char            *varname;
+};
+
+
+
+struct UiMenuBuilder {
+    UiMenu *menus_begin;
+    UiMenu *menus_end;
+    CxList *current;
+};
+
+void uic_menu_init(void);
+
+UiMenu* uic_get_menu_list(void);
+
+void uic_add_menu_to_stack(UiMenu* menu);
+
+int* uic_copy_groups(const int* groups, size_t *ngroups);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_MENU_H */
+
diff --git a/ui/common/object.c b/ui/common/object.c
new file mode 100644 (file)
index 0000000..5fd1130
--- /dev/null
@@ -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.
+ */
+
+#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;
+    }
+}
+
+void ui_object_ref(UiObject *obj) {
+    obj->ref++;
+}
+
+void ui_object_unref(UiObject *obj) {
+    // it is possible to have 0 references, in case
+    // a window was created but ui_show was never called
+    if(obj->ref == 0 || --obj->ref == 0) {
+        if(obj->destroy) {
+            obj->destroy(obj);
+        } else {
+            uic_object_destroy(obj);
+        }
+    }
+}
+
+void uic_object_destroy(UiObject *obj) {
+    if(obj->ctx->close_callback) {
+        UiEvent ev;
+        ev.window = obj->window;
+        ev.document = obj->ctx->document;
+        ev.obj = obj;
+        ev.eventdata = NULL;
+        ev.intval = 0;
+        obj->ctx->close_callback(&ev, obj->ctx->close_data);
+    }
+    cxMempoolDestroy(obj->ctx->mp);
+}
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
+    return uic_ctx_object_new(toplevel->ctx, widget);
+}
+
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) {
+    UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject));
+    newobj->ctx = ctx;
+    newobj->widget = widget;
+    
+    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;
+}
diff --git a/ui/common/object.h b/ui/common/object.h
new file mode 100644 (file)
index 0000000..bc8bd79
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+void uic_object_destroy(UiObject *obj);
+    
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget);
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
+UiObject* uic_current_obj(UiObject *toplevel);
+
+UiContainer* uic_get_current_container(UiObject *obj);;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_OBJECT_H */
+
diff --git a/ui/common/objs.mk b/ui/common/objs.mk
new file mode 100644 (file)
index 0000000..b8980af
--- /dev/null
@@ -0,0 +1,46 @@
+#
+# 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 += menu.o
+COMMON_OBJ += toolbar.o
+COMMON_OBJ += ucx_properties.o
+COMMON_OBJ += threadpool.o
+COMMON_OBJ += condvar.o
+
+TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
+TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
+
diff --git a/ui/common/properties.c b/ui/common/properties.c
new file mode 100644 (file)
index 0000000..45a9b4e
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * 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 "../ui/toolkit.h"
+
+
+#include "properties.h"
+#include <cx/string.h>
+#include <cx/buffer.h>
+
+#include <cx/hash_map.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(void) {
+    if(ui_appname() == NULL) {
+        return NULL;
+    }
+
+    return ui_configfile(NULL);
+}
+
+#ifndef _WIN32
+#define UI_PATH_SEPARATOR '/'
+#define UI_ENV_HOME "HOME"
+#else
+#define UI_PATH_SEPARATOR '\\'
+#define UI_ENV_HOME "USERPROFILE"
+#endif
+
+char* ui_configfile(char *name) {
+    const 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(UI_ENV_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] != UI_PATH_SEPARATOR) {
+        cxBufferPut(&buf, UI_PATH_SEPARATOR);
+    }
+    
+#ifdef UI_COCOA
+    // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/
+    cxBufferPutString(&buf, "Library/Application Support/");
+#elif defined(_WIN32)
+    // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/
+    cxBufferPutString(&buf, "AppData\\Local\\");
+#else
+    // app dir is $HOME/.$APPNAME/
+    cxBufferPut(&buf, '.');
+#endif
+    cxBufferPutString(&buf, appname);
+    cxBufferPut(&buf, UI_PATH_SEPARATOR);
+    
+    // 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);
+}
+
+int uic_store_app_properties() {
+    char *path = ui_configfile("application.properties");
+    if(!path) {
+        return 1;
+    }
+    
+    FILE *file = fopen(path, "w");
+    if(!file) {
+        fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path);
+        free(path);
+        return 1;
+    }
+    
+    int ret = 0;
+    if(ucx_properties_store(application_properties, file)) {
+        fprintf(stderr, "Ui Error: Cannot store application properties.\n");
+        ret = 1;
+    }
+    
+    fclose(file);
+    free(path);
+    
+    return ret;
+}
+
+
+const char* ui_get_property(const char *name) {
+    return cxMapGet(application_properties, name);
+}
+
+void ui_set_property(const char *name, const char *value) {
+    cxMapPut(application_properties, name, (char*)value);
+}
+
+const char* ui_set_default_property(const char *name, const char *value) {
+    const char *v = cxMapGet(application_properties, name);
+    if(!v) {
+        cxMapPut(application_properties, name, (char*)value);
+        v = value;
+    }
+    return v;
+}
+
+int ui_properties_store(void) {
+    return uic_store_app_properties();
+}
+
+
+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);
+}
diff --git a/ui/common/properties.h b/ui/common/properties.h
new file mode 100644 (file)
index 0000000..6e11345
--- /dev/null
@@ -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();
+int 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 */
+
diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c
new file mode 100644 (file)
index 0000000..71adb9f
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "threadpool.h"
+#include "context.h"
+
+#include <pthread.h>
+
+#ifndef _WIN32
+
+
+static threadpool_job kill_job;
+
+UiThreadpool* threadpool_new(int min, int max) {
+    UiThreadpool *pool = malloc(sizeof(UiThreadpool));
+    pool->queue = NULL;
+    pool->queue_len = 0;
+    pool->num_idle = 0;
+    pool->min_threads = min;
+    pool->max_threads = max;
+
+    pthread_mutex_init(&pool->queue_lock, NULL);
+    pthread_mutex_init(&pool->avlbl_lock, NULL);
+    pthread_cond_init(&pool->available, NULL);  
+
+    return pool;
+}
+
+int threadpool_start(UiThreadpool *pool) {
+    /* create pool threads */
+    for(int i=0;i<pool->min_threads;i++) {
+        pthread_t t;
+        if (pthread_create(&t, NULL, threadpool_func, pool) != 0) {
+            fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno));
+            return 1;
+        }
+    }
+    return 0;
+}
+
+void* threadpool_func(void *data) {
+    UiThreadpool *pool = (UiThreadpool*)data;
+    
+    for(;;) {
+        threadpool_job *job = threadpool_get_job(pool);
+        if(job == &kill_job) {
+            break;
+        }
+        
+        job->callback(job->data);
+
+        free(job);
+    }
+    return NULL;
+}
+
+threadpool_job* threadpool_get_job(UiThreadpool *pool) {
+    pthread_mutex_lock(&pool->queue_lock);
+
+    threadpool_job *job = NULL;
+    pool->num_idle++;
+    while(job == NULL) {
+        if(pool->queue_len == 0) {
+            pthread_cond_wait(&pool->available, &pool->queue_lock);
+            continue;
+        } else {
+            pool_queue_t *q = pool->queue;
+            job = q->job;
+            pool->queue = q->next;
+            pool->queue_len--;
+            free(q);
+        }
+    }
+    pool->num_idle--;
+
+    pthread_mutex_unlock(&pool->queue_lock);
+    return job;
+}
+
+void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) {
+    // TODO: handle errors
+    
+    threadpool_job *job = malloc(sizeof(threadpool_job));
+    job->callback = func;
+    job->data = data;
+
+    pthread_mutex_lock(&pool->queue_lock);
+    threadpool_enqueue_job(pool, job);
+
+    int create_thread = 0;
+    int destroy_thread = 0;
+    int diff = pool->queue_len - pool->num_idle;
+    
+    //if(pool->queue_len == 1) {
+    pthread_cond_signal(&pool->available);
+    //}
+    
+    pthread_mutex_unlock(&pool->queue_lock);
+}
+
+void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) {
+    pool_queue_t *q = malloc(sizeof(pool_queue_t));
+    q->job = job;
+    q->next = NULL;
+    
+    if(pool->queue == NULL) {
+        pool->queue = q;
+    } else {
+        pool_queue_t *last_elem = pool->queue;
+        while(last_elem->next != NULL) {
+            last_elem = last_elem->next;
+        }
+        last_elem->next = q;
+    }
+    pool->queue_len++;
+}
+
+
+
+
+
+
+UiThreadpool* ui_threadpool_create(int nthreads) {
+    UiThreadpool *pool = threadpool_new(nthreads, nthreads);
+    threadpool_start(pool); // TODO: check return value
+    return pool;
+}
+
+void ui_threadpool_destroy(UiThreadpool* pool) {
+    
+}
+
+static int ui_threadpool_job_finish(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 0;
+}
+
+static void* ui_threadpool_job_func(void *data) {
+    UiJob *job = data;
+    if (!job->job_func(job->job_data) && job->finish_callback) {
+        ui_call_mainthread(ui_threadpool_job_finish, job);
+    } else {
+        free(job);
+    }
+    return NULL;
+}
+
+void ui_threadpool_job(UiThreadpool* pool, 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;
+    threadpool_run(pool, ui_threadpool_job_func, job);
+}
+
+
+#endif
+
diff --git a/ui/common/threadpool.h b/ui/common/threadpool.h
new file mode 100644 (file)
index 0000000..2ab5e35
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_THREADPOOL_H
+#define UIC_THREADPOOL_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+    
+typedef struct UiJob {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+} UiJob;
+    
+    
+typedef struct  _threadpool_job   threadpool_job;
+typedef void*(*job_callback_f)(void *data);
+    
+typedef struct _pool_queue pool_queue_t;
+struct UiThreadpool {
+    pthread_mutex_t queue_lock;
+    pthread_mutex_t avlbl_lock;
+    pthread_cond_t  available;
+    pool_queue_t    *queue;
+    uint32_t        queue_len;
+    uint32_t        num_idle;
+    int             min_threads;
+    int             max_threads;
+};
+
+struct _threadpool_job {
+    job_callback_f      callback;
+    void                *data;
+};
+
+struct _pool_queue {
+    threadpool_job   *job;
+    pool_queue_t     *next;
+};
+
+UiThreadpool* threadpool_new(int min, int max);
+int threadpool_start(UiThreadpool *pool);
+void* threadpool_func(void *data);
+threadpool_job* threadpool_get_job(UiThreadpool *pool);
+void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data);
+void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_THREADPOOL_H */
+
diff --git a/ui/common/toolbar.c b/ui/common/toolbar.c
new file mode 100644 (file)
index 0000000..0d86f17
--- /dev/null
@@ -0,0 +1,148 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "toolbar.h"\r
+#include "menu.h"\r
+\r
+#include <string.h>\r
+\r
+static CxMap* toolbar_items;\r
+\r
+static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right\r
+\r
+static UiToolbarMenuItem* ui_appmenu;\r
+\r
+\r
+void uic_toolbar_init(void) {\r
+    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);\r
+    toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+    toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+    toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+}\r
+\r
+static char* nl_strdup(const char* str) {\r
+    return str ? strdup(str) : NULL;\r
+}\r
+\r
+static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args, size_t *ngroups) {\r
+    UiToolbarItemArgs newargs;\r
+    newargs.label = nl_strdup(args.label);\r
+    newargs.stockid = nl_strdup(args.stockid);\r
+    newargs.icon = nl_strdup(args.icon);\r
+    newargs.onclick = args.onclick;\r
+    newargs.onclickdata = args.onclickdata;\r
+    newargs.groups = uic_copy_groups(args.groups, ngroups);\r
+    return newargs;\r
+}\r
+\r
+void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args) {\r
+    UiToolbarItem* item = malloc(sizeof(UiToolbarItem));\r
+    item->item.type = UI_TOOLBAR_ITEM;\r
+    item->args = itemargs_copy(args, &item->ngroups);\r
+    cxMapPut(toolbar_items, name, item);\r
+}\r
+\r
+\r
+static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args, size_t *ngroups) {\r
+    UiToolbarToggleItemArgs newargs;\r
+    newargs.label = nl_strdup(args.label);\r
+    newargs.stockid = nl_strdup(args.stockid);\r
+    newargs.icon = nl_strdup(args.icon);\r
+    newargs.varname = nl_strdup(args.varname);\r
+    newargs.onchange = args.onchange;\r
+    newargs.onchangedata = args.onchangedata;\r
+    newargs.groups = uic_copy_groups(args.groups, ngroups);\r
+    return newargs;\r
+}\r
+\r
+void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args) {\r
+    UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem));\r
+    item->item.type = UI_TOOLBAR_TOGGLEITEM;\r
+    item->args = toggleitemargs_copy(args, &item->ngroups);\r
+    cxMapPut(toolbar_items, name, item);\r
+}\r
+\r
+static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) {\r
+    UiToolbarMenuArgs newargs;\r
+    newargs.label = nl_strdup(args.label);\r
+    newargs.stockid = nl_strdup(args.stockid);\r
+    newargs.icon = nl_strdup(args.icon);\r
+    return newargs;\r
+}\r
+\r
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) {\r
+    UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem));\r
+    item->item.type = UI_TOOLBAR_MENU;\r
+    memset(&item->menu, 0, sizeof(UiMenu));\r
+    item->args = menuargs_copy(args);\r
+\r
+    item->end = 0;\r
+\r
+    if (!name) {\r
+        // special appmenu\r
+        ui_appmenu = item;\r
+    } else {\r
+        // toplevel menu\r
+        cxMapPut(toolbar_items, name, item);\r
+    }\r
+\r
+    uic_add_menu_to_stack(&item->menu);\r
+}\r
+\r
+\r
+CxMap* uic_get_toolbar_items(void) {\r
+    return toolbar_items;\r
+}\r
+\r
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {\r
+    if (pos >= 0 && pos < 3) {\r
+        return toolbar_defaults[pos];\r
+    }\r
+    return NULL;\r
+}\r
+\r
+void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) {\r
+    char* cp = strdup(name);\r
+    if (pos >= 0 && pos < 3) {\r
+        cxListAdd(toolbar_defaults[pos], cp);\r
+    } else {\r
+        // TODO: error\r
+    }\r
+}\r
+\r
+UiBool uic_toolbar_isenabled(void) {\r
+    return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0;\r
+}\r
+\r
+UiToolbarItemI* uic_toolbar_get_item(const char* name) {\r
+    return cxMapGet(toolbar_items, name);\r
+}\r
+\r
+UiToolbarMenuItem* uic_get_appmenu(void) {\r
+    return ui_appmenu;\r
+}\r
diff --git a/ui/common/toolbar.h b/ui/common/toolbar.h
new file mode 100644 (file)
index 0000000..a13828e
--- /dev/null
@@ -0,0 +1,98 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#ifndef UIC_TOOLBAR_H\r
+#define UIC_TOOLBAR_H\r
+\r
+#include "../ui/toolbar.h"\r
+\r
+#include <cx/linked_list.h>\r
+#include <cx/hash_map.h>\r
+\r
+#include "menu.h"\r
+\r
+#ifdef __cplusplus\r
+extern "C" {\r
+#endif\r
+\r
+typedef struct UiToolbarItemI      UiToolbarItemI;\r
+\r
+typedef struct UiToolbarItem       UiToolbarItem;\r
+typedef struct UiToolbarToggleItem UiToolbarToggleItem;\r
+\r
+typedef struct UiToolbarMenuItem   UiToolbarMenuItem;\r
+\r
+enum UiToolbarItemType {\r
+    UI_TOOLBAR_ITEM = 0,\r
+    UI_TOOLBAR_TOGGLEITEM,\r
+    UI_TOOLBAR_MENU\r
+};\r
+\r
+typedef enum UiToolbarItemType UiToolbarItemType;\r
+\r
+struct UiToolbarItemI {\r
+    UiToolbarItemType type;\r
+};\r
+\r
+struct UiToolbarItem {\r
+    UiToolbarItemI item;\r
+    UiToolbarItemArgs args;\r
+    size_t ngroups;\r
+};\r
+\r
+struct UiToolbarToggleItem {\r
+    UiToolbarItemI item;\r
+    UiToolbarToggleItemArgs args;\r
+    size_t ngroups;\r
+};\r
+\r
+struct UiToolbarMenuItem {\r
+    UiToolbarItemI item;\r
+    UiMenu menu;\r
+    UiToolbarMenuArgs args;\r
+    int end;\r
+};\r
+\r
+\r
+void uic_toolbar_init(void);\r
+\r
+CxMap* uic_get_toolbar_items(void);\r
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos);\r
+\r
+UiBool uic_toolbar_isenabled(void);\r
+\r
+UiToolbarItemI* uic_toolbar_get_item(const char* name);\r
+\r
+UiToolbarMenuItem* uic_get_appmenu(void);\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif\r
+\r
+#endif /* UIC_TOOLBAR_H */\r
+\r
diff --git a/ui/common/types.c b/ui/common/types.c
new file mode 100644 (file)
index 0000000..cbea616
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * 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->getselection = 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 cxListSize(list->data);
+}
+
+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_remove(UiList *list, int i) {
+    cxListRemove(list->data, i);
+}
+
+void ui_list_clear(UiList *list) {
+    cxListClear(list->data);
+}
+
+UIEXPORT void ui_list_update(UiList *list) {
+    if(list->update) {
+        list->update(list, 0);
+    }
+}
+
+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 = cxListSize(cols);
+    info->columns = len;
+    info->types = ui_calloc(ctx, len, sizeof(UiModelType));
+    info->titles = ui_calloc(ctx, len, sizeof(char*));
+    info->columnsize = ui_calloc(ctx, len, sizeof(int));
+    
+    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;
+}
+
+UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name) {
+    UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric));
+    memset(g, 0, sizeof(UiGeneric));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_GENERIC, g);
+    }
+    return g;
+}
+
+
+void ui_int_set(UiInteger* i, int64_t value) {
+    if (i && i->set) {
+        i->set(i, value);
+    }
+}
+
+int64_t ui_int_get(UiInteger* i) {
+    if (i) {
+        return i->get ? i->get(i) : i->value;
+    } else {
+        return 0;
+    }
+}
+
+void ui_double_set(UiDouble* d, double value) {
+    if (d && d->set) {
+        d->set(d, value);
+    }
+}
+
+double ui_double_get(UiDouble* d) {
+    if (d) {
+        return d->get ? d->get(d) : d->value;
+    }
+    else {
+        return 0;
+    }
+}
+
+void ui_string_set(UiString* s, const char* value) {
+    if (s && s->set) {
+        s->set(s, value);
+    }
+}
+
+char* ui_string_get(UiString* s) {
+    if (s) {
+        return s->get ? s->get(s) : s->value.ptr;
+    }
+    else {
+        return 0;
+    }
+}
+
+void ui_text_set(UiText* s, const char* value) {
+    if (s && s->set) {
+        s->set(s, value);
+    }
+}
+
+char* ui_text_get(UiText* s) {
+    if (s) {
+        return s->get ? s->get(s) : s->value.ptr;
+    }
+    else {
+        return 0;
+    }
+}
+
+
+// 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_generic_copy(UiGeneric *from, UiGeneric *to) {
+    to->get = from->get;
+    to->get_type = from->get_type;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_int_save(UiInteger *i) {
+    if(!i->obj) return;
+    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_generic_save(UiGeneric *g) {
+    if(!g->obj) return;
+    g->get(g);
+}
+
+
+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;
+}
+
+void uic_generic_unbind(UiGeneric *g) {
+    g->get = NULL;
+    g->get_type = NULL;
+    g->set = NULL;
+    g->obj = NULL;
+}
+
+
+UIEXPORT UiListSelection ui_list_getselection(UiList *list) {
+    if (list->getselection) {
+        return list->getselection(list);
+    }
+    return (UiListSelection){ 0, NULL };
+}
+
+UIEXPORT void ui_list_setselection(UiList *list, int index) {
+    if (list->setselection && index >= 0) {
+        UiListSelection sel;
+        sel.count = 1;
+        sel.rows = &index;
+        list->setselection(list, sel);
+    }
+}
+
+UIEXPORT void ui_listselection_free(UiListSelection selection) {
+    if (selection.rows) {
+        free(selection.rows);
+    }
+}
+
+UIEXPORT UiStr ui_str(char *cstr) {
+    return (UiStr) { cstr, NULL };
+}
+
+UIEXPORT UiStr ui_str_free(char *str, void (*freefunc)(void *v)) {
+    return (UiStr) { str, freefunc };
+}
+
+
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list) {
+    char **newlist = calloc(sizeof(char*), list.nfiles);
+    for (int i = 0; i < list.nfiles; i++) {
+        newlist[i] = strdup(list.files[i]);
+    }
+    return (UiFileList) { newlist, list.nfiles };
+}
+
+UIEXPORT void ui_filelist_free(UiFileList list) {
+    for (int i = 0; i < list.nfiles; i++) {
+        free(list.files[i]);
+    }
+    free(list.files);
+}
+
+
+typedef struct UiObserverDestructor {
+    UiList *list;
+    UiObserver *observer;
+} UiObserverDestructor;
+
+static void observer_destructor(UiObserverDestructor *destr) {
+    UiObserver *remove_obs = destr->observer;
+    UiObserver *obs = destr->list->observers;
+    UiObserver *prev = NULL;
+    while(obs) {
+        if(obs == remove_obs) {
+            if(prev) {
+                prev->next = obs->next;
+            } else {
+                destr->list->observers = obs->next;
+            }
+            break;
+        }
+        prev = obs;
+        obs = obs->next;
+    }
+    free(remove_obs);
+}
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer) {
+    CxMempool *mp = ctx->mp;
+    UiObserverDestructor *destr = cxMalloc(mp->allocator, sizeof(UiObserverDestructor));
+    destr->list = list;
+    destr->observer = observer;
+    cxMempoolSetDestructor(destr, (cx_destructor_func)observer_destructor);
+}
diff --git a/ui/common/types.h b/ui/common/types.h
new file mode 100644 (file)
index 0000000..677199f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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_generic_copy(UiGeneric *from, UiGeneric *to);
+
+void uic_int_save(UiInteger *i);
+void uic_double_save(UiDouble *d);
+void uic_string_save(UiString *s);
+void uic_text_save(UiText *t);
+void uic_range_save(UiRange *r);
+void uic_generic_save(UiGeneric *g);
+
+void uic_int_unbind(UiInteger *i);
+void uic_double_unbind(UiDouble *d);
+void uic_string_unbind(UiString *s);
+void uic_text_unbind(UiText *t);
+void uic_range_unbind(UiRange *r);
+void uic_list_unbind(UiList *l);
+void uic_generic_unbind(UiGeneric *g);
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer);
+  
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TYPES_H */
+
diff --git a/ui/common/ucx_properties.c b/ui/common/ucx_properties.c
new file mode 100644 (file)
index 0000000..b9374aa
--- /dev/null
@@ -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->collection.allocator, value);
+        if(!mutvalue.ptr) {
+            return 1;
+        }
+        if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
+            cxFree(map->collection.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;
+}
+
diff --git a/ui/common/ucx_properties.h b/ui/common/ucx_properties.h
new file mode 100644 (file)
index 0000000..cfb4359
--- /dev/null
@@ -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 */
+
diff --git a/ui/gtk/Makefile b/ui/gtk/Makefile
new file mode 100644 (file)
index 0000000..aba263f
--- /dev/null
@@ -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)       
+       
diff --git a/ui/gtk/button.c b/ui/gtk/button.c
new file mode 100644 (file)
index 0000000..adbbc43
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * 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"
+
+void ui_button_set_icon_name(GtkWidget *button, const char *icon) {
+    if(!icon) {
+        return;
+    }
+    
+#ifdef UI_GTK4
+    gtk_button_set_icon_name(GTK_BUTTON(button), icon);
+#else
+#if GTK_CHECK_VERSION(2, 6, 0)
+    GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON);
+    if(image) {
+        gtk_button_set_image(GTK_BUTTON(button), image);
+    }
+#else
+    // TODO
+#endif
+#endif
+}
+
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata,
+        int event_value,
+        bool activate_event)
+{
+    GtkWidget *button = gtk_button_new_with_label(label);
+    ui_button_set_icon_name(button, icon);
+    
+    if(onclick) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = onclick;
+        event->value = event_value;
+        event->customdata = NULL;
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+        if(activate_event) {
+            g_signal_connect(
+                button,
+                "activate",
+                G_CALLBACK(ui_button_clicked),
+                event);
+        }
+    }
+    
+    return button;
+}
+
+UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata, 0, FALSE);
+    ui_set_name_and_style(button, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, button, args.groups);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, 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(void *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 = event->var->value;
+    e.intval = i->get(i);  
+    
+    ui_notify_evt(i->observers, &e);
+}
+
+static void ui_toggled_callback(GtkToggleButton *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_button_get_active(widget);
+    event->callback(&e, event->userdata);    
+}
+
+static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) {
+    if(gtk_toggle_button_get_active(widget)) {
+        ui_set_group(event->obj->ctx, event->value);
+    } else {
+        ui_unset_group(event->obj->ctx, event->value);
+    }
+}
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state)
+{
+    if(label) {
+        gtk_button_set_label(GTK_BUTTON(togglebutton), label);
+    }
+    ui_button_set_icon_name(togglebutton, icon);
+    
+    ui_bind_togglebutton(
+            obj,
+            togglebutton,
+            ui_toggle_button_get,
+            ui_toggle_button_set,
+            varname,
+            value,
+            (ui_toggled_func)ui_toggled_callback,
+            onchange,
+            onchangedata,
+            (ui_toggled_func)ui_togglebutton_enable_state_callback,
+            enable_state
+            );
+}
+
+void ui_bind_togglebutton(
+        UiObject *obj,
+        GtkWidget *widget,
+        int64_t (*getfunc)(UiInteger*),
+        void (*setfunc)(UiInteger*, int64_t),
+        const char *varname,
+        UiInteger *value,
+        void (*toggled_callback)(void*, void*),
+        ui_callback onchange,
+        void *onchangedata,
+        void (*enable_state_func)(void*, void*),
+        int enable_state)
+{
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER);
+    if (var) {
+        UiInteger* value = (UiInteger*)var->value;
+        value->obj = widget;
+        value->get = getfunc;
+        value->set = setfunc;
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = NULL;
+        event->callback = NULL;
+        event->userdata = NULL;
+
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(ui_toggled_obs),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    if(onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = onchangedata;
+        event->callback = onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(toggled_callback),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    if(enable_state > 0) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = NULL;
+        event->callback = NULL;
+        event->value = enable_state;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(enable_state_func),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+}
+
+static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    ui_setup_togglebutton(
+            current,
+            widget,
+            args.label,
+            args.icon,
+            args.varname,
+            args.value,
+            args.onchange,
+            args.onchangedata,
+            args.enable_group);
+    ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+    return togglebutton_create(obj, gtk_toggle_button_new(), args);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+int64_t ui_check_button_get(UiInteger *integer) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = (int)gtk_check_button_get_active(button);
+    return integer->value;
+}
+
+void ui_check_button_set(UiInteger *integer, int64_t value) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = value;
+    gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+static void ui_checkbox_callback(GtkCheckButton *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_check_button_get_active(widget);
+    event->callback(&e, event->userdata);    
+}
+
+static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) {
+    if(gtk_check_button_get_active(widget)) {
+        ui_set_group(event->obj->ctx, event->value);
+    } else {
+        ui_unset_group(event->obj->ctx, event->value);
+    }
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *widget = gtk_check_button_new_with_label(args.label); 
+    ui_bind_togglebutton(
+            obj,
+            widget,
+            ui_check_button_get,
+            ui_check_button_set,
+            args.varname,
+            args.value,
+            (ui_toggled_func)ui_checkbox_callback,
+            args.onchange,
+            args.onchangedata,
+            (ui_toggled_func)ui_checkbox_enable_state,
+            args.enable_group);
+    
+    ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
+}
+
+#else
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    return togglebutton_create(obj, gtk_check_button_new(), args);
+}
+#endif
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+#ifdef UI_GTK3
+    return NULL; // TODO
+#else
+    return ui_checkbox_create(obj, args);
+#endif
+}
+
+#if GTK_MAJOR_VERSION >= 4
+#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label)
+#define RADIOBUTTON_SET_GROUP(button, group) 
+#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button)
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button))
+#else
+#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label)
+#define RADIOBUTTON_SET_GROUP(button, group) /* noop */
+#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button))
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))
+#endif
+
+static void radiobutton_toggled(void *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 = RADIOBUTTON_GET_ACTIVE(widget);
+    event->callback(&e, event->userdata);    
+}
+
+typedef struct UiRadioButtonData {
+    UiInteger *value;
+    UiVarEventData *eventdata;
+    UiBool first;
+} UiRadioButtonData;
+
+static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) {
+    ui_destroy_vardata(w, data->eventdata);
+    if(data->first) {
+        g_slist_free(data->value->obj);
+        data->value->obj = NULL;
+        data->value->get = NULL;
+        data->value->set = NULL;
+    }
+    free(data);
+}
+
+UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GSList *rg = NULL;
+    UiInteger *rgroup;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    
+    UiBool first = FALSE;
+    if(var) {
+        rgroup = var->value;
+        rg = rgroup->obj;
+        if(!rg) {
+            first = TRUE;
+        }
+    }
+    
+    GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label); 
+    ui_set_name_and_style(rbutton, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, rbutton, args.groups);
+    if(rgroup) {
+#if GTK_MAJOR_VERSION >= 4
+        if(rg) {
+            gtk_check_button_set_group(GTK_CHECK_BUTTON(rbutton), rg->data);
+        }
+        rg = g_slist_prepend(rg, rbutton);
+#else
+        gtk_radio_button_set_group(GTK_RADIO_BUTTON(rbutton), rg);
+        rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
+#endif
+        
+        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;
+        event->callback = NULL;
+        event->userdata = NULL;
+        
+        UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData));
+        rbdata->value = rgroup;
+        rbdata->eventdata = event;
+        rbdata->first = first;
+        
+        g_signal_connect(
+                rbutton,
+                "toggled",
+                G_CALLBACK(ui_radio_obs),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(destroy_radiobutton),
+                rbdata);
+    }
+    
+    if(args.onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = args.onchangedata;
+        event->callback = args.onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                rbutton,
+                "toggled",
+                G_CALLBACK(radiobutton_toggled),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, rbutton, FALSE);
+    
+    return rbutton;
+}
+
+void ui_radio_obs(GtkToggleButton *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);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+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_check_button_get_active(GTK_CHECK_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_check_button_set_active(GTK_CHECK_BUTTON(ls->data), TRUE);
+            break;
+        }
+        ls = ls->next;
+        j++;
+    }
+    
+    value->value = i;
+}
+#else
+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;
+}
+#endif
diff --git a/ui/gtk/button.h b/ui/gtk/button.h
new file mode 100644 (file)
index 0000000..c855b35
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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
+    
+void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
+
+typedef void (*ui_toggled_func)(void*, void*);
+
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata,
+        int event_value,
+        bool activate_event);
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata,
+        int enable_state);
+
+void ui_bind_togglebutton(
+        UiObject *obj,
+        GtkWidget *widget,
+        int64_t (*getfunc)(UiInteger*),
+        void (*setfunc)(UiInteger*, int64_t),
+        const char *varname,
+        UiInteger *value,
+        void (*toggled_callback)(void*, void*),
+        ui_callback onchange,
+        void *onchangedata,
+        void (*enable_state_func)(void*, void*),
+        int enable_state);
+    
+// event wrapper
+void ui_button_clicked(GtkWidget *widget, UiEventData *event);
+
+
+void ui_toggled_obs(void *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(GtkToggleButton *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 */
+
diff --git a/ui/gtk/container.c b/ui/gtk/container.c
new file mode 100644 (file)
index 0000000..36e46cd
--- /dev/null
@@ -0,0 +1,962 @@
+/*
+ * 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 "headerbar.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) {
+#if GTK_MAJOR_VERSION >= 3
+    return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+#else
+    return gtk_vbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_gtk_hbox_new(int spacing) {
+#if GTK_MAJOR_VERSION >= 3
+    return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+#else
+    return gtk_hbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_subcontainer_create(
+        UiSubContainerType type,
+        UiObject *newobj,
+        int spacing,
+        int columnspacing,
+        int rowspacing,
+        int margin)
+{
+    GtkWidget *sub = NULL;
+    GtkWidget *add = NULL;
+    switch(type) {
+        default: {
+            sub = ui_gtk_vbox_new(spacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_box_container(newobj, sub, type);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_HBOX: {
+            sub = ui_gtk_hbox_new(spacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_box_container(newobj, sub, type);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_GRID: {
+            sub = ui_create_grid_widget(columnspacing, rowspacing);
+            add = ui_box_set_margin(sub, margin);
+            newobj->container = ui_grid_container(newobj, sub);
+            newobj->widget = sub;
+            break;
+        }
+        case UI_CONTAINER_NO_SUB: {
+            break;
+        }
+    }
+    return add;
+}
+
+
+/* -------------------- Box Container -------------------- */
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) {
+    UiBoxContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiBoxContainer));
+    ct->container.widget = box;
+    ct->container.add = ui_box_container_add;
+    ct->type = type;
+    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;
+#if GTK_MAJOR_VERSION >= 4
+    gtk_box_append(GTK_BOX(ct->widget), widget);
+    GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START; 
+    if(bc->type == UI_CONTAINER_VBOX) {
+        gtk_widget_set_valign(widget, align);
+        gtk_widget_set_vexpand(widget, expand);
+        gtk_widget_set_hexpand(widget, TRUE);
+    } else if(bc->type == UI_CONTAINER_HBOX) {
+        gtk_widget_set_halign(widget, align);
+        gtk_widget_set_hexpand(widget, expand);
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+#else
+    gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+#endif
+    
+    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;
+    UI_GTK_V2(ct->width = 0);
+    UI_GTK_V2(ct->height = 1);
+    return (UiContainer*)ct;
+}
+
+#if GTK_MAJOR_VERSION >= 3
+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;
+    int hfill = FALSE;
+    int vfill = FALSE;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+        hfill = TRUE;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+        vfill = TRUE;
+    }
+    if(fill) {
+        hfill = TRUE;
+        vfill = TRUE;
+    }
+    
+    if(!hfill) {
+        gtk_widget_set_halign(widget, GTK_ALIGN_START);
+    }
+    if(!vfill) {
+        gtk_widget_set_valign(widget, GTK_ALIGN_START);
+    }
+    
+    gtk_widget_set_hexpand(widget, hexpand);
+    gtk_widget_set_vexpand(widget, vexpand);
+    
+    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+    
+    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan);
+    grid->x += colspan;
+    
+    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;
+    
+    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+    // TODO: use colspan/rowspan
+    
+    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_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) {
+    FRAME_SET_CHILD(ct->widget, widget);
+}
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) {
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiContainer));
+    ct->widget = expander;
+    ct->add = ui_expander_container_add;
+    return ct;
+}
+
+void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    EXPANDER_SET_CHILD(ct->widget, widget);
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: check if the widget implements GtkScrollable
+    SCROLLEDWINDOW_SET_CHILD(ct->widget, widget);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+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;
+}
+
+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) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview");
+        return;
+    }
+    data->add_tab(ct->widget, -1, ct->layout.label, widget);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) {
+    GtkWidget *ret = box;
+#if GTK_MAJOR_VERSION >= 3
+#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
+    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_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type) {
+    UiObject *current = uic_current_obj(obj);
+    UiContainer *ct = current->container;
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
+    ui_set_name_and_style(box, args.name, args.style_class);
+    GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_box_container(obj, box, type);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, UI_CONTAINER_VBOX);
+}
+
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+    return ui_box_create(obj, args, UI_CONTAINER_HBOX);
+}
+
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing) {
+#if GTK_MAJOR_VERSION >= 3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#else
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    gtk_table_set_col_spacings(GTK_TABLE(grid), colspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+#endif
+    return grid;
+}
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    GtkWidget *widget;
+    
+    GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing);
+    ui_set_name_and_style(grid, args.name, args.style_class);
+    widget = ui_box_set_margin(grid, args.margin);
+    current->container->add(current->container, 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_frame_create(UiObject *obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *frame = gtk_frame_new(args.label);
+    UiObject *newobj = uic_object_new(obj, frame);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        FRAME_SET_CHILD(frame, sub);
+    } else {
+        newobj->widget = frame;
+        newobj->container = ui_frame_container(obj, frame);
+    }
+    current->container->add(current->container, frame, FALSE);
+    uic_obj_add(obj, newobj);
+    
+    return frame;
+}
+
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *expander = gtk_expander_new(args.label);
+    gtk_expander_set_expanded(GTK_EXPANDER(expander), args.isexpanded);
+    UiObject *newobj = uic_object_new(obj, expander);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        EXPANDER_SET_CHILD(expander, sub);
+    } else {
+        newobj->widget = expander;
+        newobj->container = ui_expander_container(obj, expander);
+    }
+    current->container->add(current->container, expander, FALSE);
+    uic_obj_add(obj, newobj);
+    
+    return expander;
+}
+
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *sw = SCROLLEDWINDOW_NEW();
+    ui_set_name_and_style(sw, args.name, args.style_class);
+    GtkWidget *widget = ui_box_set_margin(sw, args.margin);
+    current->container->add(current->container, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, sw);
+    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+    if(sub) {
+        SCROLLEDWINDOW_SET_CHILD(sw, sub);
+    } else {
+        newobj->widget = sw;
+        newobj->container = ui_scrolledwindow_container(obj, sw);
+    }
+    
+    uic_obj_add(obj, newobj);
+    
+    return sw;
+}
+
+
+void ui_notebook_tab_select(UIWIDGET tabview, int tab) {
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_remove(UIWIDGET tabview, int tab) {
+    gtk_notebook_remove_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    gtk_notebook_insert_page(
+            GTK_NOTEBOOK(widget),
+            child,
+            gtk_label_new(name),
+            index);
+}
+
+int64_t ui_notebook_get(UiInteger *i) {
+    GtkNotebook *nb = i->obj;
+    i->value = gtk_notebook_get_current_page(nb);
+    return i->value;
+}
+
+void ui_notebook_set(UiInteger *i, int64_t value) {
+    GtkNotebook *nb = i->obj;
+    gtk_notebook_set_current_page(nb, value);
+    i->value = gtk_notebook_get_current_page(nb);
+}
+
+
+#if GTK_MAJOR_VERSION >= 4
+static int stack_set_page(GtkWidget *stack, int index) {
+    GtkSelectionModel *pages = gtk_stack_get_pages(GTK_STACK(stack));
+    GListModel *list = G_LIST_MODEL(pages);
+    GtkStackPage *page = g_list_model_get_item(list, index);
+    if(page) {
+        gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_page_get_child(page));
+    } else {
+        fprintf(stderr, "UI Error: ui_stack_set value out of bounds\n");
+        return -1;
+    }
+    return index;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+    stack_set_page(tabview, tab);
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+    GtkStack *stack = GTK_STACK(tabview);
+    GtkWidget *current = gtk_stack_get_visible_child(stack);
+    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+    GListModel *list = G_LIST_MODEL(pages);
+    GtkStackPage *page = g_list_model_get_item(list, tab);
+    if(page) {
+        gtk_stack_remove(stack, gtk_stack_page_get_child(page));
+    }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    (void)gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+    GtkStack *stack = GTK_STACK(i->obj);
+    GtkWidget *current = gtk_stack_get_visible_child(stack);
+    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+    GListModel *list = G_LIST_MODEL(pages);
+    int nitems = g_list_model_get_n_items(list);
+    for(int p=0;p<nitems;p++) {
+        GtkStackPage *page = g_list_model_get_item(list, p);
+        GtkWidget *child = gtk_stack_page_get_child(page);
+        if(child == current) {
+            i->value = p;
+            break;
+        }
+    }
+    return i->value;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+    GtkWidget *widget = i->obj;
+    if(stack_set_page(widget, value) >= 0) {
+        i->value = value;
+    }
+}
+#elif GTK_MAJOR_VERSION >= 3
+static GtkWidget* stack_get_child(GtkWidget *stack, int index) {
+    GList *children = gtk_container_get_children(GTK_CONTAINER(stack));
+    if(children) {
+        return g_list_nth_data(children, index);
+    }
+    return NULL;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+    GtkWidget *child = stack_get_child(tabview, tab);
+    if(child) {
+        gtk_stack_set_visible_child(GTK_STACK(tabview), child);
+    }
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+    GtkWidget *child = stack_get_child(tabview, tab);
+    if(child) {
+        gtk_container_remove(GTK_CONTAINER(tabview), child);
+    }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+    gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+    GtkWidget *visible = gtk_stack_get_visible_child(GTK_STACK(i->obj));
+    GList *children = gtk_container_get_children(GTK_CONTAINER(i->obj));
+    GList *elm = children;
+    int n = 0;
+    int64_t v = -1;
+    while(elm) {
+        GtkWidget *child = elm->data;
+        if(child == visible) {
+            v = n;
+            break;
+        }
+        
+        elm = elm->next;
+        n++;
+    }
+    g_list_free(children);
+    i->value = v;
+    return v;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+    GtkWidget *child = stack_get_child(i->obj, value);
+    if(child) {
+        gtk_stack_set_visible_child(GTK_STACK(i->obj), child);
+        i->value = value;
+    }
+}
+
+#endif
+
+
+
+
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview) {
+    return g_object_get_data(G_OBJECT(tabview), "ui_tabview");
+}
+
+typedef int64_t(*ui_tabview_get_func)(UiInteger*);
+typedef void (*ui_tabview_set_func)(UiInteger*, int64_t);
+
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
+    UiGtkTabView *data = malloc(sizeof(UiGtkTabView));
+    data->margin = args.margin;
+    data->spacing = args.spacing;
+    data->columnspacing = args.columnspacing;
+    data->rowspacing = args.rowspacing;
+    
+    ui_tabview_get_func getfunc = NULL;
+    ui_tabview_set_func setfunc = NULL;
+    
+    GtkWidget *widget = NULL;
+    GtkWidget *data_widget = NULL;
+    switch(args.tabview) {
+        case UI_TABVIEW_DOC: {
+            // TODO
+            break;
+        }
+        case UI_TABVIEW_NAVIGATION_SIDE: {
+#if GTK_CHECK_VERSION(3, 10, 0)
+            widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+            GtkWidget *sidebar = gtk_stack_sidebar_new();
+            BOX_ADD(widget, sidebar);
+            GtkWidget *stack = gtk_stack_new();
+            gtk_stack_set_transition_type (GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN);
+            gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(sidebar), GTK_STACK(stack));
+            BOX_ADD_EXPAND(widget, stack);
+            data->select_tab = ui_stack_tab_select;
+            data->remove_tab = ui_stack_tab_remove;
+            data->add_tab = ui_stack_tab_add;
+            getfunc = ui_stack_get;
+            setfunc = ui_stack_set;
+            data_widget = stack;
+#else
+            // TODO
+#endif
+            break;
+        }
+        case UI_TABVIEW_DEFAULT: /* fall through */
+        case UI_TABVIEW_NAVIGATION_TOP: /* fall through */
+        case UI_TABVIEW_INVISIBLE: /* fall through */
+        case UI_TABVIEW_NAVIGATION_TOP2: {
+            widget = gtk_notebook_new();
+            data_widget = widget;
+            data->select_tab = ui_notebook_tab_select;
+            data->remove_tab = ui_notebook_tab_remove;
+            data->add_tab = ui_notebook_tab_add;
+            getfunc = ui_notebook_get;
+            setfunc = ui_notebook_set;
+            if(args.tabview == UI_TABVIEW_INVISIBLE) {
+                gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
+                gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
+            }
+            break;
+        }
+    }
+    
+    UiObject* current = uic_current_obj(obj);
+    if(args.value || args.varname) {
+        UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+        UiInteger *i = var->value;
+        i->get = getfunc;
+        i->set = setfunc;
+        i->obj = data_widget;
+    }
+    
+    g_object_set_data(G_OBJECT(widget), "ui_tabview", data);
+    data->widget = data_widget;
+    data->subcontainer = args.subcontainer;
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, widget);
+    newobj->container = ui_tabview_container(obj, widget);
+    uic_obj_add(obj, newobj);
+    data->obj = newobj;
+    
+    return widget;
+}
+
+void ui_tab_create(UiObject* obj, const char* title) {
+    UiObject* current = uic_current_obj(obj);
+    UiGtkTabView *data = ui_widget_get_tabview_data(current->widget);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    
+    UiObject *newobj = ui_tabview_add(current->widget, title, -1);
+    current->next = newobj;
+}
+
+
+
+void ui_tabview_select(UIWIDGET tabview, int tab) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    data->select_tab(tabview, tab);
+}
+
+void ui_tabview_remove(UIWIDGET tabview, int tab) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return;
+    }
+    data->remove_tab(tabview, tab);
+}
+
+UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
+    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+    if(!data) {
+        fprintf(stderr, "UI Error: widget is not a tabview\n");
+        return NULL;
+    }
+    
+    UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject));
+    newobj->ctx = data->obj->ctx;
+    
+    GtkWidget *sub;
+    switch(data->subcontainer) {
+        default: {
+            sub = ui_gtk_vbox_new(data->spacing);
+            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+            break;
+        }
+        case UI_CONTAINER_HBOX: {
+            sub = ui_gtk_hbox_new(data->spacing);
+            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+            break;
+        }
+        case UI_CONTAINER_GRID: {
+            sub = ui_create_grid_widget(data->columnspacing, data->rowspacing);
+            newobj->container = ui_grid_container(newobj, sub);
+            break;
+        }
+    }
+    newobj->widget = sub;
+    GtkWidget *widget = ui_box_set_margin(sub, data->margin);
+    
+    data->add_tab(data->widget, tab_index, name, widget);
+    
+    return newobj;
+}
+
+
+/* -------------------- Headerbar -------------------- */
+
+static void hb_set_part(UiObject *obj, int part) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *headerbar = current->widget;
+    
+    UiHeaderbarContainer *hb = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    memcpy(hb, current->container, sizeof(UiHeaderbarContainer));
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = (UiContainer*)hb;
+    uic_obj_add(obj, newobj);
+    
+    hb->part = part;
+}
+
+void ui_headerbar_start_create(UiObject *obj) {
+    hb_set_part(obj, 0);
+}
+
+void ui_headerbar_center_create(UiObject *obj) {
+    hb_set_part(obj, 2);
+}
+
+void ui_headerbar_end_create(UiObject *obj) {
+    hb_set_part(obj, 1);
+}
+
+UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs args) {
+    UiObject *current = uic_current_obj(obj);
+    UiContainer *ct = current->container;
+    UI_APPLY_LAYOUT1(current, args);
+    
+    GtkWidget *box = ui_gtk_hbox_new(args.alt_spacing);
+    ui_set_name_and_style(box, args.name, args.style_class);
+    ct->add(ct, box, FALSE);
+    
+    UiObject *newobj = uic_object_new(obj, box);
+    newobj->container = ui_headerbar_fallback_container(obj, box);
+    uic_obj_add(obj, newobj);
+    
+    return box;
+}
+
+static void hb_fallback_set_part(UiObject *obj, int part) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *headerbar = current->widget;
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = ui_headerbar_container(obj, headerbar);
+    uic_obj_add(obj, newobj);
+    
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container;
+    hb->part = part;
+}
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) {
+    UiHeaderbarContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    ct->container.widget = headerbar;
+    ct->container.add = ui_headerbar_fallback_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+    BOX_ADD(ct->widget, widget);
+}
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+    GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar");
+    if(!headerbar) {
+        return ui_headerbar_fallback_create(obj, args);
+    }
+    
+    UiObject *newobj = uic_object_new(obj, headerbar);
+    newobj->container = ui_headerbar_container(obj, headerbar);
+    uic_obj_add(obj, newobj);
+    
+    return headerbar;    
+}
+
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) {
+    UiHeaderbarContainer *ct = cxCalloc(
+            obj->ctx->allocator,
+            1,
+            sizeof(UiHeaderbarContainer));
+    ct->container.widget = headerbar;
+    ct->container.add = ui_headerbar_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+    if(hb->part == 0) {
+        UI_HEADERBAR_PACK_START(ct->widget, widget);
+    } else if(hb->part == 1) {
+        UI_HEADERBAR_PACK_END(ct->widget, widget);
+    } else if(hb->part == 2) {
+        if(!hb->centerbox) {
+            GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+            hb->centerbox = box;
+            UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box);
+        }
+        BOX_ADD(hb->centerbox, widget);
+    }
+}
+
+#else
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+    return ui_headerbar_fallback_create(obj, args);  
+}
+
+#endif
+
+/* -------------------- Splitpane -------------------- */
+
+static GtkWidget* create_paned(UiOrientation orientation) {
+#if GTK_MAJOR_VERSION >= 3
+    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;
+}
+
+
+
+
+
+
+/*
+ * -------------------- 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_hfill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.hfill = fill;
+}
+
+void ui_layout_vfill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.vfill = fill;
+}
+
+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;
+}
+
diff --git a/ui/gtk/container.h b/ui/gtk/container.h
new file mode 100644 (file)
index 0000000..0a06a9c
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * 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>
+
+#include <cx/allocator.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;
+    UiBool       hfill;
+    UiBool       vfill;
+    int          width;
+    int          colspan;
+    int          rowspan;
+};
+
+struct UiContainer {
+    GtkWidget *widget;
+    UIMENU menu;
+    GtkWidget *current;
+    
+    void (*add)(UiContainer*, GtkWidget*, UiBool);
+    UiLayout layout;
+    
+    int close;
+};
+
+typedef struct UiBoxContainer {
+    UiContainer container;
+    UiSubContainerType type;
+    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;
+
+typedef void (*ui_select_tab_func)(UIWIDGET widget, int tab);
+typedef void (*ui_add_tab_func)(UIWIDGET widget, int index, const char *name, UIWIDGET child);
+
+typedef struct UiGtkTabView {
+    UiObject *obj;
+    GtkWidget *widget;
+    ui_select_tab_func select_tab;
+    ui_select_tab_func remove_tab;
+    ui_add_tab_func add_tab;
+    UiSubContainerType subcontainer;
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+} UiGtkTabView;
+
+typedef struct UiHeaderbarContainer {
+    UiContainer container;
+    GtkWidget *centerbox;
+    int part;
+    UiHeaderbarAlternative alternative; /* only used by fallback headerbar */
+} UiHeaderbarContainer;
+
+GtkWidget* ui_gtk_vbox_new(int spacing);
+GtkWidget* ui_gtk_hbox_new(int spacing);
+
+GtkWidget* ui_subcontainer_create(
+        UiSubContainerType type,
+        UiObject *newobj,
+        int spacing,
+        int columnspacing,
+        int rowspacing,
+        int margin);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin);
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type);
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing);
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander);
+void ui_expander_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);
+
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
+
+void ui_gtk_notebook_select_tab(GtkWidget *widget, int tab);
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+#endif
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONTAINER_H */
+
diff --git a/ui/gtk/display.c b/ui/gtk/display.c
new file mode 100644 (file)
index 0000000..78ff754
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * 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"
+#include "../ui/display.h"
+
+#include <cx/printf.h>
+
+static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
+#if GTK_MAJOR_VERSION >= 4 || (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_create(UiObject *obj, UiLabelArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    const char *css_class = NULL;
+    char *markup = NULL;
+    if(args.label) {
+        #if GTK_MAJOR_VERSION < 3
+            switch(args.style) {
+                case UI_LABEL_STYLE_DEFAULT: break;
+                case UI_LABEL_STYLE_TITLE: {
+                    cxmutstr m = cx_asprintf("<b>%s</b>", args.label);
+                    markup = m.ptr;
+                    args.label = NULL;
+                }
+                case UI_LABEL_STYLE_SUBTITLE: {
+                    break;
+                }
+                case UI_LABEL_STYLE_DIM: {
+                    break;
+                }
+            }
+#       else
+            switch(args.style) {
+                case UI_LABEL_STYLE_DEFAULT: break;
+                case UI_LABEL_STYLE_TITLE: {
+                    css_class = "ui_label_title";
+                    break;
+                }
+                case UI_LABEL_STYLE_SUBTITLE: {
+                    css_class = "subtitle";
+                    break;
+                }
+                case UI_LABEL_STYLE_DIM: {
+                    css_class = "dim-label";
+                    break;
+                }
+            }
+#       endif
+    }
+
+    
+    GtkWidget *widget = gtk_label_new(args.label);
+    if(markup) {
+        gtk_label_set_markup(GTK_LABEL(widget), markup);
+        free(markup);
+    }
+    
+    if(css_class) {
+        WIDGET_ADD_CSS_CLASS(widget, css_class);
+    }
+    
+    switch(args.align) {
+        case UI_ALIGN_DEFAULT: break;
+        case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break;
+        case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break;
+        case UI_ALIGN_CENTER: break; // TODO
+    }
+    
+
+    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;
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, 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 *s) {
+    if(s->value.ptr) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = g_strdup(gtk_label_get_text(GTK_LABEL(s->obj)));
+    s->value.free = (ui_freefunc)g_free;
+    return s->value.ptr;
+}
+
+void ui_label_set(UiString *s, const char *value) {
+    gtk_label_set_text(GTK_LABEL(s->obj), value);
+    if(s->value.ptr) {
+        s->value.free(s->value.ptr);
+        s->value.ptr = NULL;
+        s->value.free = NULL;
+    }
+}
+
+UIWIDGET ui_space_deprecated(UiObject *obj) {
+    GtkWidget *widget = gtk_label_new("");
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, TRUE);
+    
+    return widget;
+}
+
+UIWIDGET ui_separator_deprecated(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 3
+    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 ------------------------- */
+
+typedef struct UiProgressBarRange {
+    double min;
+    double max;
+} UiProgressBarRange;
+
+UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *progressbar = gtk_progress_bar_new();
+    if(args.max > args.min) {
+        UiProgressBarRange *range = malloc(sizeof(UiProgressBarRange));
+        range->min = args.min;
+        range->max = args.max;
+        g_signal_connect(
+                progressbar,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                range);
+        g_object_set_data(G_OBJECT(progressbar), "ui_range", range);
+    }
+    
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+    if(var && var->value) {
+        UiDouble *value = var->value;
+        value->get = ui_progressbar_get;
+        value->set = ui_progressbar_set;
+        value->obj = progressbar;
+        ui_progressbar_set(value, value->value);
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, progressbar, FALSE);
+    
+    return progressbar;
+}
+
+double ui_progressbar_get(UiDouble *d) {
+    UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+    double fraction = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+    if(range) {
+        fraction = range->min + (range->max - range->min) * fraction;
+    }
+    d->value = fraction;
+    return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+    d->value = value;
+    UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+    if(range) {
+        value = (value - range->min) / (range->max - range->min);
+    }
+    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value);
+}
+
+
+/* ------------------------- progress spinner ------------------------- */
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *spinner = gtk_spinner_new();
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    if(var && var->value) {
+        UiInteger *value = var->value;
+        value->get = ui_spinner_get;
+        value->set = ui_spinner_set;
+        value->obj = spinner;
+        ui_spinner_set(value, value->value);
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, spinner, FALSE);
+    
+    return spinner;
+}
+
+int64_t ui_spinner_get(UiInteger *i) {
+    return i->value;
+}
+
+void ui_spinner_set(UiInteger *i, int64_t value) {
+    i->value = value;
+    if(i->obj) {
+        GtkSpinner *spinner = GTK_SPINNER(i->obj);
+        if(value != 0) {
+            gtk_spinner_start(spinner);
+        } else {
+            gtk_spinner_stop(spinner);
+        }
+    }
+}
diff --git a/ui/gtk/display.h b/ui/gtk/display.h
new file mode 100644 (file)
index 0000000..504c7ad
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+char* ui_label_get(UiString *s);
+void ui_label_set(UiString *s, const char *value);
+    
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+int64_t ui_spinner_get(UiInteger *i);
+void ui_spinner_set(UiInteger *i, int64_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c
new file mode 100644 (file)
index 0000000..e51c981
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * 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>
+#include <cx/array_list.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;
+}
+*/
+
+#if GTK_MAJOR_VERSION >= 4
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    if(!sel->providers) {
+        return;
+    }
+    
+    if(len == -1) {
+        len = strlen(str);
+    }
+    GBytes *bytes = g_bytes_new(str, len);
+    GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes);
+    g_bytes_unref(bytes);
+    
+    cxListAdd(sel->providers, &provider);
+}
+
+void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+    if(!sel->providers) {
+        return;
+    }
+    
+    GFile **files = calloc(nelm, sizeof(GFile*));
+    for(int i=0;i<nelm;i++) {
+        GFile *file = uris[i][0] == '/' ? g_file_new_for_path(uris[i]) : g_file_new_for_uri(uris[i]);
+        files[i] = file;
+    }
+    GdkFileList *list = gdk_file_list_new_from_array(files, nelm);
+    
+    GdkContentProvider *provider = gdk_content_provider_new_typed(GDK_TYPE_FILE_LIST, list);
+    cxListAdd(sel->providers, &provider);
+    
+    g_slist_free_full ((GSList*)list, g_object_unref);
+    free(files);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->value) {
+        return NULL;
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) {
+        const char *str = g_value_get_string(sel->value);
+        return str ? strdup(str) : NULL;
+    }
+    
+    return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->value) {
+        return (UiFileList){NULL,0};
+    }
+    
+    if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) {
+        GSList *list = g_value_get_boxed(sel->value);
+        if(!list) {
+            return (UiFileList){NULL,0};
+        }
+        guint size = g_slist_length(list);
+        
+        UiFileList flist;
+        flist.nfiles = size;
+        flist.files = calloc(size, sizeof(char*));
+        int i=0;
+        while(list) {
+            GFile *file = list->data;
+            char *uri = g_file_get_uri(file);
+            flist.files[i++] = strdup(uri);
+            g_free(uri);
+            list = list->next;
+        }
+        return flist;
+    }
+    return (UiFileList){NULL,0};
+}
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16);
+    dnd->selected_action = 0;
+    dnd->delete = FALSE;
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    cxListDestroy(dnd->providers);
+    free(dnd);
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+#else
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+    gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiDnD *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);
+    free(uriarray);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+    if(!sel->data) {
+        return NULL;
+    }
+    
+    guchar *text = gtk_selection_data_get_text(sel->data);
+    if(text) {
+        char *textcp = strdup((char*)text);
+        g_free(text);
+        return textcp;
+    }
+    return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+    if(!sel->data) {
+        return (UiFileList){NULL,0};
+    }
+    
+    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++;
+        }
+        g_strfreev(uris);
+        return (UiFileList){array,i};
+    }
+    
+    return (UiFileList){NULL,0};
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+    switch(dnd->selected_action) {
+        case 0: return UI_DND_ACTION_NONE;
+        case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+        case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+        case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+        default: break;
+    }
+    return UI_DND_ACTION_CUSTOM;
+}
+
+
+UiDnD* ui_create_dnd(void) {
+    UiDnD *dnd = malloc(sizeof(UiDnD));
+    memset(dnd, 0, sizeof(UiDnD));
+    return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+    free(dnd);
+}
+
+#endif
+
+UiBool ui_dnd_need_delete(UiDnD *dnd) {
+    return dnd->delete;
+}
+
+void ui_dnd_accept(UiDnD *dnd, UiBool accept) {
+    dnd->accept = accept;
+}
+
diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h
new file mode 100644 (file)
index 0000000..e245f99
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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"
+
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 4
+    
+struct UiDnD {
+    GtkDropTarget *target;
+    const GValue *value;
+    CxList *providers;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
+    
+#else
+   
+struct UiDnD {
+    GdkDragContext *context;
+    GtkSelectionData *data;
+    GdkDragAction selected_action;
+    gboolean delete;
+    gboolean accept;
+};
+
+#endif
+
+UiDnD* ui_create_dnd(void);
+void ui_dnd_free(UiDnD *dnd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
diff --git a/ui/gtk/draw_cairo.c b/ui/gtk/draw_cairo.c
new file mode 100644 (file)
index 0000000..fc85c32
--- /dev/null
@@ -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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_cairo.h"
+
+
+#if GTK_MAJOR_VERSION >= 3
+static void ui_drawingarea_draw(
+        GtkDrawingArea *area,
+        cairo_t *cr,
+        int width,
+        int height,
+        gpointer data)
+{
+    UiCairoGraphics g;
+    g.g.width = width;
+    g.g.height = height;
+    g.widget = GTK_WIDGET(area);
+    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);
+}
+#endif
+
+#if UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+    int width = gtk_widget_get_allocated_width(w);
+    int height = gtk_widget_get_allocated_height(w);
+    ui_drawingarea_draw(GTK_DRAWING_AREA(w), cr, width, height, data);
+    return FALSE;
+}
+#endif
+#ifdef UI_GTK2
+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) {
+#if GTK_MAJOR_VERSION >= 4
+    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), ui_drawingarea_draw, event, NULL);
+#elif GTK_MAJOR_VERSION == 3
+    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);
+}
+
diff --git a/ui/gtk/draw_cairo.h b/ui/gtk/draw_cairo.h
new file mode 100644 (file)
index 0000000..6be96a7
--- /dev/null
@@ -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 */
+
diff --git a/ui/gtk/draw_gdk.c b/ui/gtk/draw_gdk.c
new file mode 100644 (file)
index 0000000..5e43ed7
--- /dev/null
@@ -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);
+}
diff --git a/ui/gtk/draw_gdk.h b/ui/gtk/draw_gdk.h
new file mode 100644 (file)
index 0000000..f4aa821
--- /dev/null
@@ -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 */
+
diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c
new file mode 100644 (file)
index 0000000..38ebe17
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * 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_create(UiObject *obj, UiSpinnerArgs args) {
+    double min = 0;
+    double max = 1000;
+    
+    UiObject* current = uic_current_obj(obj);
+    
+    UiVar *var = NULL;
+    if(args.varname) {
+        var = uic_get_var(obj->ctx, args.varname);
+    }
+    
+    if(!var) {
+        if(args.intvalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.intvalue, NULL, UI_VAR_INTEGER);
+        } else if(args.doublevalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.doublevalue, NULL, UI_VAR_DOUBLE);
+        } else if(args.rangevalue) {
+            var = uic_widget_var(obj->ctx, current->ctx, args.rangevalue, NULL, UI_VAR_RANGE);
+        }
+    }
+    
+    if(var && var->type == UI_VAR_RANGE) {
+        UiRange *r = var->value;
+        min = r->min;
+        max = r->max;
+    }
+    if(args.step == 0) {
+        args.step = 1;
+    }
+#ifdef UI_GTK2LEGACY
+    if(min == max) {
+        max = min + 1;
+    }
+#endif
+    GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args.step);
+    ui_set_name_and_style(spin, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, spin, args.groups);
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args.digits);
+    UiObserver **obs = NULL;
+    if(var) {
+        double value = 0;
+        switch(var->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;
+    event->callback = args.onchange;
+    event->userdata = args.onchangedata;
+
+    g_signal_connect(
+            spin,
+            "value-changed",
+            G_CALLBACK(ui_spinner_changed),
+            event);
+    g_signal_connect(
+            spin,
+            "destroy",
+            G_CALLBACK(ui_destroy_vardata),
+            event);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, 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) {
+    gdouble value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner));
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = &value;
+    e.intval = (int64_t)value;
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(event->observers) {
+        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;
+}
diff --git a/ui/gtk/entry.h b/ui/gtk/entry.h
new file mode 100644 (file)
index 0000000..ae43b0f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+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 */
+
diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c
new file mode 100644 (file)
index 0000000..dcaff88
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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;
+}
+
+
+#if GTK_MAJOR_VERSION <= 3
+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;
+}
+#endif
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+#if GTK_MAJOR_VERSION >= 4
+    *width = gtk_widget_get_width(drawingarea);
+    *height = gtk_widget_get_height(drawingarea);
+#elif GTK_MAJOR_VERSION == 3
+    *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) {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO
+#else
+    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;
+        event->customdata = NULL;
+        event->value = 0;
+        
+        g_signal_connect(G_OBJECT(widget),
+                "button-press-event",
+                G_CALLBACK(widget_button_pressed),
+                event);
+    } else {
+         // TODO: warning
+    }
+#endif
+}
+
+
+// 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);
+}
diff --git a/ui/gtk/graphics.h b/ui/gtk/graphics.h
new file mode 100644 (file)
index 0000000..67616bd
--- /dev/null
@@ -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 */
+
diff --git a/ui/gtk/headerbar.c b/ui/gtk/headerbar.c
new file mode 100644 (file)
index 0000000..e1b80b5
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "headerbar.h"
+
+#include "button.h"
+#include "menu.h"
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) {
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT);
+    ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER);
+    
+    UiToolbarMenuItem *appmenu = uic_get_appmenu();
+    if(appmenu) {
+        ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT);
+    }
+    ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT);
+}
+
+static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) {
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            ui_add_headerbar_item(headerbar, box, (UiToolbarItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            ui_add_headerbar_toggleitem(headerbar, box, (UiToolbarToggleItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            ui_add_headerbar_menu(headerbar, box, (UiToolbarMenuItem*)i, obj, pos);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+static void headerbar_add(GtkWidget *headerbar, GtkWidget *box, GtkWidget *item, enum UiToolbarPos pos) {
+    switch(pos) {
+        case UI_TOOLBAR_LEFT: {
+            UI_HEADERBAR_PACK_START(headerbar, item);
+            break;
+        }
+        case UI_TOOLBAR_CENTER: {
+            
+#if GTK_MAJOR_VERSION >= 4
+            gtk_box_append(GTK_BOX(box), item);
+#else
+            gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0);
+#endif
+            break;
+        }
+        case UI_TOOLBAR_RIGHT: {
+            UI_HEADERBAR_PACK_END(headerbar, item);
+            break;
+        }
+    }
+}
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos) {
+    GtkWidget *box = NULL;
+    
+    if(pos == UI_TOOLBAR_CENTER && cxListSize(items) > 0) {
+        box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+        UI_HEADERBAR_SET_TITLE_WIDGET(headerbar, box);
+    }
+    
+    CxIterator i = pos == UI_TOOLBAR_RIGHT ? cxListBackwardsIterator(items) : cxListIterator(items);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        create_item(obj, headerbar, box, item, pos);
+    }
+}
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE);
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    WIDGET_ADD_CSS_CLASS(button, "flat");
+    headerbar_add(headerbar, box, button, pos);
+}
+
+void ui_add_headerbar_toggleitem(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarToggleItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *button = gtk_toggle_button_new();
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    WIDGET_ADD_CSS_CLASS(button, "flat");
+    ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0);
+    headerbar_add(headerbar, box, button, pos);
+}
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    
+    
+#if GTK_MAJOR_VERSION >= 4
+    GtkWidget *menubutton = gtk_menu_button_new();
+    if(item->args.label) {
+        gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label);
+    }
+    if(item->args.icon) {
+        gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); 
+    }
+    
+    if(!item->args.label && !item->args.icon) {
+        gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), "open-menu-symbolic"); 
+    }
+    
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, &item->menu, obj);
+    
+    gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu));
+#else
+    GtkWidget *menubutton = gtk_menu_button_new();
+    
+    // TODO
+    
+    
+#endif
+    
+    headerbar_add(headerbar, box, menubutton, pos);
+}
+    
+#endif // GTK_CHECK_VERSION(3, 10, 0)
diff --git a/ui/gtk/headerbar.h b/ui/gtk/headerbar.h
new file mode 100644 (file)
index 0000000..acea9fe
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HEADERBAR_H
+#define HEADERBAR_H
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+    
+#ifdef UI_LIBADWAITA
+#define UI_HEADERBAR AdwHeaderBar*
+#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR GtkHeaderBar*
+#define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w)
+#if GTK_MAJOR_VERSION >= 4
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w)
+#endif
+#endif
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar);
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_toggleitem(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarToggleItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+    
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HEADERBAR_H */
+
diff --git a/ui/gtk/icon.c b/ui/gtk/icon.c
new file mode 100644 (file)
index 0000000..608ad04
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cx/map.h>
+
+#include "toolkit.h"
+#include "icon.h"
+#include "../common/properties.h"
+
+static CxMap *image_map;
+
+static GtkIconTheme *icon_theme;
+
+#if GTK_MAJOR_VERSION >= 4
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_for_display(gdk_display_get_default())
+#else
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_default()
+#endif
+
+void ui_image_init(void) {
+    image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    
+    icon_theme = ICONTHEME_GET_DEFAULT();
+}
+
+// **** deprecated functions ****
+
+GdkPixbuf* ui_get_image(const char *name) {
+    UiImage *img = cxMapGet(image_map, name);
+    if(img) {
+        return img->pixbuf;
+    } else {
+        //ui_add_image(name, name);
+        //return ucx_map_cstr_get(image_map, name);
+        // TODO
+        return NULL;
+    }
+}
+
+// **** deprecated2****
+
+static UiIcon* get_icon(const char *name, int size, int scale) {
+#if GTK_MAJOR_VERSION >= 4
+    GtkIconPaintable *info = gtk_icon_theme_lookup_icon(icon_theme, name, NULL, size, scale, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR);
+#elif defined(UI_SUPPORTS_SCALE)
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
+#else
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+#endif
+    if(info) {
+        UiIcon *icon = malloc(sizeof(UiIcon));
+        icon->info = info;
+        icon->pixbuf = NULL;
+        return icon;
+    }
+    return NULL;
+}
+
+UiIcon* ui_icon(const char* name, size_t size) {
+    return get_icon(name, size, ui_get_scalefactor());
+}
+
+UiIcon* ui_imageicon(const char* file) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(file, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", file);
+        return NULL;
+    }
+    
+    UiIcon *icon = malloc(sizeof(UiIcon));
+    icon->info = NULL;
+    icon->pixbuf = pixbuf;
+    return icon;
+}
+
+void ui_icon_free(UiIcon* icon) {
+    if(icon->info) {
+        g_object_unref(icon->info);
+    }
+    if(icon->pixbuf) {
+        g_object_unref(icon->pixbuf);
+    }
+    free(icon);
+}
+
+UiIcon* ui_foldericon(size_t size) {
+    return ui_icon("folder", size);
+}
+
+UiIcon* ui_fileicon(size_t size) {
+    UiIcon *icon = ui_icon("file", size);
+#if GTK_MAJOR_VERSION >= 4
+    GFile *file = gtk_icon_paintable_get_file(icon->info);
+    char *path = g_file_get_path(file);
+    if(!path) {
+        icon = ui_icon("application-x-generic", size);
+    }
+#else
+    if(!icon) {
+        icon = ui_icon("application-x-generic", size);
+    }
+#endif
+    return icon;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+    return get_icon(name, size, 1);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+    if(!icon->pixbuf) {
+        GFile *file = gtk_icon_paintable_get_file(icon->info);
+        GError *error = NULL;
+        char *path = g_file_get_path(file);
+        icon->pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    }
+    return icon->pixbuf;
+}
+#else
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+    if(!icon->pixbuf) {
+        GError *error = NULL;
+        icon->pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    }
+    return icon->pixbuf;
+}
+#endif
+
+/*
+UiImage* ui_icon_image(UiIcon *icon) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    if(pixbuf) {
+        UiImage *img = malloc(sizeof(UiImage));
+        img->pixbuf = pixbuf;
+        return img;
+    }
+    return NULL;
+}
+*/
+
+/*
+UiImage* ui_image(const char *filename) {
+    return ui_named_image(filename, NULL);
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+    char *path =  uic_get_image_path(filename);
+    if(!path) {
+        fprintf(stderr, "UiError: pixmaps directory not set\n");
+        return NULL;
+    }
+    UiImage *img = ui_load_image_from_path(path, name);
+    free(path);
+    return img;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", path);
+        return NULL;
+    }
+    
+    UiImage *img = malloc(sizeof(UiImage));
+    img->pixbuf = pixbuf;
+    if(name) {
+        cxMapPut(image_map, name, img);
+    }
+    return img;
+}
+*/
+
+void ui_free_image(UiImage *img) {
+    g_object_unref(img->pixbuf);
+    free(img);
+}
diff --git a/ui/gtk/icon.h b/ui/gtk/icon.h
new file mode 100644 (file)
index 0000000..d811817
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ICON_H
+#define        ICON_H
+
+#include "../ui/icons.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+    
+struct UiIcon {
+#if GTK_MAJOR_VERSION >= 4
+    GtkIconPaintable *info;
+#else
+    GtkIconInfo *info;
+#endif
+    GdkPixbuf *pixbuf;
+};
+
+struct UiImage {
+    GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ICON_H */
+
diff --git a/ui/gtk/image.c b/ui/gtk/image.c
new file mode 100644 (file)
index 0000000..3c2a0e6
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "image.h"
+
+#include "container.h"
+#include "menu.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
+    UiObject *current = uic_current_obj(obj);
+    
+    GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW();
+#if GTK_CHECK_VERSION(4, 0, 0)
+    GtkWidget *image = gtk_picture_new();
+#else
+    GtkWidget *image = gtk_image_new();
+#endif
+    
+    ui_set_name_and_style(image, args.name, args.style_class);
+    
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *eventbox = gtk_event_box_new();
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
+    gtk_container_add(GTK_CONTAINER(eventbox), image);
+#else
+    SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
+    GtkWidget *eventbox = image;
+#endif
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scrolledwindow, TRUE);
+    
+    UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
+    if(var) {
+        UiGeneric *value = var->value;
+        value->get = ui_imageviewer_get;
+        value->get_type = ui_imageviewer_get_type;
+        value->set = ui_imageviewer_set;
+        value->obj = image;
+        if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) {
+            GdkPixbuf *pixbuf = value->value;
+            value->value = NULL;
+            ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE);
+        }
+    }
+    
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
+        ui_widget_set_contextmenu(eventbox, menu);
+    }
+    
+    return scrolledwindow;
+}
+
+void* ui_imageviewer_get(UiGeneric *g) {
+    return g->value;
+}
+
+const char* ui_imageviewer_get_type(UiGeneric *g) {
+    
+}
+
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
+    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
+        return 1;
+    }
+    
+    // TODO: do we need to free the previous value here?
+    
+    g->value = value;
+    g->type = type;
+    GdkPixbuf *pixbuf = value;
+    
+    if(pixbuf) {
+        int width = gdk_pixbuf_get_width(pixbuf);
+        int height = gdk_pixbuf_get_height(pixbuf);
+        
+#if GTK_CHECK_VERSION(4, 0, 0)
+        GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
+        gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
+#else
+        gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf);
+#endif
+        gtk_widget_set_size_request(g->obj, width, height);
+    }
+
+    
+    return 0;
+}
+
+
+
+int ui_image_load_file(UiGeneric *obj, const char *path) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    if(!pixbuf) {
+        return 1;
+    }
+    
+    if(obj->set) {
+        obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
+    } else {
+        obj->value = pixbuf;
+    }
+    
+    return 0;
+}
diff --git a/ui/gtk/image.h b/ui/gtk/image.h
new file mode 100644 (file)
index 0000000..00c205d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void* ui_imageviewer_get(UiGeneric *g);
+const char* ui_imageviewer_get_type(UiGeneric *g);
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
diff --git a/ui/gtk/list.c b/ui/gtk/list.c
new file mode 100644 (file)
index 0000000..34bf993
--- /dev/null
@@ -0,0 +1,984 @@
+/*
+ * 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 <cx/array_list.h>
+
+#include "list.h"
+#include "icon.h"
+#include "menu.h"
+#include "dnd.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
+}
+
+static GtkListStore* create_list_store(UiList *list, UiModel *model) {
+    int columns = model->columns;
+    GType types[2*columns];
+    int c = 0;
+    for(int i=0;i<columns;i++,c++) {
+        switch(model->types[i]) {
+            case UI_STRING: 
+            case UI_STRING_FREE: types[c] = G_TYPE_STRING; break;
+            case UI_INTEGER: types[c] = G_TYPE_INT; break;
+            case UI_ICON: types[c] = G_TYPE_OBJECT; break;
+            case UI_ICON_TEXT: 
+            case UI_ICON_TEXT_FREE: {
+                types[c] = G_TYPE_OBJECT;
+                types[++c] = G_TYPE_STRING;
+            }
+        }
+    }
+    
+    GtkListStore *store = gtk_list_store_newv(c, types);
+    
+    if(list) {
+        void *elm = list->first(list);
+       while(elm) {
+            // insert new row
+            GtkTreeIter iter;
+            gtk_list_store_insert (store, &iter, -1);
+            
+            // set column values
+            int c = 0;
+            for(int i=0;i<columns;i++,c++) {
+                void *data = model->getvalue(elm, c);
+                
+                GValue value = G_VALUE_INIT;
+                switch(model->types[i]) {
+                    case UI_STRING: 
+                    case UI_STRING_FREE: {
+                        g_value_init(&value, G_TYPE_STRING);
+                        g_value_set_string(&value, data);
+                        if(model->types[i] == UI_STRING_FREE) {
+                            free(data);
+                        }
+                        break;
+                    }
+                    case UI_INTEGER: {
+                        g_value_init(&value, G_TYPE_INT);
+                        int *intptr = data;
+                        g_value_set_int(&value, *intptr);
+                        break;
+                    }
+                    case UI_ICON: {
+                        g_value_init(&value, G_TYPE_OBJECT);
+                        UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                        g_value_set_object(&value, icon->info); // TODO: does this work?
+#else
+                        if(!icon->pixbuf && icon->info) {
+                            GError *error = NULL;
+                            GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                            icon->pixbuf = pixbuf;
+                        }
+                        
+                        if(icon->pixbuf) {
+                            g_value_set_object(&value, icon->pixbuf);
+                        }
+#endif
+                        break;
+                    }
+                    case UI_ICON_TEXT:
+                    case UI_ICON_TEXT_FREE: {
+                        UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+                        if(icon) {
+                            GValue iconvalue = G_VALUE_INIT;
+                            g_value_init(&iconvalue, G_TYPE_OBJECT);
+                            g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
+                            gtk_list_store_set_value(store, &iter, c, &iconvalue);
+                        }
+#else
+                        GValue pixbufvalue = G_VALUE_INIT;
+                        if(icon) {
+                            if(!icon->pixbuf && icon->info) {
+                                GError *error = NULL;
+                                GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+                                icon->pixbuf = pixbuf;
+                            }
+                            g_value_init(&pixbufvalue, G_TYPE_OBJECT);
+                            g_value_set_object(&pixbufvalue, icon->pixbuf);
+                            gtk_list_store_set_value(store, &iter, c, &pixbufvalue);
+                        }
+#endif
+                        c++;
+                        
+                        char *str = model->getvalue(elm, c);
+                        g_value_init(&value, G_TYPE_STRING);
+                        g_value_set_string(&value, str);
+                        if(model->types[i] == UI_ICON_TEXT_FREE) {
+                            free(str);
+                        }
+                        break;
+                    }
+                }
+                
+                gtk_list_store_set_value(store, &iter, c, &value);
+            }
+            
+            // next row
+            elm = list->next(list);
+        }
+    }
+    
+    return store;
+}
+
+
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    ui_set_name_and_style(view, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, view, args.groups);
+    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 = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(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->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
+    list->obj = listview;
+    
+    // add callback
+    UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = args.onactivate;
+    event->activatedata = args.onactivatedata;
+    event->selection = args.onselection;
+    event->selectiondata = args.onselectiondata;
+    g_signal_connect(
+            view,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    if(args.onactivate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(args.onselection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view);
+        ui_widget_set_contextmenu(view, menu);
+    }
+    
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, FALSE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    current->container->current = view;
+    
+    return scroll_area;
+}
+
+/*
+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_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    
+    UiModel *model = args.model;
+    int columns = model ? model->columns : 0;
+    
+    int addi = 0;
+    for(int i=0;i<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 if (model->types[i] == UI_ICON) {
+            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                iconrenderer,
+                "pixbuf",
+                i + addi,
+                NULL);
+        } else {
+            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                renderer,
+                "text",
+                i + addi,
+                NULL);
+        }
+        
+        int colsz = model->columnsize[i];
+        if(colsz > 0) {
+            gtk_tree_view_column_set_fixed_width(column, colsz);
+        } else if(colsz < 0) {
+            gtk_tree_view_column_set_expand(column, TRUE);
+        }
+        
+        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
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(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;
+    tableview->ondragstart = args.ondragstart;
+    tableview->ondragstartdata = args.ondragstartdata;
+    tableview->ondragcomplete = args.ondragcomplete;
+    tableview->ondragcompletedata = args.ondragcompletedata;
+    tableview->ondrop = args.ondrop;
+    tableview->ondropdata = args.ondropsdata;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                tableview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
+    list->obj = tableview;
+    
+    // add callback
+    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = args.onactivate;
+    event->selection = args.onselection;
+    event->activatedata = args.onactivatedata;
+    event->selectiondata = args.onselectiondata;
+    if(args.onactivate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(args.onselection) {
+        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
+    
+    
+    if(args.ondragstart) {
+        ui_listview_add_dnd(tableview, &args);
+    }
+    if(args.ondrop) {
+        ui_listview_enable_drop(tableview, &args);
+    }
+      
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+    if(args.multiselection) {
+        gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+    }
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+    
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area);
+#if GTK_MAJOR_VERSION >= 4
+        ui_widget_set_contextmenu(scroll_area, menu);
+#else
+        ui_widget_set_contextmenu(view, menu);
+#endif
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, FALSE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    current->container->current = view;
+    
+    return scroll_area;
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
+    //printf("drag prepare\n");
+    UiListView *listview = data;
+    
+    UiDnD *dnd = ui_create_dnd();
+    GdkContentProvider *provider = NULL;
+    
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+    
+    size_t numproviders = cxListSize(dnd->providers);
+    if(numproviders > 0) {
+        GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0);
+        provider = gdk_content_provider_new_union(providers, numproviders);
+    }
+    ui_dnd_free(dnd);
+    
+    return provider;
+}
+
+static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) {
+    //printf("drag begin\n");
+}
+
+static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) {
+    //printf("drag end\n");
+    UiListView *listview = user_data;
+    if(listview->ondragcomplete) {
+        UiDnD dnd;
+        dnd.target = NULL;
+        dnd.value = NULL;
+        dnd.providers = NULL;
+        dnd.selected_action = gdk_drag_get_selected_action(drag);
+        dnd.delete = delete_data;
+        dnd.accept = FALSE;
+        
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+static gboolean ui_listview_drop(
+        GtkDropTarget *target,
+        const GValue* value,
+        gdouble x,
+        gdouble y,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.providers = NULL;
+    dnd.target = target;
+    dnd.value = value;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+    
+    return dnd.accept;
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    GtkDragSource *dragsource = gtk_drag_source_new();
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource));
+    g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview);
+    g_signal_connect(
+            dragsource,
+            "drag-begin",
+            G_CALLBACK(ui_listview_drag_begin),
+            listview);
+    g_signal_connect(
+            dragsource,
+            "drag-end",
+            G_CALLBACK(ui_listview_drag_end),
+            listview);
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
+    gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target));
+    GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING };
+    gtk_drop_target_set_gtypes(target, default_types, 2);
+    g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview);
+}
+
+#else
+
+static GtkTargetEntry targetentries[] =
+{
+    { "STRING",        0, 0 },
+    { "text/plain",    0, 1 },
+    { "text/uri-list", 0, 2 },
+};
+
+static void ui_listview_drag_getdata(
+        GtkWidget* self,
+        GdkDragContext* context,
+        GtkSelectionData* data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondragstart) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragstart(&event, listview->ondragstartdata);
+    }
+}
+
+static void ui_listview_drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = NULL;
+    dnd.selected_action = gdk_drag_context_get_selected_action(context);
+    dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE;
+    dnd.accept = FALSE;
+    if(listview->ondragcomplete) {
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondragcomplete(&event, listview->ondragcompletedata);
+    }
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(listview->widget),
+            GDK_BUTTON1_MASK,
+            targetentries,
+            2,
+            GDK_ACTION_COPY);
+    
+    g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview);
+    g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview);
+}
+
+
+
+
+static void ui_listview_drag_data_received(
+        GtkWidget *self,
+        GdkDragContext *context,
+        gint x,
+        gint y,
+        GtkSelectionData *data,
+        guint info,
+        guint time,
+        gpointer user_data)
+{
+    UiListView *listview = user_data;
+    UiDnD dnd;
+    dnd.context = context;
+    dnd.data = data;
+    dnd.selected_action = 0;
+    dnd.delete = FALSE;
+    dnd.accept = FALSE;
+    
+    if(listview->ondrop) {
+        dnd.accept = TRUE;
+        UiEvent event;
+        event.obj = listview->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &dnd;
+        event.intval = 0;
+        listview->ondrop(&event, listview->ondropdata);
+    }
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(listview->widget),
+            targetentries,
+            3,
+            GDK_ACTION_COPY);
+    if(listview->ondrop) {
+        g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview);
+    }
+}
+
+#endif
+
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+    return SCROLLEDWINDOW_GET_CHILD(widget);
+}
+
+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);
+    
+    // disabled
+    //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;
+    GtkListStore *store = create_list_store(list, view->model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
+    g_object_unref(G_OBJECT(store));
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+    UiListView *view = list->obj;
+    UiListSelection selection = ui_listview_selection(
+            gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)),
+            NULL);
+    return selection;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+    GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+    gtk_tree_selection_select_path(sel, path);
+    //g_object_unref(path);
+}
+
+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);
+    free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    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->activatedata);
+    
+    if(selection.count > 0) {
+        free(selection.rows);
+    }
+}
+
+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->selectiondata);
+    
+    if(selection.count > 0) {
+        free(selection.rows);
+    }
+}
+
+UiListSelection ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event)
+{
+    GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+    
+    UiListSelection ls;
+    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_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
+    ui_set_name_and_style(combobox, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, combobox, args.groups);
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, combobox, FALSE);
+    current->container->current = combobox;
+    return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
+    GtkWidget *combobox = gtk_combo_box_new();
+       
+    UiListView *uicbox = malloc(sizeof(UiListView));
+    uicbox->obj = obj;
+    uicbox->widget = combobox;
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(list, model);
+    
+    if(listmodel) {
+        gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
+        g_object_unref(listmodel);
+    }
+    
+    uicbox->var = var;
+    uicbox->model = model;
+    
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_combobox_destroy),
+                uicbox);
+    
+    // bind var
+    if(list) {
+        list->update = ui_combobox_modelupdate;
+        list->getselection = ui_combobox_getselection;
+        list->setselection = ui_combobox_setselection;
+        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;
+        event->customdata = NULL;
+
+        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;
+    GtkListStore *store = create_list_store(view->var->value, view->model);
+    gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
+    g_object_unref(store);
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+    UiListView *combobox = list->obj;
+    UiListSelection ret;
+    ret.rows = malloc(sizeof(int*));
+    ret.count = 1;
+    ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
+    return ret;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    UiListView *combobox = list->obj;
+    if(selection.count > 0) {
+        gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
+    }
+}
diff --git a/ui/gtk/list.h b/ui/gtk/list.h
new file mode 100644 (file)
index 0000000..3978fb3
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    UiObject    *obj;
+    GtkWidget   *widget;
+    UiVar       *var;
+    UiModel     *model;
+    ui_callback ondragstart;
+    void        *ondragstartdata;
+    ui_callback ondragcomplete;
+    void        *ondragcompletedata;
+    ui_callback ondrop;
+    void        *ondropdata;
+    
+} UiListView;
+
+typedef struct UiTreeEventData {
+    UiObject    *obj;
+    ui_callback activate;
+    ui_callback selection;
+    void        *activatedata;
+    void        *selectiondata;
+} 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);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+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);
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args);
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_modelupdate(UiList *list, int i);
+UiListSelection ui_combobox_getselection(UiList *list);
+void ui_combobox_setselection(UiList *list, UiListSelection selection);
+        
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c
new file mode 100644 (file)
index 0000000..5e5dec0
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+ * 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 "../common/types.h"
+#include "../ui/properties.h"
+#include "../ui/window.h"
+#include "container.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#if GTK_MAJOR_VERSION <= 3
+
+static ui_menu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ add_menu_widget,
+    /* UI_MENU_ITEM            */ add_menuitem_widget,
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
+};
+
+// 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 ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it, obj);
+        it = it->next;
+        index++;
+    }
+}
+
+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);
+    
+    ui_add_menu_items(menu_widget, i, menu, obj);
+    
+    
+    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;
+        event->customdata = NULL;
+
+        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) {
+        CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
+        cxListAddArray(groups, i->groups, i->ngroups);
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+        cxListDestroy(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) {
+    UiMenuCheckItem *ci = (UiMenuCheckItem*)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;
+        event->customdata = NULL;
+        
+        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_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    // TODO
+}
+
+/*
+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->getvalue = il->getvalue;
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    ls->list = var->value;
+    
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
+    ls->list->observers = ui_obsvlist_add(ls->list->observers, observer);
+    uic_list_register_observer_destructor(obj->ctx, ls->list, observer);
+    
+    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++;
+        }
+    }
+    
+    void* elm = ui_list_first(list->list);
+    if(elm) {
+        GtkWidget *widget = gtk_separator_menu_item_new();
+        gtk_menu_shell_insert(list->menu, widget, list->index);
+        gtk_widget_show(widget);
+    }
+    
+    ui_getvaluefunc getvalue = list->getvalue;
+    int i = 1;
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        
+        GtkWidget *widget = gtk_menu_item_new_with_label(label);
+        gtk_menu_shell_insert(list->menu, widget, list->index + i);
+        gtk_widget_show(widget);
+        
+        if(list->callback) {
+            UiEventData *event = malloc(sizeof(UiEventData));
+            event->obj = list->object;
+            event->userdata = list->userdata;
+            event->callback = list->callback;
+            event->value = i - 1;
+            event->customdata = elm;
+
+            g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+            g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+        }
+        
+        elm = 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 = event->customdata;
+    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
+ */
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+    GtkWidget *menu_widget = gtk_menu_new();
+    ui_add_menu_items(menu_widget, 0, builder->menus_begin, obj);
+    return GTK_MENU(menu_widget);
+}
+
+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, widget, 0, 0);
+        }
+    }
+    return FALSE;
+}
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkMenu *menu) {
+    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, GtkWidget *widget, int x, int y) {
+#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
+}
+
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+
+
+#if GTK_MAJOR_VERSION >= 4
+
+
+
+static ui_gmenu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ ui_gmenu_add_menu,
+    /* UI_MENU_ITEM            */ ui_gmenu_add_menuitem,
+    /* UI_MENU_CHECK_ITEM      */ ui_gmenu_add_checkitem,
+    /* UI_MENU_RADIO_ITEM      */ ui_gmenu_add_radioitem,
+    /* UI_MENU_ITEM_LIST       */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_CHECKITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_RADIOITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_SEPARATOR       */ ui_gmenu_add_menuseparator
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    int index_section = 0;
+    GMenu *section = NULL;
+    while(it) {
+        if(it->type == UI_MENU_SEPARATOR) {
+            section = g_menu_new();
+            g_menu_append_section(parent, NULL, G_MENU_MODEL(section));
+            index++;
+            index_section = 0;
+        } else {
+            if(section) {
+                createMenuItem[it->type](section, index_section++, it, obj);
+            } else {
+                createMenuItem[it->type](parent, index++, it, obj);
+            }
+        }
+        it = it->next;
+    }
+}
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *mi = (UiMenu*)item;
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, mi, obj);
+    g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu));
+}
+
+static void action_enable(GSimpleAction *action, int enabled) {
+    g_simple_action_set_enabled(action, enabled);
+}
+
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *i = (UiMenuItem*)item;
+     
+    GSimpleAction *action = g_simple_action_new(item->id, NULL);
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    
+    if(i->groups) {
+        CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
+        cxListAddArray(groups, i->groups, i->ngroups);
+        uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups);
+        cxListDestroy(groups);
+    }
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+        event->customdata = NULL;
+
+        g_signal_connect(
+                action,
+                "activate",
+                G_CALLBACK(ui_activate_event_wrapper),
+                event);
+        g_signal_connect(
+                obj->widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    char action_name[32];
+    snprintf(action_name, 32, "win.%s", item->id); 
+    g_menu_append(parent, i->label, action_name);
+}
+
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    
+}
+
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item;
+    
+    // TODO
+}
+
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    
+}
+
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item; 
+    
+    const CxAllocator *a = obj->ctx->allocator;
+    
+    UiActiveGMenuItemList *ls = cxMalloc(
+            a,
+            sizeof(UiActiveGMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = p;
+    ls->index = index;
+    ls->oldcount = 0;
+    ls->getvalue = il->getvalue;
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    ls->var = var;
+    UiList *list = var->value;
+    
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    UiObserver *observer = ui_observer_new((ui_callback)ui_update_gmenu_item_list, ls);
+    list->observers = ui_obsvlist_add(list->observers, observer);
+    uic_list_register_observer_destructor(obj->ctx, list, observer);
+    
+    GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    snprintf(ls->action, 32, "win.%s", item->id);
+    
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = il->userdata;
+    event->callback = il->callback;
+    event->customdata = var;
+    event->value = 0;
+    
+    g_signal_connect(
+            action,
+            "activate",
+            G_CALLBACK(ui_menu_list_item_activate_event_wrapper),
+            event);
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    ui_update_gmenu_item_list(NULL, ls);
+}
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int intval = event->value;
+    if(parameter && g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
+        intval = g_variant_get_int32(parameter);
+    }
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = event->customdata;
+    evt.intval = intval;
+    event->callback(&evt, event->userdata);    
+}
+
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int index = g_variant_get_int32(parameter);
+    UiVar *var = event->customdata;
+    UiList *list = var->value;
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = ui_list_get(list, index);
+    evt.intval = index;
+    event->callback(&evt, event->userdata);    
+    
+}
+
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) {
+    // remove old items
+    for(int i=0;i<list->oldcount;i++) {
+        g_menu_remove(list->menu, list->index);
+    }
+    UiList *ls = list->var->value;
+    
+    // add list items
+    ui_getvaluefunc getvalue = list->getvalue;
+    int i = 0;
+    void* elm = ui_list_first(ls);
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        
+        GMenuItem *item = g_menu_item_new(label, NULL);
+        GVariant *v = g_variant_new("i", i);
+        g_menu_item_set_action_and_target_value(item, list->action, v);
+        g_menu_insert_item(list->menu, list->index+i, item);
+        
+        elm = ui_list_next(ls);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
+
+
+/* --------------------- context menu / menubuilder --------------------- */
+
+static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) {
+    gtk_widget_unparent(GTK_WIDGET(menu));
+}
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) {
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj);
+    GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
+    gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE);
+    gtk_widget_set_halign(contextmenu, GTK_ALIGN_START);
+    gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget);
+    g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(remove_popover),
+                contextmenu);
+    return GTK_POPOVER_MENU(contextmenu);
+}
+
+static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(user_data));
+}
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) {
+    GtkGesture *gesture = gtk_gesture_click_new();
+    gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+    gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture));
+    g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(menu));
+}
+
+#endif
diff --git a/ui/gtk/menu.h b/ui/gtk/menu.h
new file mode 100644 (file)
index 0000000..1963bb1
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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
+    
+UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj);
+void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu);
+    
+#if GTK_MAJOR_VERSION <= 3
+
+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_getvaluefunc getvalue;
+    ui_callback     callback;
+    void            *userdata;
+};
+
+GtkWidget *ui_create_menubar(UiObject *obj);
+
+void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, 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_radioitem_widget(GtkWidget *p, int index, 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);
+
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+#if GTK_MAJOR_VERSION >= 4
+
+typedef void(*ui_gmenu_add_f)(GMenu *, int, UiMenuItemI*, UiObject*);
+
+typedef struct UiActiveGMenuItemList UiActiveGMenuItemList;
+struct UiActiveGMenuItemList {
+    UiObject         *object;
+    GMenu            *menu;
+    char             action[32];
+    int              index;
+    int              oldcount;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj);
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list);
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_H */
+
diff --git a/ui/gtk/objs.mk b/ui/gtk/objs.mk
new file mode 100644 (file)
index 0000000..865216e
--- /dev/null
@@ -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.
+#
+
+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 += list.o
+GTKOBJ += image.o
+GTKOBJ += icon.o
+GTKOBJ += graphics.o
+GTKOBJ += range.o
+GTKOBJ += entry.o
+GTKOBJ += dnd.o
+GTKOBJ += headerbar.o
+
+TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
+TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
diff --git a/ui/gtk/range.c b/ui/gtk/range.c
new file mode 100644 (file)
index 0000000..c992159
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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) {
+#if GTK_MAJOR_VERSION >= 3
+    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;
+        event->customdata = NULL;
+        
+        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 * 100 + GTK_MIMOR_VERSION < 318
+    gtk_adjustment_changed(a);
+#endif
+    range->extent = extent;
+}
diff --git a/ui/gtk/range.h b/ui/gtk/range.h
new file mode 100644 (file)
index 0000000..0856e3b
--- /dev/null
@@ -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 */
+
diff --git a/ui/gtk/text.c b/ui/gtk/text.c
new file mode 100644 (file)
index 0000000..fe8aac3
--- /dev/null
@@ -0,0 +1,1123 @@
+/*
+ * 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 <cx/printf.h>
+
+#include <gdk/gdkkeysyms.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_create(UiObject *obj, UiTextAreaArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);
+    
+    GtkWidget *text_area = gtk_text_view_new();
+    ui_set_name_and_style(text_area, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, text_area, args.groups);
+    
+    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->obj = obj;
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    uitext->last_selection_state = 0;
+    uitext->onchange = args.onchange;
+    uitext->onchangedata = args.onchangedata;
+    
+    g_signal_connect(
+                text_area,
+                "destroy",
+                G_CALLBACK(ui_textarea_destroy),
+                uitext);
+    
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
+    
+    // font and padding
+    //PangoFontDescription *font;
+    //font = pango_font_description_from_string("Monospace");
+    //gtk_widget_modify_font(text_area, font); // TODO
+    //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
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, scroll_area, TRUE);
+    
+    // bind value
+    if(var) {
+        UiText *value = var->value;
+        GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
+        
+        if(value->value.ptr) {
+            gtk_text_buffer_set_text(buf, value->value.ptr, -1);
+            value->value.free(value->value.ptr);
+        }
+        
+        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) {
+    if(textarea->var) {
+        ui_destroy_boundvar(textarea->ctx, textarea->var);
+    }
+    free(textarea);
+}
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
+    return SCROLLEDWINDOW_GET_CHILD(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, const 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;
+    
+    UiEvent e;
+    e.obj = textarea->obj;
+    e.window = e.obj->window;
+    e.document = textarea->ctx->document;
+    e.eventdata = value;
+    e.intval = 0;
+    
+    if(textarea->onchange) {
+        textarea->onchange(&e, textarea->onchangedata);
+    }
+    
+    if(value->observers) {
+        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(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
+    GtkWidget *textfield = gtk_entry_new();
+    ui_set_name_and_style(textfield, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, textfield, args.groups);
+    
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    
+    UiTextField *uitext = malloc(sizeof(UiTextField));
+    uitext->obj = obj;
+    uitext->var = var;
+    uitext->onchange = args.onchange;
+    uitext->onchangedata = args.onchangedata;
+    
+    g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(ui_textfield_destroy),
+                uitext);
+    
+    if(args.width > 0) {
+        // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
+#endif
+    }
+    if(frameless) {
+        // TODO: gtk2legacy workaroud
+        gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
+    }
+    if(password) {
+        gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+    }
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, textfield, FALSE);
+    
+    if(var) {
+        UiString *value = var->value;
+        if(value->value.ptr) {
+            ENTRY_SET_TEXT(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);
+    }
+    
+    if(args.onchange || var) {
+        g_signal_connect(
+                textfield,
+                "changed",
+                G_CALLBACK(ui_textfield_changed),
+                uitext);
+    }
+    
+    return textfield;
+}
+
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, FALSE, args);
+}
+
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, TRUE, FALSE, args);
+}
+
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, TRUE, args);
+}
+
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
+    free(textfield);
+}
+
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+    UiString *value = textfield->var->value;
+    
+    UiEvent e;
+    e.obj = textfield->obj;
+    e.window = e.obj->window;
+    e.document = textfield->obj->ctx->document;
+    e.eventdata = value;
+    e.intval = 0;
+    
+    if(textfield->onchange) {
+        textfield->onchange(&e, textfield->onchangedata);
+    }
+    
+    if(textfield->var) {
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_textfield_set(UiString *str, const char *value) {
+    ENTRY_SET_TEXT(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
+
+// ----------------------- path textfield -----------------------
+
+// TODO: move to common
+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 NULL;
+    }
+
+    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;
+}
+
+static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
+    for(int i=0;i<nelm;i++) {
+        free(elms[i].name);
+        free(elms[i].path);
+    }
+    free(elms);
+}
+
+static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
+    g_object_unref(pathtf->entry);
+    free(pathtf);
+}
+
+void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) {
+    UiPathTextField *pathtf = event->customdata1;
+    for(int i=0;i<event->value1;i++) {
+        if(i <= event->value0) {
+            WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+        } else {
+            WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+        }
+    }
+    
+    UiPathElm *elm = event->customdata0;
+    cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = evt.obj->window;
+    evt.document = evt.obj->ctx->document;
+    evt.eventdata = elm->path;
+    evt.intval = event->value0;
+    event->callback(&evt, event->userdata);
+    free(path.ptr);
+}
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) {
+    size_t full_path_len = strlen(full_path);
+    if(full_path_len == 0) {
+        return 1;
+    }
+    
+    size_t nelm = 0;
+    UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
+    if (!path_elm) {
+        return 1;
+    }
+    
+    free(pathtf->current_path);
+    pathtf->current_path = strdup(full_path);
+    
+    ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
+    free(pathtf->current_path_buttons);
+    pathtf->current_path_buttons = calloc(nelm, sizeof(GtkWidget*));
+    pathtf->current_pathelms = path_elm;
+    pathtf->current_nelm = nelm;
+    
+    return ui_pathtextfield_update_widget(pathtf);
+}
+
+static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) {
+    cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
+    GtkWidget *button = gtk_button_new_with_label(name.ptr);
+    pathtf->current_path_buttons[i] = button;
+    free(name.ptr);
+
+    if(pathtf->onactivate) {
+        UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt));
+        memset(eventdata, 0, sizeof(UiEventDataExt));
+        eventdata->callback = pathtf->onactivate;
+        eventdata->userdata = pathtf->onactivatedata;
+        eventdata->obj = pathtf->obj;
+        eventdata->customdata0 = elm;
+        eventdata->customdata1 = pathtf;
+        eventdata->value0 = i;
+        eventdata->value1 = pathtf->current_nelm;        
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_path_button_clicked),
+                eventdata);
+
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                eventdata);
+    }
+    
+    return button;
+}
+
+static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
+    const gchar *text = ENTRY_GET_TEXT(pathtf->entry);
+    if(strlen(text) == 0) {
+        return;
+    }
+    
+    UiObject *obj = pathtf->obj;
+    
+    if(ui_pathtextfield_update(pathtf, text)) {
+        return;
+    }
+    
+    if(pathtf->onactivate) {
+        UiEvent evt;
+        evt.obj = obj;
+        evt.window = obj->window;
+        evt.document = obj->ctx->document;
+        evt.eventdata = (char*)text;
+        evt.intval = -1;
+        pathtf->onactivate(&evt, pathtf->onactivatedata);
+    }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
+    if(pathtf->current_path) {
+        gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+        ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
+    }
+}
+
+static gboolean ui_path_textfield_key_controller(
+        GtkEventControllerKey* self,
+        guint keyval,
+        guint keycode,
+        GdkModifierType state,
+        UiPathTextField *pathtf)
+{
+    if(keyval == GDK_KEY_Escape) {
+        pathbar_show_hbox(NULL, pathtf);
+    }
+    return FALSE;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    pathtf->stack = gtk_stack_new();
+    gtk_widget_set_name(pathtf->stack, "path-textfield-box");
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, pathtf->stack, FALSE);
+    
+    pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    pathtf->entry = gtk_entry_new();
+    gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry);
+    gtk_widget_set_hexpand(pathtf->entry, TRUE);
+    
+    GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic");
+    gtk_widget_add_css_class(cancel_button, "flat");
+    gtk_widget_add_css_class(cancel_button, "pathbar-extra-button");
+    gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button);
+    g_signal_connect(
+                cancel_button,
+                "clicked",
+                G_CALLBACK(pathbar_show_hbox),
+                pathtf);
+    
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    g_object_ref(pathtf->entry); // for compatibility with older pathbar version
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    
+    GtkEventController *entry_cancel = gtk_event_controller_key_new();
+    gtk_widget_add_controller(pathtf->entry, entry_cancel);
+    g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf);
+    
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    
+    
+    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 = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return pathtf->stack;    
+}
+
+static void pathbar_pressed(
+        GtkGestureClick* self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        UiPathTextField *pathtf)
+{
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    gtk_widget_grab_focus(pathtf->entry);
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    // recreate button hbox
+    if(pathtf->hbox) {
+        gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox);
+    }
+    pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE);
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    gtk_widget_set_name(pathtf->hbox, "pathbar");
+    
+    // add buttons for path elements
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_widget_add_css_class(button, "flat");
+        
+        gtk_box_append(GTK_BOX(pathtf->hbox), button);
+        
+        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+            GtkWidget *path_separator = gtk_label_new("/");
+            gtk_widget_add_css_class(path_separator, "pathbar-button-inactive");
+            gtk_box_append(GTK_BOX(pathtf->hbox), path_separator);
+        }
+    }
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    
+    // create a widget for receiving button press events
+    GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    GtkGesture *handler = gtk_gesture_click_new();
+    gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler));
+    g_signal_connect(
+                handler,
+                "pressed",
+                G_CALLBACK(pathbar_pressed),
+                pathtf);
+    gtk_widget_set_hexpand(event_area, TRUE);
+    gtk_widget_set_vexpand(event_area, TRUE);
+    gtk_box_append(GTK_BOX(pathtf->hbox), event_area);
+    
+    return 0;
+}
+
+#else
+
+static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0);
+    gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+    pathtf->buttonbox = NULL;
+    
+    gtk_widget_show(pathtf->entry);
+    gtk_widget_grab_focus(pathtf->entry);    
+    
+    return TRUE;
+}
+
+static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
+    if (event->keyval == GDK_KEY_Escape) {
+        // reset GtkEntry value
+        gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path);
+        const gchar *text = gtk_entry_get_text(GTK_ENTRY(self));
+        ui_pathtextfield_update(pathtf, text);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static GtkWidget* create_path_button_box() {
+    GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style
+    gtk_box_set_homogeneous(GTK_BOX(bb), FALSE);
+    gtk_box_set_spacing(GTK_BOX(bb), 0);
+    return bb;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    // top level container for the path textfield is a GtkEventBox
+    // the event box is needed to handle background button presses
+    GtkWidget *eventbox = gtk_event_box_new();
+    g_signal_connect(
+            eventbox,
+            "button-press-event",
+            G_CALLBACK(path_textfield_btn_pressed),
+            pathtf);
+    g_signal_connect(
+            eventbox,
+            "destroy",
+            G_CALLBACK(ui_path_textfield_destroy),
+            pathtf);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, eventbox, FALSE);
+    
+    // hbox as parent for the GtkEntry and GtkButtonBox
+    GtkWidget *hbox = ui_gtk_hbox_new(0);
+    pathtf->hbox = hbox;
+    gtk_container_add(GTK_CONTAINER(eventbox), hbox);
+    gtk_widget_set_name(hbox, "path-textfield-box");
+    
+    // create GtkEntry, that is also visible by default (with input yet)
+    pathtf->entry = gtk_entry_new();
+    g_object_ref(G_OBJECT(pathtf->entry));
+    gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0);
+    
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    g_signal_connect(
+            pathtf->entry,
+            "key-press-event",
+            G_CALLBACK(ui_path_textfield_key_press),
+            pathtf);
+    
+    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 = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return hbox;
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    GtkWidget *buttonbox = create_path_button_box();
+    
+    // switch from entry to buttonbox or remove current buttonbox
+    if(pathtf->buttonbox) {
+        gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+    } else {
+        gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry);
+    }
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
+    pathtf->buttonbox = buttonbox;
+    
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
+    }
+    
+    gtk_widget_show_all(buttonbox);
+    
+    return 0;
+}
+
+#endif
+
+char* ui_path_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    UiPathTextField *tf = str->obj;
+    str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_path_textfield_set(UiString *str, const char *value) {
+    UiPathTextField *tf = str->obj;
+    ENTRY_SET_TEXT(tf->entry, value);
+    ui_pathtextfield_update(tf, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
diff --git a/ui/gtk/text.h b/ui/gtk/text.h
new file mode 100644 (file)
index 0000000..6e8d198
--- /dev/null
@@ -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.
+ */
+
+#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 {
+    UiObject    *obj;
+    UiContext   *ctx;
+    UiVar       *var;
+    int         last_selection_state;
+    ui_callback onchange;
+    void        *onchangedata;
+} UiTextArea;
+
+typedef struct UiTextField {
+    UiObject    *obj;
+    UiVar       *var;
+    ui_callback onchange;
+    void        *onchangedata;
+} UiTextField;
+
+typedef struct UiPathTextField {
+    UiObject *obj;
+    
+    GtkWidget *stack;
+    GtkWidget *hbox;
+    GtkWidget *entry_box;
+    GtkWidget *entry;
+#if GTK_MAJOR_VERSION == 3
+    GtkWidget *buttonbox;
+#endif
+    
+    char *current_path;
+    UiPathElm *current_pathelms;
+    GtkWidget **current_path_buttons;
+    size_t current_nelm;
+    
+    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;
+} UiPathTextField;
+
+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, const char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+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, const char *value);
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path);
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf);
+char* ui_path_textfield_get(UiString *str);
+void ui_path_textfield_set(UiString *str, const char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEXT_H */
+
diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c
new file mode 100644 (file)
index 0000000..60c4bac
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * 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 "menu.h"
+#include "button.h"
+#include "icon.h"
+#include "list.h"
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include "../common/context.h"
+
+
+#if UI_GTK2 || UI_GTK3
+
+GtkWidget* ui_create_toolbar(UiObject *obj) {
+    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
+    
+    CxMap *items = uic_get_toolbar_items();
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_toolbar_add_items(obj, toolbar, items, left_defaults);
+    ui_toolbar_add_items(obj, toolbar, items, center_defaults);
+    ui_toolbar_add_items(obj, toolbar, items, right_defaults);
+    
+    /*
+    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;
+}
+
+static void create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+    GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            add_toolitem_widget(tb, (UiToolbarItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            add_toolitem_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            add_toolitem_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults) {
+    // add pre-configured items
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        create_item(obj, toolbar, item);
+    }
+}
+
+static void set_toolbutton_icon(GtkToolItem *item, const char *icon_name) {
+#if GTK_MAJOR_VERSION >= 3
+        gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), icon_name);
+#else
+        UiIcon *icon = ui_icon(icon_name, 24);
+        if(icon) {
+            GdkPixbuf *pixbuf = ui_icon_pixbuf(icon);
+            if(pixbuf) {
+                GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+                gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), image);
+            }
+        }
+#endif
+}
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+        // TODO: gtk3 stock
+        button = gtk_tool_button_new(NULL, item->args.label);
+#endif
+    } else {
+        button = gtk_tool_button_new(NULL, item->args.label);
+    }
+    
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->args.icon) {
+        set_toolbutton_icon(button, item->args.icon);
+    }
+    gtk_tool_item_set_is_important(button, TRUE);
+    
+    ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
+    
+    if(item->args.onclick) {
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->callback = item->args.onclick;
+        event->userdata = item->args.onclickdata;
+        event->customdata = NULL;
+        event->value = 0;
+        
+        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, UiToolbarToggleItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_toggle_tool_button_new_from_stock(item->args.stockid);
+#else
+        button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); // TODO: gtk3 stock
+#endif
+    } else {
+        button = gtk_toggle_tool_button_new();
+        gtk_tool_item_set_homogeneous(button, FALSE);
+        if(item->args.label) {
+            gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.label);
+        }
+        if(item->args.icon) {
+            set_toolbutton_icon(button, item->args.icon);
+        }    
+    }
+    ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *i = (UiInteger*)var->value;
+       if(i) {
+            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);
+            }
+        }
+    }
+    
+    UiVarEventData *event = cxMalloc(
+                obj->ctx->allocator,
+                sizeof(UiVarEventData));
+    event->obj = obj;
+    event->callback = item->args.onchange;
+    event->userdata = item->args.onchangedata;
+    event->var = var;
+
+    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, UiVarEventData *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);
+    
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    UiVar *var = event->var;
+    UiInteger *i = var ? var->value : NULL;
+    
+    if(i) {
+        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;
+}
+
+
+
+typedef struct UiToolbarMenuWidget {
+    GtkWidget *button;
+    GtkMenu *menu;
+} UiToolbarMenuWidget;
+
+static void ui_toolbar_menubutton_clicked(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+    int x;
+    gtk_menu_popup_at_widget(menu->menu, menu->button, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+}
+
+static void ui_toolbar_menubutton_destroy(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+    free(menu);
+}
+
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->args.stockid) {
+#ifdef UI_GTK2
+        button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+        // TODO: gtk3 stock
+        button = gtk_tool_button_new(NULL, item->args.label);
+#endif
+    } else {
+        button = gtk_tool_button_new(NULL, item->args.label);
+    }
+    
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->args.icon) {
+        set_toolbutton_icon(button, item->args.icon);
+    }
+    gtk_tool_item_set_is_important(button, TRUE);
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    // menu
+    GtkWidget *menu_widget = gtk_menu_new();
+    ui_add_menu_items(menu_widget, 0, &item->menu, obj); 
+    gtk_widget_show_all(menu_widget);
+    
+    UiToolbarMenuWidget *tbmenu = malloc(sizeof(UiToolbarMenuWidget));
+    tbmenu->button = GTK_WIDGET(button);
+    tbmenu->menu = GTK_MENU(menu_widget);
+    
+    g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_toolbar_menubutton_clicked),
+                tbmenu);
+    
+    g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_toolbar_menubutton_destroy),
+                tbmenu);
+}
+
+
+
+
+// deprecated / unsupported
+/*
+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);
+    }
+}
+*/
+
+
+
+#ifdef UI_GTK3
+
+GtkWidget* ui_create_headerbar(UiObject *obj) {
+    GtkWidget *headerbar = gtk_header_bar_new();
+    
+    CxMap *items = uic_get_toolbar_items();
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, left_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, center_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, right_defaults);
+    
+    return headerbar;
+}
+
+static void hb_create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+    GtkHeaderBar *tb = GTK_HEADER_BAR(toolbar);
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            add_headerbar_item_widget(tb, (UiToolbarItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            add_headerbar_item_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            add_headerbar_item_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
+    // add pre-configured items
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        hb_create_item(obj, headerbar, item);
+    }
+}
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj) {
+    GtkWidget *button = gtk_button_new_with_label(item->args.label);
+    if(item->args.icon) {
+        ui_button_set_icon_name(button, item->args.icon);
+    }
+    ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    
+    gtk_header_bar_pack_start(hb, button);
+    
+}
+
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj) {
+    
+}
+
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj) {
+    
+}
+
+#endif /* UI_GTK3 */
+
+#endif /* UI_GTK2 || UI_GTK3 */
diff --git a/ui/gtk/toolbar.h b/ui/gtk/toolbar.h
new file mode 100644 (file)
index 0000000..80bd113
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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 "../common/toolbar.h"
+#include <cx/map.h>
+#include <cx/list.h>
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if UI_GTK2 || UI_GTK3
+    
+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_toolitem_vstgr(
+        char *name,
+        char *stockid,
+        int isimportant,
+        ui_callback f,
+        void *userdata,
+        va_list ap);
+
+GtkWidget* ui_create_toolbar(UiObject *obj);
+
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults);
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj);
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj);
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event);
+int64_t ui_tool_toggle_button_get(UiInteger *integer);
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
+
+GtkWidget* ui_create_headerbar(UiObject *obj);
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj);
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj);
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *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);
+*/
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLBAR_H */
+
diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c
new file mode 100644 (file)
index 0000000..91ce244
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * 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 "icon.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+#include "../common/threadpool.h"
+
+#include <cx/utils.h>
+#include <cx/string.h>
+#include <cx/printf.h>
+
+#include <pthread.h>
+
+#ifdef UI_APPLICATION
+UI_APPLICATION app;
+#endif
+
+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;
+
+static int scale_factor = 1;
+
+UIEXPORT void ui_init(const char *appname, int argc, char **argv) {
+    application_name = appname;
+    uic_init_global_context();
+    
+#if GTK_MAJOR_VERSION >= 4
+    gtk_init();
+#else
+    gtk_init(&argc, &argv);
+#endif
+    
+    ui_css_init();
+    uic_docmgr_init();
+    uic_menu_init();
+    uic_toolbar_init();
+    ui_image_init();
+    uic_load_app_properties();
+    
+#if GTK_MAJOR_VERSION >= 4
+    scale_factor = 1; // TODO
+#elif defined(UI_SUPPORTS_SCALE)
+    scale_factor = gdk_monitor_get_scale_factor(
+            gdk_display_get_primary_monitor(gdk_display_get_default()));
+#endif
+}
+
+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;
+}
+
+
+#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() {
+#ifdef UI_APPLICATION
+    cxmutstr appid = cx_asprintf(
+            "ui.%s",
+            application_name ? application_name : "application1");
+    app = UI_APPLICATION_NEW(appid.ptr);
+    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 GTK_APPLICATION(app);
+}
+#endif
+
+void ui_show(UiObject *obj) {
+    gboolean visible = gtk_widget_is_visible(obj->widget);
+    
+    uic_check_group_widgets(obj->ctx);
+#if GTK_MAJOR_VERSION >= 4
+    gtk_window_present(GTK_WINDOW(obj->widget));
+#elif GTK_MAJOR_VERSION <= 3
+    gtk_widget_show_all(obj->widget);
+#endif
+    
+    if(!visible) {
+        obj->ref++;
+    }
+}
+
+void ui_close(UiObject *obj) {
+    uic_context_prepare_close(obj->ctx);
+#if GTK_CHECK_VERSION(4, 0, 0)
+    gtk_window_close(GTK_WINDOW(obj->widget));
+#else
+    gtk_widget_destroy(obj->widget);
+#endif
+}
+
+
+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 && job->finish_callback) {
+        g_idle_add(ui_job_finished, job);
+    } else {
+        free(job);
+    }
+    return NULL;
+}
+
+static gboolean ui_idle_func(void *data) {
+    UiJob *job = data;
+    job->job_func(job->job_data);
+    free(job);
+    return FALSE;
+}
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+    UiJob *job = malloc(sizeof(UiJob));
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = NULL;
+    job->finish_data = NULL;
+    job->obj = NULL;
+    g_idle_add(ui_idle_func, job);
+}
+
+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) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+    gtk_widget_set_no_show_all(widget, !value);
+#endif
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+    if(visible) {
+        gtk_widget_set_no_show_all(widget, FALSE);
+        gtk_widget_show_all(widget);
+    } else {
+        gtk_widget_hide(widget);
+    }
+#endif
+}
+
+void ui_clipboard_set(char *str) {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+#else
+    GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    gtk_clipboard_set_text(cb, str, strlen(str));
+#endif
+}
+
+char* ui_clipboard_get() {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+    return NULL;
+#else
+    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;
+    }
+#endif
+}
+
+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) {
+    if(data->var) {
+        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) {
+        ui_free(var->from_ctx, 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;
+}
+
+
+#if GTK_MAJOR_VERSION >= 3
+
+static GtkCssProvider* ui_gtk_css_provider;
+
+#if GTK_MAJOR_VERSION == 4
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: alpha(currentColor, 0.1);"
+"  border-radius: 6px;"
+"  padding: 0px;"
+"}\n"
+".pathbar-extra-button {\n"
+"  border-top-right-radius: 6px;"
+"  border-bottom-right-radius: 6px;"
+"  border-top-left-radius: 0px;"
+"  border-bottom-left-radius: 0px;"
+"}\n"
+"#pathbar button {\n"
+"  margin: 3px;"
+"  border-radius: 4px;"
+"  padding-top: 0px;"
+"  padding-bottom: 0px;"
+"  padding-left: 8px;"
+"  padding-right: 8px;"
+"}\n"
+"#path-textfield-box entry {\n"
+"  background-color: #00000000;"
+"  border-top-left-radius: 6px;"
+"  border-bottom-left-radius: 6px;"
+"  border-top-right-radius: 0px;"
+"  border-bottom-right-radius: 0px;"
+"}\n"
+".pathbar-button-inactive {\n"
+"  color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+"  background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+"  font-weight: bold;\n"
+"}\n"
+;
+
+#elif GTK_MAJOR_VERSION == 3
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: @theme_base_color;\n"
+"  border-radius: 5px;\n"
+"  padding: 0px;\n"
+"}\n"
+".pathbar-button-inactive {\n"
+"  color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+"  background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+"  font-weight: bold;\n"
+"}\n"
+;
+#endif
+
+void ui_css_init(void) {
+    ui_gtk_css_provider = gtk_css_provider_new();
+    
+#ifdef UI_GTK3
+    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL);
+    
+    GdkScreen *screen = gdk_screen_get_default();
+    gtk_style_context_add_provider_for_screen(
+            screen,
+            GTK_STYLE_PROVIDER(ui_gtk_css_provider),
+            GTK_STYLE_PROVIDER_PRIORITY_USER);
+#endif /* UI_GTK3 */
+    
+#ifdef UI_GTK4
+    
+    
+#if GTK_MINOR_VERSION < 12
+    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1);
+#else
+    gtk_css_provider_load_from_string(ui_gtk_css_provider, ui_gtk_css);
+#endif /* GTK_MINOR_VERSION < 12 */
+    
+    GdkDisplay *display = gdk_display_get_default();
+    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    
+#endif /* UI_GTK4 */
+}
+
+
+
+#endif
+
+void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style_classes) {
+    if(name) {
+        gtk_widget_set_name(widget, name);
+    }
+    if(style_classes) {
+        cxstring *cls = NULL;
+        size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls);
+        for(int i=0;i<numClasses;i++) {
+            cxmutstr m = cx_strdup(cls[i]);
+#if GTK_MAJOR_VERSION >= 4
+            gtk_widget_add_css_class(widget, m.ptr);
+#elif GTK_MAJOR_VERSION >= 3
+            GtkStyleContext *ctx = gtk_widget_get_style_context(widget);
+            gtk_style_context_add_class(ctx, m.ptr);
+#endif
+            free(m.ptr);
+        }
+        free(cls);
+        
+    }
+}
+
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) {
+    if(!groups) {
+        return;
+    }
+    size_t ngroups = uic_group_array_size(groups);
+    ui_set_widget_ngroups(ctx, widget, groups, ngroups);
+}
+
+void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) {
+    if(ngroups > 0) {
+        uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);
+        ui_set_enabled(widget, FALSE);
+    }
+}
diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h
new file mode 100644 (file)
index 0000000..cdaf42a
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * 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"
+
+  
+#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 74
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS
+#else
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE
+#endif
+    
+#ifdef UI_LIBADWAITA
+#define UI_APPLICATION AdwApplication*
+#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS)
+#elif GTK_MAJOR_VERSION >= 3  
+#define UI_APPLICATION GtkApplication*
+#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window))
+#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window))
+#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child)
+#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child)
+#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new()
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_frame_set_child(GTK_FRAME(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass)
+#else
+#define WINDOW_SHOW(window) gtk_widget_show_all(window)
+#define WINDOW_DESTROY(window) gtk_widget_destroy(window)
+#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child)
+#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0)  
+#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_container_add(GTK_CONTAINER(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass)
+#endif
+    
+#ifdef UI_GTK2
+#undef SCROLLEDWINDOW_SET_CHILD
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st)
+#define UI_GTK_V4(st) st
+#elif GTK_MAJOR_VERSION >= 3
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) st 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st) st
+#define UI_GTK_V4(st)
+#else
+#define UI_GTK_SINCE_V4(st) 
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st) st
+#define UI_GTK_V3(st) 
+#define UI_GTK_V4(st)
+#endif
+
+
+typedef struct UiEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+    void        *customdata;
+} UiEventData;
+
+typedef struct UiEventDataExt {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    ui_callback callback2;
+    void        *userdata2;
+    int         value0;
+    int         value1;
+    int         value2;
+    int         value3;
+    void        *customdata0;
+    void        *customdata1;
+    void        *customdata2;
+    void        *customdata3;
+} UiEventDataExt;
+
+typedef struct UiVarEventData {
+    UiObject    *obj;
+    UiVar       *var;
+    UiObserver  **observers;
+    ui_callback callback;
+    void        *userdata;
+} UiVarEventData;
+
+#ifndef UI_GTK4
+struct UiSelection {
+    GtkSelectionData *data;
+};
+#endif
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+#ifdef UI_APPLICATION
+void ui_app_quit();
+GtkApplication* ui_get_application();
+#endif
+
+int ui_get_scalefactor();
+
+void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style);
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups);
+void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups);
+
+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();
+
+#if GTK_MAJOR_VERSION >= 3
+void ui_css_init(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
diff --git a/ui/gtk/window.c b/ui/gtk/window.c
new file mode 100644 (file)
index 0000000..96e1e68
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * 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 "../common/menu.h"
+#include "../common/toolbar.h"
+
+#include <cx/mempool.h>
+
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+#include "headerbar.h"
+#include "button.h"
+
+static int nwindows = 0;
+
+static int window_default_width = 650;
+static int window_default_height = 550;
+
+static gboolean ui_window_destroy(void *data)  {
+    UiObject *obj = data;
+    uic_object_destroy(obj);
+    
+    nwindows--;
+#ifdef UI_GTK2
+    if(nwindows == 0) {
+        gtk_main_quit();
+    }
+#endif
+    
+    return FALSE;
+}
+
+void ui_window_widget_destroy(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 4
+    gtk_window_destroy(GTK_WINDOW(obj->widget));
+#else
+    gtk_widget_destroy(obj->widget);
+#endif
+}
+
+void ui_exit_event(GtkWidget *widget, gpointer data) {
+    // delay exit handler  
+    g_idle_add(ui_window_destroy, data);
+}
+
+static gboolean ui_window_close_request(UiObject *obj) {
+    uic_context_prepare_close(obj->ctx);
+    obj->ref--;
+    if(obj->ref > 0) {
+#if GTK_CHECK_VERSION(2, 18, 0)
+        gtk_widget_set_visible(obj->widget, FALSE);
+#else
+        gtk_widget_hide(obj->widget);
+#endif
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+static gboolean close_request(GtkWindow* self, UiObject *obj) {
+    return ui_window_close_request(obj);
+}
+#else
+static gboolean close_request(GtkWidget* self, GdkEvent* event, UiObject *obj) {
+    return ui_window_close_request(obj);
+}
+#endif
+
+static UiObject* create_window(const char *title, void *window_data, UiBool simple) {
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+    obj->ref = 0;
+   
+#ifdef UI_LIBADWAITA
+    obj->widget = adw_application_window_new(ui_get_application());
+#elif !defined(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);
+    obj->window = window_data;
+    
+#if GTK_CHECK_VERSION(4, 0, 0)
+    obj->ctx->action_map = G_ACTION_MAP(obj->widget);
+#endif
+    
+    if(title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(obj->widget), title);
+    }
+    
+    const char *width = ui_get_property("ui.window.width");
+    const char *height = ui_get_property("ui.window.height");
+    if(width && height) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                atoi(width),
+                atoi(height));
+    } else {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                window_default_width,
+                window_default_height);
+    }
+    
+    obj->destroy = ui_window_widget_destroy;
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+#if GTK_MAJOR_VERSION >= 4
+    g_signal_connect(
+            obj->widget,
+            "close-request",
+            G_CALLBACK(close_request),
+            obj);
+#else
+    g_signal_connect(
+            obj->widget,
+            "delete-event",
+            G_CALLBACK(close_request),
+            obj);
+#endif
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(0);
+#ifdef UI_LIBADWAITA
+    GtkWidget *toolbar_view = adw_toolbar_view_new();
+    adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
+
+    GtkWidget *headerbar = adw_header_bar_new();
+    adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
+    g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar);
+    
+    if(!simple) {
+        ui_fill_headerbar(obj, headerbar);
+    }
+#elif GTK_MAJOR_VERSION >= 4
+    WINDOW_SET_CONTENT(obj->widget, vbox);
+#else
+    gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
+    
+    if(!simple) {
+        // menu
+        if(uic_get_menu_list()) {
+            GtkWidget *mb = ui_create_menubar(obj);
+            if(mb) {
+                gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+            }
+        }
+
+        // toolbar
+        if(uic_toolbar_isenabled()) {
+            GtkWidget *tb = ui_create_toolbar(obj);
+            if(tb) {
+                gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+            }
+        }
+        
+        //GtkWidget *hb = ui_create_headerbar(obj);
+        //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb);
+    }
+#endif
+    
+    // window content
+    // the content has a (TODO: not yet) configurable frame
+    // TODO: really? why
+    /*
+    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);
+    */
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+    obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
+    
+    nwindows++;
+    return obj;
+}
+
+
+UiObject* ui_window(const char *title, void *window_data) {
+    return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simple_window(const char *title, void *window_data) {
+    return create_window(title, window_data, TRUE);
+}
+
+void ui_window_size(UiObject *obj, int width, int height) {
+    gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                width,
+                height);
+}
+
+#ifdef UI_LIBADWAITA
+
+static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) {
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.eventdata = NULL;
+    evt.intval = 0;
+    
+    if(!strcmp(response, "btn1")) {
+        evt.intval = 1;
+    } else if(!strcmp(response, "btn2")) {
+        evt.intval = 2;
+    }
+    
+    if(data->customdata) {
+        GtkWidget *entry = data->customdata;
+        evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+    }
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+}
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+    AdwDialog *dialog = adw_alert_dialog_new(args.title, args.content);
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->callback = args.result;
+    event->userdata = args.resultdata;
+    event->customdata = NULL;
+    event->value = 0;
+    event->obj = parent;
+    
+    if(args.button1_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn1", args.button1_label);
+    }
+    if(args.button2_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn2", args.button2_label);
+    }
+    if(args.closebutton_label) {
+        adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "close", args.closebutton_label);
+        adw_alert_dialog_set_close_response(ADW_ALERT_DIALOG(dialog), "close");
+    }
+    
+    GtkWidget *entry = NULL;
+    if(args.input || args.password) {
+        entry = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(entry, args.input_value);
+        }
+        adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry);
+        event->customdata = entry;
+    }
+    
+    g_signal_connect(
+                dialog,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    
+    g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), event);
+    adw_dialog_present(dialog, parent->widget);
+    
+    if(entry) {
+        gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
+    }
+}
+#else
+
+static void ui_dialog_response (GtkDialog* self, gint response_id, gpointer user_data) {
+    UiEventData *data = user_data;
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.eventdata = NULL;
+    evt.intval = 0;
+    
+    if(data->customdata) {
+        GtkWidget *entry = data->customdata;
+        evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+        
+    }
+    
+    if(response_id == 1 || response_id == 2) {
+        evt.intval = response_id;
+    }
+    
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+    
+    WINDOW_DESTROY(GTK_WIDGET(self));
+}
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+    GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new());
+    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    
+    GtkWidget *dialog_w = GTK_WIDGET(dialog);
+    if(args.title) {
+        gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+    }
+    if(args.button1_label) {
+        gtk_dialog_add_button(dialog, args.button1_label, 1);
+    }
+    if(args.button2_label) {
+        gtk_dialog_add_button(dialog, args.button2_label, 2);
+    }
+    if(args.closebutton_label) {
+        gtk_dialog_add_button(dialog, args.closebutton_label, 0);
+    }
+    
+    GtkWidget *content_area = gtk_dialog_get_content_area(dialog);
+    if(args.content) {
+        GtkWidget *label = gtk_label_new(args.content);
+        BOX_ADD(content_area, label);
+    }
+    
+    GtkWidget *textfield = NULL;
+    if(args.input || args.password) {
+        textfield = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(textfield, args.input_value);
+        }
+        BOX_ADD(content_area, textfield);
+    }
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = parent;
+    event->callback = args.result;
+    event->userdata = args.resultdata;
+    event->value = 0;
+    event->customdata = textfield;
+    
+    g_signal_connect(dialog_w,
+                           "response",
+                           G_CALLBACK(ui_dialog_response),
+                           event);
+    
+    WINDOW_SHOW(GTK_WIDGET(dialog_w));
+}
+#endif
+
+
+#if GTK_MAJOR_VERSION >= 3
+UiFileList listmodel2filelist(GListModel *selection) {
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    flist.nfiles = g_list_model_get_n_items(selection);
+    flist.files = calloc(flist.nfiles, sizeof(char*));
+    for(int i=0;i<flist.nfiles;i++) {
+        GFile *file = g_list_model_get_item(selection, i);
+        char *path = g_file_get_path(file);
+        flist.files[i] = path ? strdup(path) : NULL;
+        g_object_unref(file);
+    }
+    return flist;
+}
+#endif
+
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+#define UI_GTK_FILEDIALOG_OPEN 16
+#define UI_GTK_FILEDIALOG_SAVE 32
+
+static void filechooser_opened(GObject *source, GAsyncResult *result, void *data) {
+    UiEventData *event = data;
+    
+    GFile *file = NULL;
+    GListModel *selection = NULL;
+    GError *error = NULL;
+    
+    int mode = event->value;
+    int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        if(multi) {
+            selection = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error);
+        } else {
+            file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), result, &error);
+        }
+    } else if((mode & UI_GTK_FILEDIALOG_OPEN) == UI_GTK_FILEDIALOG_OPEN) {
+        if(multi) {
+            selection = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error);
+        } else {
+            file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), result, &error);
+        }
+    } else {
+        file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error);
+    }
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    if(selection) {
+        flist = listmodel2filelist(selection);
+        g_object_unref(selection);
+    } else if(file) {
+        char *path = g_file_get_path(file);
+        if(path) {
+            flist.nfiles = 1;
+            flist.files = calloc(flist.nfiles, sizeof(char*));
+            flist.files[0] = strdup(path);
+        }
+        g_object_unref(file); 
+    }
+    
+    if(event->callback) {
+        event->callback(&evt, event->userdata);
+    }
+    
+    for(int i=0;i<flist.nfiles;i++) {
+        free(flist.files[i]);
+    }
+}
+
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        mode |= UI_GTK_FILEDIALOG_OPEN;
+    } else {
+        mode |= UI_GTK_FILEDIALOG_SAVE;
+    }
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->callback = file_selected_callback;
+    event->userdata = cbdata;
+    event->customdata = NULL;
+    event->value = mode;
+    event->obj = obj;
+    
+    GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(obj->widget));
+    GtkFileDialog *dialog = gtk_file_dialog_new();
+    if(name) {
+        gtk_file_dialog_set_initial_name(dialog, name);
+    }
+    
+    int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        if(multi) {
+            gtk_file_dialog_select_multiple_folders(dialog, parent, NULL, filechooser_opened, event);
+        } else {
+            gtk_file_dialog_select_folder(dialog, parent, NULL, filechooser_opened, event);
+        }
+    } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        if(multi) {
+            gtk_file_dialog_open_multiple(dialog, parent, NULL, filechooser_opened, event);
+        } else {
+            gtk_file_dialog_open(dialog, parent, NULL, filechooser_opened, event);
+        }
+    } else {
+        gtk_file_dialog_save(dialog, parent, NULL, filechooser_opened, event);
+    }
+    
+    g_object_unref(dialog);
+}
+#else
+
+
+
+static void filechooser_response(GtkDialog* self, gint response_id, UiEventData *data) {
+    UiEvent evt;
+    evt.obj = data->obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    if(response_id == GTK_RESPONSE_ACCEPT) {
+#if GTK_CHECK_VERSION(4, 0, 0)
+        GListModel *selection = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(self));
+        flist = flist = listmodel2filelist(selection);
+        g_object_unref(selection);
+#else
+        GSList *selection = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(self));
+        flist.nfiles = g_slist_length(selection);
+        flist.files = calloc(flist.nfiles, sizeof(char*));
+        int i = 0;
+        while(selection) {
+            char *file = selection->data;
+            flist.files[i] = strdup(file);
+            g_free(file);
+            selection = selection->next;
+            i++;
+        }
+        g_slist_free(selection);
+#endif
+    }
+    
+    
+    if(data->callback) {
+        data->callback(&evt, data->userdata);
+    }
+    
+    for(int i=0;i<flist.nfiles;i++) {
+        free(flist.files[i]);
+    }
+    
+    WINDOW_DESTROY(GTK_WIDGET(self));
+}
+
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    char *button;
+    char *title;
+    
+    GtkWidget *dialog;
+    if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+        dialog = gtk_file_chooser_dialog_new (
+                "Open Folder",
+                GTK_WINDOW(obj->widget),
+                GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Select Folder",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
+    } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        dialog = gtk_file_chooser_dialog_new (
+                "Select Folder",
+                GTK_WINDOW(obj->widget),
+                action,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Open File",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
+    } else {
+        dialog = gtk_file_chooser_dialog_new (
+                "Save File",
+                GTK_WINDOW(obj->widget),
+                action,
+                "Cancel",
+                GTK_RESPONSE_CANCEL,
+                "Save File",
+                GTK_RESPONSE_ACCEPT,
+                NULL);
+    }
+    
+    if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+        gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+    }
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = cbdata;
+    event->callback = file_selected_callback;
+    event->value = 0;
+    event->customdata = NULL;
+    
+    g_signal_connect(
+                dialog,
+                "response",
+                G_CALLBACK(filechooser_response),
+                event);
+    g_signal_connect(
+                dialog,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    
+    
+    UiEvent evt;
+    evt.obj = obj;
+    evt.document = evt.obj->ctx->document;
+    evt.window = evt.obj->window;
+    evt.intval = 0;
+    
+    UiFileList flist;
+    flist.files = NULL;
+    flist.nfiles = 0;
+    evt.eventdata = &flist;
+    
+    gtk_widget_show(dialog);
+}
+#endif
+
+void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+    ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN, mode, NULL, file_selected_callback, cbdata);
+}
+
+void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {
+    ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata);
+}
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+#define DIALOG_NEW() gtk_window_new()
+#else
+#define DIALOG_NEW() gtk_dialog_new()
+
+static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) {
+    UiEventData *event = user_data;
+    // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT?
+    if(event->callback) {
+        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);
+    }
+}
+
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button)
+#else
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button)
+#endif
+
+
+
+UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
+    GtkWidget *dialog = DIALOG_NEW();
+    if(args.width > 0 || args.height > 0) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(dialog),
+                args.width,
+                args.height);
+    }
+    
+    
+    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+    if(args.modal != UI_OFF) {
+        gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    }
+    
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
+    obj->ctx = uic_context(obj, mp);
+    obj->widget = dialog;
+    obj->ref = 0;
+    obj->destroy = ui_window_widget_destroy;
+    nwindows++;
+    
+    if(args.title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+    }
+    
+#if ! GTK_CHECK_VERSION(4, 10, 0)
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = args.onclickdata;
+    event->callback = args.onclick;
+    event->value = 0;
+    event->customdata = NULL;
+
+    g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event);
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+#endif
+    
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+#if GTK_MAJOR_VERSION >= 4
+    g_signal_connect(
+            obj->widget,
+            "close-request",
+            G_CALLBACK(close_request),
+            obj);
+#else
+    g_signal_connect(
+            obj->widget,
+            "delete-event",
+            G_CALLBACK(close_request),
+            obj);
+#endif
+    
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    gtk_container_remove(GTK_CONTAINER(dialog), c);
+#endif
+    
+    GtkWidget *content_vbox = ui_gtk_vbox_new(0);
+    obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX);
+    if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) {
+#if GTK_CHECK_VERSION(3, 10, 0)
+        if(args.titlebar_buttons != UI_OFF) {
+            GtkWidget *headerbar = gtk_header_bar_new();
+            gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar);
+            if(args.show_closebutton == UI_OFF) {
+                HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE);
+            }
+            
+            if(args.lbutton1) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 1) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            if(args.lbutton2) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 2) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            
+            if(args.rbutton4) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 4) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            if(args.rbutton3) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 3) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                    DEFAULT_BUTTON(dialog, button);
+                }
+            }
+            WINDOW_SET_CONTENT(obj->widget, content_vbox);
+            return obj;
+        }
+#endif
+        GtkWidget *vbox = ui_gtk_vbox_new(0);
+        WINDOW_SET_CONTENT(obj->widget, vbox);
+        
+        GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        
+        GtkWidget *grid = ui_create_grid_widget(10, 10);
+        GtkWidget *widget = ui_box_set_margin(grid, 16);
+        gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); 
+        
+        if(args.lbutton1) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+            gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
+            if(args.default_button == 1) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        if(args.lbutton2) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+            gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
+            if(args.default_button == 2) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        GtkWidget *space = gtk_label_new(NULL);
+        gtk_widget_set_hexpand(space, TRUE);
+        gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
+        if(args.rbutton3) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+            gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
+            if(args.default_button == 3) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        if(args.rbutton4) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+            gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
+            if(args.default_button == 4) {
+                WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                DEFAULT_BUTTON(dialog, button);
+            }
+        }
+        
+        BOX_ADD_EXPAND(vbox, content_vbox);   
+        BOX_ADD_NO_EXPAND(vbox, separator);
+        BOX_ADD_NO_EXPAND(vbox, widget);
+    } else {
+        WINDOW_SET_CONTENT(obj->widget, content_vbox);
+    }
+    
+    return obj;
+}
diff --git a/ui/motif/Makefile b/ui/motif/Makefile
new file mode 100644 (file)
index 0000000..3f7c064
--- /dev/null
@@ -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)       
diff --git a/ui/motif/button.c b/ui/motif/button.c
new file mode 100644 (file)
index 0000000..8757fdf
--- /dev/null
@@ -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;
+    }
+}
diff --git a/ui/motif/button.h b/ui/motif/button.h
new file mode 100644 (file)
index 0000000..9fd7dca
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/container.c b/ui/motif/container.c
new file mode 100644 (file)
index 0000000..007c10f
--- /dev/null
@@ -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->mp);
+    content->ctx->parent = view->ctx;
+    content->ctx->attach_document = uic_context_attach_document;
+    content->ctx->detach_document2 = uic_context_detach_document2;
+    content->widget = frame;
+    content->window = view->ctx->obj->window;
+    content->container = ui_frame_container(content, frame);
+    content->next = NULL;
+    
+    // add tab button
+    cxListAdd(v->tabs, tab);
+    
+    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;
+}
diff --git a/ui/motif/container.h b/ui/motif/container.h
new file mode 100644 (file)
index 0000000..a012f8b
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/dnd.c b/ui/motif/dnd.c
new file mode 100644 (file)
index 0000000..2f2313e
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/dnd.h b/ui/motif/dnd.h
new file mode 100644 (file)
index 0000000..3653692
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/graphics.c b/ui/motif/graphics.c
new file mode 100644 (file)
index 0000000..fefeca6
--- /dev/null
@@ -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);
+}
diff --git a/ui/motif/graphics.h b/ui/motif/graphics.h
new file mode 100644 (file)
index 0000000..fa248b7
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/image.c b/ui/motif/image.c
new file mode 100644 (file)
index 0000000..5142dbc
--- /dev/null
@@ -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) {
+    
+}
+
diff --git a/ui/motif/image.h b/ui/motif/image.h
new file mode 100644 (file)
index 0000000..681d232
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/label.c b/ui/motif/label.c
new file mode 100644 (file)
index 0000000..8962197
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/label.h b/ui/motif/label.h
new file mode 100644 (file)
index 0000000..adc644c
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/list.c b/ui/motif/list.c
new file mode 100644 (file)
index 0000000..0c5b193
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/list.h b/ui/motif/list.h
new file mode 100644 (file)
index 0000000..35f97ea
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/menu.c b/ui/motif/menu.c
new file mode 100644 (file)
index 0000000..1b27a62
--- /dev/null
@@ -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);
+}
diff --git a/ui/motif/menu.h b/ui/motif/menu.h
new file mode 100644 (file)
index 0000000..db75775
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk
new file mode 100644 (file)
index 0000000..179deac
--- /dev/null
@@ -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)
diff --git a/ui/motif/range.c b/ui/motif/range.c
new file mode 100644 (file)
index 0000000..91cb045
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/range.h b/ui/motif/range.h
new file mode 100644 (file)
index 0000000..1c6da04
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/stock.c b/ui/motif/stock.c
new file mode 100644 (file)
index 0000000..4866d54
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/stock.h b/ui/motif/stock.h
new file mode 100644 (file)
index 0000000..03e643f
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/text.c b/ui/motif/text.c
new file mode 100644 (file)
index 0000000..1e98b84
--- /dev/null
@@ -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, const char *str) {
+    XmTextSetString(text->obj, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    int length = end - begin;
+    char *str = XtMalloc(length + 1);
+    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    text->value.ptr = NULL;
+    XmTextInsert(text->obj, pos, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+    long begin;
+    long end;
+    XmTextGetSelectionPosition(text->obj, &begin, &end);
+    text->pos = begin;
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+    return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+void ui_text_set(UiText *text, char *str) {
+    if(text->set) {
+        text->set(text, str);
+    } else {
+        if(text->value.ptr) {
+            text->value.free(text->value.ptr);
+        }
+        text->value.ptr = XtNewString(str);
+        text->value.free = (ui_freefunc)XtFree;
+    }
+}
+
+char* ui_text_get(UiText *text) {
+    if(text->get) {
+        return text->get(text);
+    } else {
+        return text->value.ptr;
+    }
+}
+
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->end = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+    UiTextBufOp *op = mgr->begin;
+    while(op) {
+        UiTextBufOp *nextOp = op->next;
+        if(op->text) {
+            free(op->text);
+        }
+        free(op);
+        op = nextOp;
+    }
+    free(mgr);
+}
+
+void ui_text_selection_callback(
+        Widget widget,
+        UiTextArea *textarea,
+        XtPointer data)
+{
+    long left = 0;
+    long right = 0;
+    XmTextGetSelectionPosition(widget, &left, &right);
+    int sel = left < right ? 1 : 0;
+    if(sel != textarea->last_selection_state) {
+        if(sel) {
+            ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
+        } else {
+            ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
+        }
+    }
+    textarea->last_selection_state = sel;
+}
+
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
+    UiText *value = var->value;
+    if(!value->obj) {
+        // TODO: bug, fix
+        return;
+    }
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    
+    XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
+    int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    char *text = txv->text->ptr;
+    int length = txv->text->length;
+    
+    if(mgr->cur) {
+        UiTextBufOp *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
+            while(elm) {
+                elm->prev = NULL;   
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
+                elm = next;
+            }
+        }
+        
+        UiTextBufOp *last_op = mgr->cur;
+        if(
+            last_op->type == UI_TEXTBUF_INSERT &&
+            ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+        {
+            // append text to last op       
+            int ln = last_op->len;
+            char *newtext = malloc(ln + length + 1);
+            memcpy(newtext, last_op->text, ln);
+            memcpy(newtext+ln, text, length);
+            newtext[ln+length] = '\0';
+            
+            last_op->text = newtext;
+            last_op->len = ln + length;
+            last_op->end += length;
+            
+            return;
+        }
+    }
+    
+    char *str;
+    if(type == UI_TEXTBUF_INSERT) {
+        str = malloc(length + 1);
+        memcpy(str, text, length);
+        str[length] = 0;
+    } else {
+        length = txv->endPos - txv->startPos;
+        str = malloc(length + 1);
+        XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
+    }
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
+    op->type = type;
+    op->start = txv->startPos;
+    op->end = txv->endPos + 1;
+    op->len = length;
+    op->text = str;
+    
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+    // return 1 if oldstr + newstr are one word
+    
+    int has_space = 0;
+    for(int i=0;i<oldlen;i++) {
+        if(oldstr[i] < 33) {
+            has_space = 1;
+            break;
+        }
+    }
+    
+    for(int i=0;i<newlen;i++) {
+        if(has_space && newstr[i] > 32) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+    if(op->text) {
+        free(op->text);
+    }
+    free(op);
+}
+
+
+void ui_text_undo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    if(mgr->cur) {
+        UiTextBufOp *op = mgr->cur;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextInsert(value->obj, op->start, op->text);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = mgr->cur->prev;
+    }
+}
+
+void ui_text_redo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    UiTextBufOp *elm = NULL;
+    if(mgr->cur) {
+        if(mgr->cur->next) {
+            elm = mgr->cur->next;
+        }
+    } else if(mgr->begin) {
+        elm = mgr->begin;
+    }
+    
+    if(elm) {
+        UiTextBufOp *op = elm;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                XmTextInsert(value->obj, op->start, op->text);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+/* ------------------------- textfield ------------------------- */
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiContainer *ct = uic_get_current_container(obj);
+    int n = 0;
+    Arg args[16];
+    XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
+    n++;
+    if(width > 0) {
+        XtSetArg(args[n], XmNcolumns, width / 2 + 1);
+        n++;
+    }
+    if(frameless) {
+        XtSetArg(args[n], XmNshadowThickness, 0);
+        n++;
+    }
+    if(password) {
+        // TODO
+    }
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget textfield = XmCreateText(parent, "text_field", args, n);
+    ct->add(ct, textfield);
+    XtManageChild(textfield);
+    
+    // bind value
+    if(value) {
+        if(value->value.ptr) {
+            XmTextSetString(textfield, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textfield_set;
+        value->get = ui_textfield_get;
+        value->value.ptr = NULL;
+        value->obj = textfield;
+    }
+    
+    return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+    if(var) {
+        UiString *value = var->value;
+        return ui_textfield(obj, value);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    char *value = XmTextGetString(str->obj);
+    str->value.ptr = value;
+    str->value.free = (ui_freefunc)XtFree;
+    return value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    XmTextSetString(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = NULL;
+}
+
diff --git a/ui/motif/text.h b/ui/motif/text.h
new file mode 100644 (file)
index 0000000..d40ce5c
--- /dev/null
@@ -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, const char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_text_selection_callback(
+        Widget widget,
+        UiTextArea *textarea,
+        XtPointer data);
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+void ui_free_textbuf_op(UiTextBufOp *op);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEXT_H */
+
diff --git a/ui/motif/toolbar.c b/ui/motif/toolbar.c
new file mode 100644 (file)
index 0000000..35914f9
--- /dev/null
@@ -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) {
+    
+}
diff --git a/ui/motif/toolbar.h b/ui/motif/toolbar.h
new file mode 100644 (file)
index 0000000..777adf3
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c
new file mode 100644 (file)
index 0000000..f975b4f
--- /dev/null
@@ -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);
+}
diff --git a/ui/motif/toolkit.h b/ui/motif/toolkit.h
new file mode 100644 (file)
index 0000000..b14cbd9
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/tree.c b/ui/motif/tree.c
new file mode 100644 (file)
index 0000000..1cac8a9
--- /dev/null
@@ -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;
+}
diff --git a/ui/motif/tree.h b/ui/motif/tree.h
new file mode 100644 (file)
index 0000000..68e5b06
--- /dev/null
@@ -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 */
+
diff --git a/ui/motif/window.c b/ui/motif/window.c
new file mode 100644 (file)
index 0000000..b2f1504
--- /dev/null
@@ -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/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, mp);
+    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);
+}
diff --git a/ui/qt/Makefile b/ui/qt/Makefile
new file mode 100644 (file)
index 0000000..a1e7f99
--- /dev/null
@@ -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:
+
diff --git a/ui/qt/button.cpp b/ui/qt/button.cpp
new file mode 100644 (file)
index 0000000..04b84a4
--- /dev/null
@@ -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;
+    }
+}
diff --git a/ui/qt/button.h b/ui/qt/button.h
new file mode 100644 (file)
index 0000000..1761333
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/container.cpp b/ui/qt/container.cpp
new file mode 100644 (file)
index 0000000..9cb27d8
--- /dev/null
@@ -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;
+}
diff --git a/ui/qt/container.h b/ui/qt/container.h
new file mode 100644 (file)
index 0000000..1a1c353
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/graphics.cpp b/ui/qt/graphics.cpp
new file mode 100644 (file)
index 0000000..d9a03c1
--- /dev/null
@@ -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);
+}
+
diff --git a/ui/qt/graphics.h b/ui/qt/graphics.h
new file mode 100644 (file)
index 0000000..b4c5f0b
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/label.cpp b/ui/qt/label.cpp
new file mode 100644 (file)
index 0000000..85bfc57
--- /dev/null
@@ -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;
+}
diff --git a/ui/qt/label.h b/ui/qt/label.h
new file mode 100644 (file)
index 0000000..8dd1b68
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/menu.cpp b/ui/qt/menu.cpp
new file mode 100644 (file)
index 0000000..6482e01
--- /dev/null
@@ -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()));
+}
diff --git a/ui/qt/menu.h b/ui/qt/menu.h
new file mode 100644 (file)
index 0000000..7bffb08
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp
new file mode 100644 (file)
index 0000000..7cdf63e
--- /dev/null
@@ -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;
+}
diff --git a/ui/qt/model.h b/ui/qt/model.h
new file mode 100644 (file)
index 0000000..b74b186
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/objs.mk b/ui/qt/objs.mk
new file mode 100644 (file)
index 0000000..50ac471
--- /dev/null
@@ -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)
+
diff --git a/ui/qt/qt4.pro b/ui/qt/qt4.pro
new file mode 100644 (file)
index 0000000..efc299d
--- /dev/null
@@ -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
+
diff --git a/ui/qt/stock.cpp b/ui/qt/stock.cpp
new file mode 100644 (file)
index 0000000..43a3ea9
--- /dev/null
@@ -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;
+}
+
+
diff --git a/ui/qt/stock.h b/ui/qt/stock.h
new file mode 100644 (file)
index 0000000..f183f6e
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/text.cpp b/ui/qt/text.cpp
new file mode 100644 (file)
index 0000000..3d7027e
--- /dev/null
@@ -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;
+}
diff --git a/ui/qt/text.h b/ui/qt/text.h
new file mode 100644 (file)
index 0000000..3ca8fd2
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/toolbar.cpp b/ui/qt/toolbar.cpp
new file mode 100644 (file)
index 0000000..c810e8c
--- /dev/null
@@ -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;
+}
diff --git a/ui/qt/toolbar.h b/ui/qt/toolbar.h
new file mode 100644 (file)
index 0000000..77f059e
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/toolkit.cpp b/ui/qt/toolkit.cpp
new file mode 100644 (file)
index 0000000..13c8f29
--- /dev/null
@@ -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);
+}
diff --git a/ui/qt/toolkit.h b/ui/qt/toolkit.h
new file mode 100644 (file)
index 0000000..f767ef6
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/tree.cpp b/ui/qt/tree.cpp
new file mode 100644 (file)
index 0000000..d62b20c
--- /dev/null
@@ -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;
+}
+
diff --git a/ui/qt/tree.h b/ui/qt/tree.h
new file mode 100644 (file)
index 0000000..54dd0cd
--- /dev/null
@@ -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 */
+
diff --git a/ui/qt/window.cpp b/ui/qt/window.cpp
new file mode 100644 (file)
index 0000000..119ea3e
--- /dev/null
@@ -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;
+    }
+}
diff --git a/ui/qt/window.h b/ui/qt/window.h
new file mode 100644 (file)
index 0000000..19b6c3f
--- /dev/null
@@ -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 */
+
diff --git a/ui/ui/button.h b/ui/ui/button.h
new file mode 100644 (file)
index 0000000..17407b7
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    UiLabelType labeltype;
+    ui_callback onclick;
+    void* onclickdata;
+    
+    const int* groups;
+} UiButtonArgs;
+
+typedef struct UiToggleArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    const char* label;
+    const char* stockid;
+    const char* icon;
+    UiLabelType labeltype;
+    UiInteger* value;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+    int enable_group;
+    
+    const int* groups;
+} 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 */
+
diff --git a/ui/ui/container.h b/ui/ui/container.h
new file mode 100644 (file)
index 0000000..ec35c9c
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * 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,
+    UI_CONTAINER_NO_SUB
+} 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 enum UiHeaderbarAlternative {
+    UI_HEADERBAR_ALTERNATIVE_DEFAULT = 0,
+    UI_HEADERBAR_ALTERNATIVE_TOOLBAR,
+    UI_HEADERBAR_ALTERNATIVE_BOX
+} UiHeaderbarAlternative;
+
+typedef struct UiContainerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+} UiContainerArgs;
+
+typedef struct UiFrameArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    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;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiTabViewType tabview;
+
+    UiSubContainerType subcontainer;
+    
+    UiInteger *value;
+    const char* varname;
+
+    int margin;
+    int spacing;
+    int columnspacing;
+    int rowspacing;
+
+    const char* label;
+    UiBool isexpanded;
+} UiTabViewArgs;
+
+typedef struct UiHeaderbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    UiBool showtitle;
+    UiBool showwindowbuttons;
+    
+    UiHeaderbarAlternative alternative;
+    int alt_spacing;
+} UiHeaderbarArgs;
+
+
+
+#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
+
+#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_headerbar(obj, ...) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#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_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+
+UIEXPORT void ui_end(UiObject *obj);
+    
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs args);
+
+UIEXPORT UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args);
+UIEXPORT void ui_tab_create(UiObject *obj, const char* title);
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab);
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab);
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index);
+
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args);
+UIEXPORT void ui_headerbar_start_create(UiObject *obj);
+UIEXPORT void ui_headerbar_center_create(UiObject *obj);
+UIEXPORT void ui_headerbar_end_create(UiObject *obj);
+
+
+UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj); // TODO
+
+UIEXPORT UIWIDGET ui_sidebar(UiObject *obj); // TODO
+
+UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
+UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
+
+
+// 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_hfill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill);
+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);
+
+// TODO
+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.hfill) ui_layout_hfill(obj, 1); \
+    if(args.vfill) ui_layout_vfill(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 */
+
diff --git a/ui/ui/display.h b/ui/ui/display.h
new file mode 100644 (file)
index 0000000..958f6f9
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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;
+
+enum UiLabelStyle {
+    UI_LABEL_STYLE_DEFAULT = 0,
+    UI_LABEL_STYLE_TITLE,
+    UI_LABEL_STYLE_SUBTITLE,
+    UI_LABEL_STYLE_DIM
+};
+
+typedef enum UiLabelStyle UiLabelStyle;
+
+typedef struct UiLabelArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+
+    const char* label;
+    UiAlignment align;
+    UiLabelStyle style;
+    UiString* value;
+    const char* varname;
+} UiLabelArgs;
+
+typedef struct UiProgressbarArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    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;
+    UiBool hfill;
+    UiBool vfill;
+    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_deprecated(UiObject *obj);
+UIWIDGET ui_separator_deprecated(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 */
+
diff --git a/ui/ui/dnd.h b/ui/ui/dnd.h
new file mode 100644 (file)
index 0000000..4c6913f
--- /dev/null
@@ -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 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 UiFileList ui_selection_geturis(UiDnD *sel);
+
+UIEXPORT UiDnDAction ui_dnd_result(UiDnD *dnd);
+UIEXPORT UiBool ui_dnd_need_delete(UiDnD *dnd);
+
+UIEXPORT void ui_dnd_accept(UiDnD *dnd, UiBool accept);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DND_H */
+
diff --git a/ui/ui/entry.h b/ui/ui/entry.h
new file mode 100644 (file)
index 0000000..d6fc039
--- /dev/null
@@ -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.
+ */
+
+#ifndef UI_ENTRY_H
+#define UI_ENTRY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiSpinnerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    double step;
+    int digits;
+    UiInteger *intvalue;
+    UiDouble* doublevalue;
+    UiRange *rangevalue;
+    const char* varname;
+    ui_callback onchange;
+    void* onchangedata;
+    
+    const int *groups;
+} UiSpinnerArgs;
+
+
+    
+UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args);
+
+#define ui_spinner(obj, ...) ui_spinner_create(obj, (UiSpinnerArgs){ __VA_ARGS__ } )
+
+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 */
+
diff --git a/ui/ui/graphics.h b/ui/ui/graphics.h
new file mode 100644 (file)
index 0000000..6eb0daf
--- /dev/null
@@ -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 */
+
diff --git a/ui/ui/icons.h b/ui/ui/icons.h
new file mode 100644 (file)
index 0000000..cd4ef6c
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ICONS_H
+#define UI_ICONS_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef UI_GTK
+    
+#define UI_ICON_HOME "go-home"
+#define UI_ICON_NEW_WINDOW "list-add"
+#define UI_ICON_REFRESH "view-refresh"
+#define UI_ICON_NEW_FOLDER "folder-new"
+#define UI_ICON_ADD "document-new"
+#define UI_ICON_UPLOAD "document-open"
+#define UI_ICON_SAVE_LOCAL "document-save-as"
+#define UI_ICON_DELETE "edit-delete"
+#define UI_ICON_DOCK_LEFT ""
+#define UI_ICON_DOCK_RIGHT ""
+#define UI_ICON_GO_BACK "go-previous"
+#define UI_ICON_GO_FORWARD "go-next"
+    
+#endif /* UI_GTK */
+    
+    
+
+#ifdef UI_WINUI
+    
+#define UI_ICON_HOME "Home"
+#define UI_ICON_NEW_WINDOW "NewWindow"
+#define UI_ICON_REFRESH "Refresh"
+#define UI_ICON_NEW_FOLDER "NewFolder"
+#define UI_ICON_ADD "Add"
+#define UI_ICON_UPLOAD "Upload"
+#define UI_ICON_SAVE_LOCAL "SaveLocal"
+#define UI_ICON_DELETE "Delete"
+#define UI_ICON_DOCK_LEFT "DockLeft"
+#define UI_ICON_DOCK_RIGHT "DockRight"
+#define UI_ICON_GO_BACK "Back"
+#define UI_ICON_GO_FORWARD "Forward"
+    
+#endif /* UI_WINUI */
+    
+    
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
+UIEXPORT UiIcon* ui_icon_unscaled(const char *name, int size);
+UIEXPORT UiIcon* ui_imageicon(const char* file);
+UIEXPORT void ui_icon_free(UiIcon* icon);
+
+UIEXPORT UiIcon* ui_foldericon(size_t size);
+UIEXPORT UiIcon* ui_fileicon(size_t size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ICONS_H */
+
diff --git a/ui/ui/image.h b/ui/ui/image.h
new file mode 100644 (file)
index 0000000..c26ec3c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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
+
+#define UI_IMAGE_OBJECT_TYPE "image"
+    
+typedef struct UiImageViewerArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    UiBool scrollarea;
+    UiBool autoscale;
+    UiGeneric *value;
+    const char *varname;
+    UiMenuBuilder *contextmenu;
+} UiImageViewerArgs;
+    
+#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
+    
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args);
+
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_IMAGE_H */
+
diff --git a/ui/ui/menu.h b/ui/ui/menu.h
new file mode 100644 (file)
index 0000000..c01fa68
--- /dev/null
@@ -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_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_getvaluefunc getvalue;
+       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__ })
+#define ui_menu_itemlist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS__ } )
+#define ui_menu_togglelist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} )
+#define ui_menu_radiolist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __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_end(void); // TODO: private
+
+/*
+ * widget menu functions
+ */
+
+#define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close())
+
+UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder);
+UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder);
+UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget);
+UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y);
+
+// used for macro
+UIEXPORT void ui_menu_close(void);
+UIEXPORT int ui_menu_is_open(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_MENU_H */
+
diff --git a/ui/ui/properties.h b/ui/ui/properties.h
new file mode 100644 (file)
index 0000000..5c985d9
--- /dev/null
@@ -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 UI_PROPERTIES_H
+#define        UI_PROPERTIES_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char* ui_get_property(const char *name);
+void  ui_set_property(const char *name, const char *value);
+const char* ui_set_default_property(const char *name, const char *value);
+
+int ui_properties_store(void);
+
+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 */
+
diff --git a/ui/ui/range.h b/ui/ui/range.h
new file mode 100644 (file)
index 0000000..28ebb3f
--- /dev/null
@@ -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 */
+
diff --git a/ui/ui/stock.h b/ui/ui/stock.h
new file mode 100644 (file)
index 0000000..ab2d13d
--- /dev/null
@@ -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 */
+
diff --git a/ui/ui/text.h b/ui/ui/text.h
new file mode 100644 (file)
index 0000000..bbe300b
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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 UiTextAreaArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
+
+    UiText *value;
+    const char *varname;
+    ui_callback onchange;
+    void *onchangedata;
+    
+    const int *groups;
+} UiTextAreaArgs;
+    
+typedef struct UiTextFieldArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    int width;
+    const char *name;
+    const char *style_class;
+
+    UiString* value;
+    const char *varname;
+    ui_callback onchange;
+    void *onchangedata;
+    
+    const int *groups;
+} 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;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    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;
+
+#define ui_textarea(obj, ...) ui_textarea_create(obj, (UiTextAreaArgs) { __VA_ARGS__ })
+
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args);
+
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+
+UIEXPORT void ui_text_undo(UiText *value);
+UIEXPORT 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 */
+
diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h
new file mode 100644 (file)
index 0000000..0509321
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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;
+        
+        const int *groups;
+} UiToolbarItemArgs;
+
+typedef struct UiToolbarToggleItemArgs {
+       const char* label;
+       const char* stockid;
+       const char* icon;
+
+       const char* varname;
+       ui_callback onchange;
+       void* onchangedata;
+        
+        const int *groups;
+} 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(name, ...) for(ui_toolbar_menu_create(name, (UiToolbarMenuArgs){ __VA_ARGS__ });ui_menu_is_open();ui_menu_close())
+#define ui_toolbar_appmenu() for(ui_toolbar_menu_create(NULL, (UiToolbarMenuArgs){ 0 });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);
+
+UIEXPORT void ui_toolbar_add_default(const char *name, enum UiToolbarPos pos);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TOOLBAR_H */
+
diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h
new file mode 100644 (file)
index 0000000..9e3ab36
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * 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 || UI_GTK4
+
+#include <gtk/gtk.h>
+#define UIWIDGET GtkWidget*
+
+#if UI_GTK2 || UI_GTK3
+#define UIMENU   GtkMenu*
+#endif
+#ifdef UI_GTK4
+#define UIMENU   GtkPopoverMenu*
+#endif
+
+#define UI_GTK
+
+#ifdef UI_LIBADWAITA
+#include <adwaita.h>
+#endif
+
+#elif UI_MOTIF
+
+#include <Xm/XmAll.h> 
+#define UIWIDGET Widget
+#define UIMENU   Widget
+
+#elif defined(UI_QT4) || defined(UI_QT5)
+#ifdef __cplusplus
+#include <QApplication>
+#include <QWidget>
+#include <QMenu>
+#define UIWIDGET QWidget*
+#define UIMENU   QMenu*
+#else /* __cplusplus */
+#define UIWIDGET void*
+#define UIMENU   void*
+#endif
+
+#elif UI_WINUI
+
+#define UIEXPORT __declspec(dllexport) 
+
+#ifdef __cplusplus
+
+#include <functional>
+#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;
+    std::function<void()> Show;
+    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(...) (const 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 UiGeneric    UiGeneric;
+
+typedef struct UiStr        UiStr;
+
+typedef struct UiFileList   UiFileList;
+
+typedef struct UiListSelection UiListSelection;
+
+/* begin opaque types */
+typedef struct UiContext     UiContext;
+typedef struct UiContainer   UiContainer;
+typedef struct UiMenuBuilder UiMenuBuilder;
+
+typedef struct UiIcon        UiIcon;
+typedef struct UiImage       UiImage;
+
+typedef struct UiDnD         UiDnD;
+
+typedef struct UiThreadpool  UiThreadpool;
+/* end opaque types */
+
+typedef struct UiTabbedPane UiTabbedPane;
+
+typedef enum UiTri UiTri;
+typedef enum UiLabelType UiLabelType;
+
+typedef enum UiDnDAction UiDnDAction;
+
+enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
+
+enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON };
+
+enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM };
+  
+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*, int);
+
+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;
+    
+    /*
+     * obj destroy func
+     */
+    void (*destroy)(UiObject *obj);
+    
+    /*
+     * reference counter
+     */
+    unsigned int ref;
+};
+
+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*, const char*);
+    char* (*get)(UiText*);
+    char* (*getsubstr)(UiText*, int, int); /* text, begin, end */
+    void  (*insert)(UiText*, int, char*);
+    void  (*setposition)(UiText*,int);
+    int   (*position)(UiText*);
+    void  (*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;
+};
+
+struct UiGeneric {
+    void* (*get)(UiGeneric*);
+    const char* (*get_type)(UiGeneric*);
+    int (*set)(UiGeneric*, void *, const char *type);
+    void *obj;
+    
+    void *value;
+    const char *type;
+    UiObserver *observers;
+};
+    
+/*
+ * abstract list
+ */
+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 functions */
+    void (*update)(UiList *list, int i);
+    UiListSelection (*getselection)(UiList *list);
+    void (*setselection)(UiList *list, UiListSelection selection);
+    /* binding object */
+    void *obj;
+    
+    /* list of observers */
+    UiObserver *observers;
+};
+
+
+struct UiListSelection {
+    /*
+    * number of selected items
+    */
+    int count;
+
+    /*
+    * indices of selected rows
+    */
+    int *rows;
+};
+
+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
+};
+
+struct UiFileList {
+    char **files;
+    size_t nfiles;
+};
+
+typedef struct UiCondVar {
+    void *data;
+    int intdata;
+} UiCondVar;
+
+
+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_context_destroy(UiContext *ctx);
+
+UIEXPORT void ui_object_ref(UiObject *obj);
+UIEXPORT void ui_object_unref(UiObject *obj);
+
+UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
+UIEXPORT void ui_onopen(ui_callback f, void *userdata);
+UIEXPORT void ui_onexit(ui_callback f, void *userdata);
+
+UIEXPORT void ui_main();
+UIEXPORT void ui_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_call_mainthread(ui_threadfunc tf, void* td);
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads);
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool);
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, 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_allocator(UiContext *ctx);
+UIEXPORT void* ui_cx_mempool(UiContext *ctx);
+
+UIEXPORT void* ui_malloc(UiContext *ctx, size_t size);
+UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
+UIEXPORT void  ui_free(UiContext *ctx, void *ptr);
+UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str);
+
+// 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 UiGeneric* ui_generic_new(UiContext *ctx, char *name);
+
+#define ui_get(v) _Generic(v, \
+    UiInteger*: ui_int_get, \
+    UiDouble*: ui_double_get, \
+    UiString*: ui_string_get, \
+    UiText*:ui_text_get) (v)
+
+#define ui_set(v, n) _Generic(v, \
+    UiInteger*: ui_int_set, \
+    UiDouble*: ui_double_set, \
+    UiString*: ui_string_set, \
+    UiText*:ui_text_set) (v, n)
+
+UIEXPORT void ui_int_set(UiInteger *i, int64_t value);
+UIEXPORT int64_t ui_int_get(UiInteger *i);
+UIEXPORT void ui_double_set(UiDouble *d, double value);
+UIEXPORT double ui_double_get(UiDouble *d);
+UIEXPORT void ui_string_set(UiString *s, const char *value);
+UIEXPORT char* ui_string_get(UiString *s);
+UIEXPORT void ui_text_set(UiText *s, const char* value);
+UIEXPORT char* ui_text_get(UiText *s);
+
+UIEXPORT void ui_var_set_int(UiContext *ctx, const char *name, int64_t value);
+UIEXPORT int64_t ui_var_get_int(UiContext *ctx, const char *name);
+UIEXPORT void ui_var_set_double(UiContext *ctx, const char *name, double value);
+UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name);
+UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value);
+UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name);
+
+UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data);
+UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer);
+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_remove(UiList *list, int i);
+UIEXPORT void ui_list_clear(UiList *list);
+UIEXPORT void ui_list_update(UiList *list);
+UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data);
+UIEXPORT void ui_list_notify(UiList *list);
+
+UIEXPORT UiListSelection ui_list_getselection(UiList *list);
+UIEXPORT void ui_list_setselection(UiList *list, int index);
+
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list);
+UIEXPORT void ui_filelist_free(UiFileList 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 void ui_listselection_free(UiListSelection selection);
+
+
+UIEXPORT UiStr ui_str(char *cstr);
+UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v));
+
+
+UIEXPORT char* ui_getappdir(void);
+UIEXPORT char* ui_configfile(char *name);
+
+UIEXPORT UiCondVar* ui_condvar_create(void);
+UIEXPORT void ui_condvar_wait(UiCondVar *var);
+UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata);
+UIEXPORT void ui_condvar_destroy(UiCondVar *var);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TOOLKIT_H */
+
diff --git a/ui/ui/tree.h b/ui/ui/tree.h
new file mode 100644 (file)
index 0000000..83ad1e8
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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 UiListDnd       UiListDnd;
+
+typedef struct UiListArgs      UiListArgs;
+
+typedef enum UiModelType {
+    UI_STRING = 0,
+    UI_STRING_FREE,
+    UI_INTEGER,
+    UI_ICON,
+    UI_ICON_TEXT,
+    UI_ICON_TEXT_FREE
+} 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;
+    
+    /*
+     * array of column size hints
+     */
+    int *columnsize;
+    
+    /*
+     * 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 UiListArgs {
+    UiTri fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+
+    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;
+    UiMenuBuilder *contextmenu;
+    
+    const int *groups;
+};
+
+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_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragsource_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+void ui_table_dragdest_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragdest_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TREE_H */
+
diff --git a/ui/ui/ui.h b/ui/ui/ui.h
new file mode 100644 (file)
index 0000000..a0311e9
--- /dev/null
@@ -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 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"
+#include "icons.h"
+
+#endif /* UI_H */
+
diff --git a/ui/ui/window.h b/ui/ui/window.h
new file mode 100644 (file)
index 0000000..ec568a9
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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
+
+#define UI_FILEDIALOG_SELECT_SINGLE       0
+#define UI_FILEDIALOG_SELECT_MULTI        1
+#define UI_FILEDIALOG_SELECT_FOLDER       2
+
+typedef struct UiDialogArgs {
+    const char *title;
+    const char *content;
+    const char *button1_label;
+    const char *button2_label;
+    const char *closebutton_label;
+    const char *input_value;
+    UiBool input;
+    UiBool password;
+    ui_callback result;
+    void *resultdata;
+} UiDialogArgs;
+
+typedef struct UiDialogWindowArgs {
+    UiTri modal;
+    UiTri titlebar_buttons;
+    UiTri show_closebutton;
+    const char *title;
+    const char *lbutton1;
+    const char *lbutton2;
+    const char *rbutton3;
+    const char *rbutton4;
+    const int *lbutton1_groups;
+    const int *lbutton2_groups;
+    const int *rbutton3_groups;
+    const int *rbutton4_groups;
+    int default_button;
+    int width;
+    int height;
+    ui_callback onclick;
+    void *onclickdata;
+} UiDialogWindowArgs;
+
+UIEXPORT UiObject* ui_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
+
+#define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ });
+#define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 });
+
+UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
+
+#define ui_dialog(parent, ...) ui_dialog_create(parent, (UiDialogArgs){ __VA_ARGS__ } )
+
+UIEXPORT void ui_dialog_create(UiObject *parent, UiDialogArgs args);
+
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata);
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+
diff --git a/ui/winui/App.idl b/ui/winui/App.idl
new file mode 100644 (file)
index 0000000..73c7760
--- /dev/null
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+namespace winui\r
+{\r
+}\r
diff --git a/ui/winui/App.xaml b/ui/winui/App.xaml
new file mode 100644 (file)
index 0000000..c2677d7
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Application\r
+    x:Class="winui.App"\r
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\r
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\r
+    xmlns:local="using:winui">\r
+    <Application.Resources>\r
+        <ResourceDictionary>\r
+            <ResourceDictionary.MergedDictionaries>\r
+                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />\r
+                <!-- Other merged dictionaries here -->\r
+            </ResourceDictionary.MergedDictionaries>\r
+            <!-- Other app resources here -->\r
+            <SolidColorBrush x:Key="WindowCaptionBackground">Transparent</SolidColorBrush>\r
+            <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">Transparent</SolidColorBrush>\r
+        </ResourceDictionary>\r
+    </Application.Resources>\r
+</Application>\r
diff --git a/ui/winui/App.xaml.cpp b/ui/winui/App.xaml.cpp
new file mode 100644 (file)
index 0000000..b1a27ed
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
+\r
+#include "App.xaml.h"\r
+#include "MainWindow.xaml.h"\r
+\r
+#include "toolkit.h"\r
+\r
+using namespace winrt;\r
+using namespace Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::Navigation;\r
+using namespace winui;\r
+using namespace winui::implementation;\r
+\r
+// To learn more about WinUI, the WinUI project structure,\r
+// and more about our project templates, see: http://aka.ms/winui-project-info.\r
+\r
+/// <summary>\r
+/// Initializes the singleton application object.  This is the first line of authored code\r
+/// executed, and as such is the logical equivalent of main() or WinMain().\r
+/// </summary>\r
+App::App()\r
+{\r
+    InitializeComponent();\r
+\r
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION\r
+    UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)\r
+    {\r
+        if (IsDebuggerPresent())\r
+        {\r
+            auto errorMessage = e.Message();\r
+            __debugbreak();\r
+        }\r
+    });\r
+#endif\r
+}\r
+\r
+/// <summary>\r
+/// Invoked when the application is launched.\r
+/// </summary>\r
+/// <param name="e">Details about the launch request and process.</param>\r
+void App::OnLaunched(LaunchActivatedEventArgs const&)\r
+{\r
+    ui_app_run_startup();\r
+    //window = make<MainWindow>();\r
+    //window.Activate();\r
+}\r
diff --git a/ui/winui/App.xaml.h b/ui/winui/App.xaml.h
new file mode 100644 (file)
index 0000000..c2b407c
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+\r
+#include "App.xaml.g.h"\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+    struct App : AppT<App>\r
+    {\r
+        App();\r
+\r
+        void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);\r
+\r
+    private:\r
+        winrt::Microsoft::UI::Xaml::Window window{ nullptr };\r
+    };\r
+}\r
diff --git a/ui/winui/Assets/LockScreenLogo.scale-200.png b/ui/winui/Assets/LockScreenLogo.scale-200.png
new file mode 100644 (file)
index 0000000..7440f0d
Binary files /dev/null and b/ui/winui/Assets/LockScreenLogo.scale-200.png differ
diff --git a/ui/winui/Assets/SplashScreen.scale-200.png b/ui/winui/Assets/SplashScreen.scale-200.png
new file mode 100644 (file)
index 0000000..32f486a
Binary files /dev/null and b/ui/winui/Assets/SplashScreen.scale-200.png differ
diff --git a/ui/winui/Assets/Square150x150Logo.scale-200.png b/ui/winui/Assets/Square150x150Logo.scale-200.png
new file mode 100644 (file)
index 0000000..53ee377
Binary files /dev/null and b/ui/winui/Assets/Square150x150Logo.scale-200.png differ
diff --git a/ui/winui/Assets/Square44x44Logo.scale-200.png b/ui/winui/Assets/Square44x44Logo.scale-200.png
new file mode 100644 (file)
index 0000000..f713bba
Binary files /dev/null and b/ui/winui/Assets/Square44x44Logo.scale-200.png differ
diff --git a/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644 (file)
index 0000000..dc9f5be
Binary files /dev/null and b/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/ui/winui/Assets/StoreLogo.png b/ui/winui/Assets/StoreLogo.png
new file mode 100644 (file)
index 0000000..a4586f2
Binary files /dev/null and b/ui/winui/Assets/StoreLogo.png differ
diff --git a/ui/winui/Assets/Wide310x150Logo.scale-200.png b/ui/winui/Assets/Wide310x150Logo.scale-200.png
new file mode 100644 (file)
index 0000000..8b4a5d0
Binary files /dev/null and b/ui/winui/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/ui/winui/MainWindow.idl b/ui/winui/MainWindow.idl
new file mode 100644 (file)
index 0000000..45d3303
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+namespace winui\r
+{\r
+    [default_interface]\r
+    runtimeclass MainWindow : Microsoft.UI.Xaml.Window\r
+    {\r
+        MainWindow();\r
+        Int32 MyProperty;\r
+    }\r
+}\r
diff --git a/ui/winui/MainWindow.xaml b/ui/winui/MainWindow.xaml
new file mode 100644 (file)
index 0000000..064718f
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Window\r
+    x:Class="winui.MainWindow"\r
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\r
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\r
+    xmlns:local="using:winui"\r
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"\r
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"\r
+    mc:Ignorable="d">\r
+\r
+    <Window.SystemBackdrop>\r
+        <MicaBackdrop />\r
+    </Window.SystemBackdrop>\r
+\r
+    <Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*"  Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">\r
+        <StackPanel x:Name="AppTitleBar" Orientation="Horizontal" Grid.Column="0" Grid.Row="0" Padding="10,5,5,10">\r
+            <TextBlock x:Name="AppTitleTextBlock" Text="Window Title" CanDrag="True"></TextBlock>\r
+        </StackPanel>\r
+    </Grid>\r
+</Window>\r
diff --git a/ui/winui/MainWindow.xaml.cpp b/ui/winui/MainWindow.xaml.cpp
new file mode 100644 (file)
index 0000000..bb78427
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
+#include "MainWindow.xaml.h"\r
+#if __has_include("MainWindow.g.cpp")\r
+#include "MainWindow.g.cpp"\r
+#endif\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+\r
+// To learn more about WinUI, the WinUI project structure,\r
+// and more about our project templates, see: http://aka.ms/winui-project-info.\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+    MainWindow::MainWindow()\r
+    {\r
+        InitializeComponent();\r
+        //ExtendsContentIntoTitleBar(true);\r
+        //SetTitleBar(AppTitleBar());\r
+    }\r
+\r
+    int32_t MainWindow::MyProperty()\r
+    {\r
+        throw hresult_not_implemented();\r
+    }\r
+\r
+    void MainWindow::MyProperty(int32_t /* value */)\r
+    {\r
+        throw hresult_not_implemented();\r
+    }\r
+\r
+    void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)\r
+    {\r
+        \r
+    }\r
+}\r
diff --git a/ui/winui/MainWindow.xaml.h b/ui/winui/MainWindow.xaml.h
new file mode 100644 (file)
index 0000000..2060aad
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+\r
+#include "MainWindow.g.h"\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+    struct MainWindow : MainWindowT<MainWindow>\r
+    {\r
+        MainWindow();\r
+\r
+        int32_t MyProperty();\r
+        void MyProperty(int32_t value);\r
+\r
+        void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);\r
+    };\r
+}\r
+\r
+namespace winrt::winui::factory_implementation\r
+{\r
+    struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>\r
+    {\r
+    };\r
+}\r
diff --git a/ui/winui/Package.appxmanifest b/ui/winui/Package.appxmanifest
new file mode 100644 (file)
index 0000000..f273ce1
--- /dev/null
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+\r
+<Package\r
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"\r
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"\r
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"\r
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"\r
+  IgnorableNamespaces="uap rescap">\r
+\r
+  <Identity\r
+    Name="e3554f39-079b-4a0b-aa08-4ce5c4306644"\r
+    Publisher="CN=Olaf"\r
+    Version="1.0.0.0" />\r
+\r
+  <mp:PhoneIdentity PhoneProductId="e3554f39-079b-4a0b-aa08-4ce5c4306644" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>\r
+\r
+  <Properties>\r
+    <DisplayName>winui</DisplayName>\r
+    <PublisherDisplayName>Olaf</PublisherDisplayName>\r
+    <Logo>Assets\StoreLogo.png</Logo>\r
+  </Properties>\r
+\r
+  <Dependencies>\r
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />\r
+    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />\r
+  </Dependencies>\r
+\r
+  <Resources>\r
+    <Resource Language="x-generate"/>\r
+  </Resources>\r
+\r
+  <Applications>\r
+    <Application Id="App"\r
+      Executable="$targetnametoken$.exe"\r
+      EntryPoint="$targetentrypoint$">\r
+      <uap:VisualElements\r
+        DisplayName="winui"\r
+        Description="winui"\r
+        BackgroundColor="transparent"\r
+        Square150x150Logo="Assets\Square150x150Logo.png"\r
+        Square44x44Logo="Assets\Square44x44Logo.png">\r
+        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />\r
+        <uap:SplashScreen Image="Assets\SplashScreen.png" />\r
+      </uap:VisualElements>\r
+    </Application>\r
+  </Applications>\r
+\r
+  <Capabilities>\r
+    <rescap:Capability Name="runFullTrust" />\r
+  </Capabilities>\r
+</Package>\r
diff --git a/ui/winui/app.manifest b/ui/winui/app.manifest
new file mode 100644 (file)
index 0000000..cddfee2
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+  <assemblyIdentity version="1.0.0.0" name="winui.app"/>\r
+\r
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+    <application>\r
+      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+      \r
+      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+    </application>\r
+  </compatibility>\r
+  \r
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+    <windowsSettings>\r
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+    </windowsSettings>\r
+  </application>\r
+</assembly>
\ No newline at end of file
diff --git a/ui/winui/appmenu.cpp b/ui/winui/appmenu.cpp
new file mode 100644 (file)
index 0000000..5cb5274
--- /dev/null
@@ -0,0 +1,295 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "appmenu.h"\r
+\r
+#include <cx/linked_list.h>\r
+#include <cx/array_list.h>\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include "util.h"\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+static void add_top_menu_widget(MenuBar &parent, int i, UiMenuItemI* item, UiObject* obj);\r
+\r
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_radioitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+\r
+static ui_menu_add_f createMenuItem[] = {\r
+    /* UI_MENU                 */ add_menu_widget,\r
+    /* UI_MENU_ITEM            */ add_menuitem_widget,\r
+    /* UI_MENU_CHECK_ITEM      */ add_checkitem_widget,\r
+    /* UI_MENU_RADIO_ITEM      */ add_radioitem_widget,\r
+    /* UI_MENU_ITEM_LIST       */ add_menuitem_list_widget,\r
+    /* UI_MENU_CHECKITEM_LIST  */ add_menuitem_list_widget,\r
+    /* UI_MENU_RADIOITEM_LIST  */ add_menuitem_list_widget,\r
+    /* UI_MENU_SEPARATOR       */ add_menuseparator_widget\r
+};\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj) {\r
+       MenuBar mb = MenuBar();\r
+\r
+    UiMenu* menus_begin = uic_get_menu_list();\r
+\r
+    UiMenu* ls = menus_begin;\r
+    while (ls) {\r
+        UiMenu* menu = ls;\r
+        add_top_menu_widget(mb, 0, &menu->item, obj);\r
+\r
+        ls = (UiMenu*)ls->item.next;\r
+    }\r
+\r
+       return mb;\r
+}\r
+\r
+static void add_top_menu_widget(MenuBar& parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+    UiMenu* menu = (UiMenu*)item;\r
+\r
+    MenuBarItem mi = MenuBarItem();\r
+    wchar_t* wlabel = str2wstr(menu->label, NULL);\r
+    mi.Title(wlabel);\r
+    free(wlabel);\r
+\r
+    UiMenuItemI* it = menu->items_begin;\r
+    int index = 0;\r
+    while (it) {\r
+        createMenuItem[it->type](mi.Items(), index, it, obj);\r
+\r
+        it = it->next;\r
+        index++;\r
+    }\r
+\r
+    parent.Items().Append(mi);\r
+}\r
+\r
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+    UiMenu* menu = (UiMenu*)item;\r
+\r
+    MenuFlyoutSubItem mi = MenuFlyoutSubItem();\r
+    wchar_t* wlabel = str2wstr(menu->label, NULL);\r
+    mi.Text(wlabel);\r
+    free(wlabel);\r
+\r
+    parent.Append(mi);\r
+\r
+    UiMenuItemI* it = menu->items_begin;\r
+    int index = 0;\r
+    while (it) {\r
+        createMenuItem[it->type](mi.Items(), index, it, obj);\r
+\r
+        it = it->next;\r
+        index++;\r
+    }\r
+\r
+    \r
+}\r
+\r
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+    UiMenuItem* it = (UiMenuItem*)item;\r
+    \r
+    MenuFlyoutItem mi = MenuFlyoutItem();\r
+    wchar_t* wlabel = str2wstr(it->label, NULL);\r
+    mi.Text(wlabel);\r
+    free(wlabel);\r
+\r
+    parent.Append(mi);\r
+}\r
+\r
+static void add_menuseparator_widget(\r
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+    int i,\r
+    UiMenuItemI* item,\r
+    UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+static void add_checkitem_widget(\r
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+    int i,\r
+    UiMenuItemI* item,\r
+    UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+static void add_radioitem_widget(\r
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+    int i,\r
+    UiMenuItemI* item,\r
+    UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+\r
+class UiMenuList {\r
+public:\r
+    UiObject *obj = nullptr;\r
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent = { nullptr };\r
+    UiMenuItemType type;\r
+    int prevSize = 0;\r
+    int insertPos = 0;\r
+    UiVar* var = nullptr;\r
+    ui_getvaluefunc getvalue = nullptr;\r
+    ui_callback callback = nullptr;\r
+    void* userdata = nullptr;\r
+\r
+    UiMenuList() {\r
+\r
+    }\r
+\r
+    void updateItems() {\r
+        UiList* list = (UiList*)var->value;\r
+\r
+        // delete previous items\r
+        for (int i = 0; i < prevSize; i++) {\r
+            parent.RemoveAt(insertPos);\r
+        }\r
+        \r
+        // insert new items\r
+        int count = 0;\r
+        void* elm = list->first(list);\r
+        while (elm) {\r
+            char *menuItemLabel = (char*) (getvalue ? getvalue(elm, 0) : elm);\r
+\r
+            MenuFlyoutItem mi = MenuFlyoutItem();\r
+            wchar_t* wlabel = str2wstr(menuItemLabel ? menuItemLabel : "", NULL);\r
+            mi.Text(wlabel);\r
+            free(wlabel);\r
+\r
+            if (callback) {\r
+                mi.Click([this, elm, count](Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& e)\r
+                    {\r
+                        UiEvent evt;\r
+                        evt.obj = obj;\r
+                        evt.window = obj->window;\r
+                        evt.document = obj->ctx->document;\r
+                        evt.eventdata = elm;\r
+                        evt.intval = count;\r
+                        callback(&evt, userdata);\r
+                    });\r
+            }\r
+\r
+            parent.InsertAt(insertPos + count, mi);\r
+\r
+            elm = list->next(list);\r
+            count++;\r
+        }\r
+\r
+        prevSize = count;\r
+    }\r
+};\r
+\r
+extern "C" void destroy_ui_menu_list(void* ptr) {\r
+    UiMenuList* ls = (UiMenuList*)ptr;\r
+    delete ls;\r
+}\r
+\r
+static void ui_context_add_menu_list_destructor(UiContext* ctx, UiMenuList* list) {\r
+    cxMempoolRegister(ctx->mp, list, destroy_ui_menu_list);\r
+}\r
+\r
+static void ui_menulist_update(UiEvent* event, void* userdata) {\r
+    UiMenuList* mlist = (UiMenuList*)userdata;\r
+    mlist->updateItems();\r
+}\r
+\r
+static void add_menuitem_list_widget(\r
+    winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+    int i,\r
+    UiMenuItemI* item,\r
+    UiObject* obj)\r
+{\r
+    UiMenuItemList* it = (UiMenuItemList*)item;\r
+    if (!it->varname) {\r
+        return;\r
+    }\r
+    \r
+    uint32_t size = parent.Size();\r
+    \r
+    UiVar* var = uic_create_var(ui_global_context(), it->varname, UI_VAR_LIST);\r
+\r
+    UiMenuList* mlist = new UiMenuList();\r
+    mlist->obj = obj;\r
+    mlist->parent = parent;\r
+    mlist->getvalue = it->getvalue;\r
+    mlist->callback = it->callback;\r
+    mlist->userdata = it->userdata;\r
+    mlist->prevSize = 0;\r
+    mlist->insertPos = size;\r
+    mlist->type = item->type;\r
+    mlist->var = var;\r
+    ui_context_add_menu_list_destructor(obj->ctx, mlist);\r
+\r
+    UiList* list = (UiList*)var->value;\r
+    list->observers = ui_add_observer(list->observers, ui_menulist_update, mlist);\r
+\r
+    mlist->updateItems();\r
+}\r
+\r
+\r
+\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef) {\r
+    MenuFlyout flyout = MenuFlyout();\r
+\r
+    UiMenuItemI* it = menudef->items_begin;\r
+    int index = 0;\r
+    while (it) {\r
+        createMenuItem[it->type](flyout.Items(), index, it, obj);\r
+\r
+        it = it->next;\r
+        index++;\r
+    }\r
+\r
+    return flyout;\r
+}\r
+\r
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {\r
+    return NULL;\r
+}\r
+\r
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {\r
+    \r
+}\r
diff --git a/ui/winui/appmenu.h b/ui/winui/appmenu.h
new file mode 100644 (file)
index 0000000..914ce62
--- /dev/null
@@ -0,0 +1,50 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../common/menu.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
+\r
+typedef void(*ui_menu_add_f)(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int, UiMenuItemI*, UiObject*);\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj);\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef);\r
+\r
diff --git a/ui/winui/button.cpp b/ui/winui/button.cpp
new file mode 100644 (file)
index 0000000..75e2578
--- /dev/null
@@ -0,0 +1,408 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "button.h"\r
+\r
+#include "util.h"\r
+#include "container.h"\r
+#include "icons.h"\r
+\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+\r
+\r
+void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) {\r
+       // TODO: stockid\r
+\r
+       if (type == UI_LABEL_ICON) {\r
+               label = NULL;\r
+       }\r
+       else if (type == UI_LABEL_TEXT) {\r
+               icon = NULL;\r
+       }\r
+\r
+       IconElement icon_elm = { nullptr };\r
+       if (icon) {\r
+               icon_elm = ui_get_icon(icon);\r
+       }\r
+\r
+       if (label && icon_elm) {\r
+               StackPanel panel = StackPanel();\r
+               panel.Orientation(Orientation::Horizontal);\r
+               panel.Spacing(5);\r
+               \r
+               panel.Children().Append(icon_elm);\r
+\r
+               wchar_t* wlabel = str2wstr(label, nullptr);\r
+               TextBlock label = TextBlock();\r
+               label.Text(wlabel);\r
+               panel.Children().Append(label);\r
+               free(wlabel);\r
+\r
+               button.Content(panel);\r
+       }\r
+       else if (label) {\r
+               wchar_t* wlabel = str2wstr(label, nullptr);\r
+               button.Content(box_value(wlabel));\r
+               free(wlabel);\r
+       }\r
+       else if (icon_elm) {\r
+               button.Content(ui_get_icon(icon));\r
+       }\r
+}\r
+\r
+\r
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {\r
+       UiObject* current = uic_current_obj(obj);\r
+\r
+       // create button with label\r
+       Button button = Button();\r
+       ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+\r
+       // create toolkit wrapper object and register destructor\r
+       UIElement elm = button;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+       // register callback\r
+       if (args.onclick) {\r
+               ui_callback cbfunc = args.onclick;\r
+               void* cbdata = args.onclickdata;\r
+               button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) {\r
+                       UiEvent evt;\r
+                       evt.obj = obj;\r
+                       evt.window = obj->window;\r
+                       evt.document = obj->ctx->document;\r
+                       evt.eventdata = nullptr;\r
+                       evt.intval = 0;\r
+                       cbfunc(&evt, cbdata);\r
+                       });\r
+       }\r
+\r
+       // add button to current container\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       current->container->Add(button, false);\r
+\r
+       return widget;\r
+}\r
+\r
+\r
+void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) {\r
+       button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+               UiInteger* i = (UiInteger*)var->value;\r
+               UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+               ui_notify_evt(i->observers, &evt);\r
+               });\r
+}\r
+\r
+void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) {\r
+       button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+               UiInteger* i = (UiInteger*)var->value;\r
+               UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+               ui_notify_evt(i->observers, &evt);\r
+               });\r
+}\r
+\r
+void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) {\r
+       ui_callback callback = args.onchange;\r
+       void* cbdata = args.onchangedata;\r
+       if (callback) {\r
+               button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+                       UiEvent evt = ui_create_int_event(obj, true);\r
+                       callback(&evt, cbdata);\r
+                       });\r
+               button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+                       UiEvent evt = ui_create_int_event(obj, false);\r
+                       callback(&evt, cbdata);\r
+                       });\r
+       }\r
+}\r
+\r
+// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same\r
+// code again, because although everything is basically the same, it is named differently\r
+static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) {\r
+       button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+               UiInteger* i = (UiInteger*)var->value;\r
+               UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+               ui_notify_evt(i->observers, &evt);\r
+               });\r
+}\r
+\r
+static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) {\r
+       ui_callback callback = args.onchange;\r
+       void* cbdata = args.onchangedata;\r
+       if (callback) {\r
+               button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+                       UiEvent evt = ui_create_int_event(obj, button.IsOn());\r
+                       callback(&evt, cbdata);\r
+                       });\r
+       }\r
+}\r
+\r
+static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) {\r
+       if (onchange) {\r
+               UiEvent evt;\r
+               evt.obj = obj;\r
+               evt.window = obj->window;\r
+               evt.document = obj->ctx->document;\r
+               evt.eventdata = nullptr;\r
+               evt.intval = checked;\r
+               onchange(&evt, onchangedata);\r
+       }\r
+       if (enable_state > 0) {\r
+               if (checked) {\r
+                       ui_set_group(obj->ctx, enable_state);\r
+               } else {\r
+                       ui_unset_group(obj->ctx, enable_state);\r
+               }\r
+       }\r
+}\r
+\r
+static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) {\r
+       UiObject* current = uic_current_obj(obj);\r
+\r
+       // set label\r
+       ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+       togglebutton_register_callback(button, obj, args);\r
+\r
+       // create toolkit wrapper object and register destructor\r
+       UIElement elm = button;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+       // bind variable\r
+       UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);\r
+       if (var) {\r
+               UiInteger* value = (UiInteger*)var->value;\r
+               value->obj = widget;\r
+               value->get = ui_toggle_button_get;\r
+               value->set = ui_toggle_button_set;\r
+\r
+               // listener for notifying observers\r
+               togglebutton_register_checked_observers(button, obj, var);\r
+               togglebutton_register_unchecked_observers(button, obj, var);\r
+       }\r
+\r
+       if (args.enable_group > 0 || args.onchange) {\r
+               button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) {\r
+                       togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group);\r
+               });\r
+               button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) {\r
+                       togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group);\r
+                       });\r
+       }\r
+\r
+       // add button to current container\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       current->container->Add(button, false);\r
+\r
+       return widget;\r
+}\r
+\r
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {\r
+       ToggleButton button = ToggleButton();\r
+       return create_togglebutton(obj, button, args);\r
+}\r
+\r
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {\r
+       CheckBox button = CheckBox();\r
+       return create_togglebutton(obj, button, args);\r
+}\r
+\r
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {\r
+       ToggleSwitch button = ToggleSwitch();\r
+       if (args.label) {\r
+               wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+               button.Header(box_value(wlabel));\r
+               free(wlabel);\r
+       }\r
+       switch_register_callback(button, obj, args);\r
+\r
+       UiObject* current = uic_current_obj(obj);\r
+\r
+       // create toolkit wrapper object and register destructor\r
+       UIElement elm = button;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+       // bind variable\r
+       UiVar* var = nullptr;\r
+       if (args.value) {\r
+               var = uic_create_value_var(current->ctx, args.value);\r
+       }\r
+       else if (args.varname) {\r
+               var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);\r
+       }\r
+       if (var) {\r
+               UiInteger* value = (UiInteger*)var->value;\r
+               value->obj = widget;\r
+               value->get = ui_toggle_button_get;\r
+               value->set = ui_toggle_button_set;\r
+\r
+               // listener for notifying observers\r
+               switch_register_observers(button, obj, var);\r
+       }\r
+\r
+       // add button to current container\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       current->container->Add(button, false);\r
+\r
+       return widget;\r
+}\r
+\r
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {\r
+       RadioButton button = RadioButton();\r
+\r
+       UiObject* current = uic_current_obj(obj);\r
+\r
+       // set label\r
+       ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+       togglebutton_register_callback(button, obj, args);\r
+\r
+       // create toolkit wrapper object and register destructor\r
+       UIElement elm = button;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+       UiVar* var = nullptr;\r
+       if (args.value) {\r
+               var = uic_create_value_var(current->ctx, args.value);\r
+       }\r
+       else if (args.varname) {\r
+               var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);\r
+       }\r
+\r
+       // bind radio button to the value\r
+       if (var) {\r
+               UiInteger* value = (UiInteger*)var->value;\r
+\r
+               // store a list of radio buttons in the value\r
+               CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS));\r
+               // get or create the group name\r
+               static int groupCount = 0;\r
+               winrt::hstring groupName;\r
+               if (cxListSize(radioButtons) == 0) {\r
+                       groupName = winrt::to_hstring(groupCount++);\r
+               } else {\r
+                       UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0);\r
+                       RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>();\r
+                       groupName = firstRadioButton.GroupName();\r
+               }\r
+\r
+               // set the group name for the new radiobutton\r
+               cxListAdd(radioButtons, widget);\r
+               button.GroupName(groupName);\r
+\r
+               value->obj = radioButtons;\r
+               value->get = ui_radio_button_get;\r
+               value->set = ui_radio_button_set;\r
+\r
+               // listener for notifying observers (only checked, not unchecked)\r
+               togglebutton_register_checked_observers(button, obj, var);\r
+       }\r
+\r
+       // add button to current container\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       current->container->Add(button, false);\r
+\r
+       return widget;\r
+}\r
+\r
+\r
+int64_t ui_toggle_button_get(UiInteger* integer) {\r
+       UiWidget* widget = (UiWidget*)integer->obj;\r
+       ToggleButton toggleButton = widget->uielement.as<ToggleButton>();\r
+       int val = toggleButton.IsChecked().GetBoolean();\r
+       integer->value = val;\r
+       return val;\r
+}\r
+\r
+void ui_toggle_button_set(UiInteger* integer, int64_t value) {\r
+       UiWidget* widget = (UiWidget*)integer->obj;\r
+       ToggleButton toggleButton = widget->uielement.as<ToggleButton>();\r
+       toggleButton.IsChecked((bool)value);\r
+       integer->value = value;\r
+}\r
+\r
+int64_t ui_switch_get(UiInteger * integer) {\r
+       UiWidget* widget = (UiWidget*)integer->obj;\r
+       ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();\r
+       int val = toggleButton.IsOn();\r
+       integer->value = val;\r
+       return val;\r
+}\r
+\r
+void ui_switch_set(UiInteger * integer, int64_t value) {\r
+       UiWidget* widget = (UiWidget*)integer->obj;\r
+       ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();\r
+       toggleButton.IsOn((bool)value);\r
+       integer->value = value;\r
+}\r
+\r
+int64_t ui_radio_button_get(UiInteger * integer) {\r
+       CxList* list = (CxList*)integer->obj;\r
+       CxIterator i = cxListIterator(list);\r
+       int selection = -1;\r
+       cx_foreach(UiWidget*, widget, i) {\r
+               ToggleButton button = widget->uielement.as<ToggleButton>();\r
+               if (button.IsChecked().GetBoolean()) {\r
+                       selection = i.index;\r
+                       break;\r
+               }\r
+       }\r
+       integer->value = selection;\r
+       return selection;\r
+}\r
+\r
+void ui_radio_button_set(UiInteger * integer, int64_t value) {\r
+       CxList* list = (CxList*)integer->obj;\r
+       UiWidget* widget = (UiWidget*)cxListAt(list, value);\r
+       if (widget) {\r
+               ToggleButton button = widget->uielement.as<ToggleButton>();\r
+               button.IsChecked(true);\r
+               integer->value = value;\r
+       }\r
+}\r
diff --git a/ui/winui/button.h b/ui/winui/button.h
new file mode 100644 (file)
index 0000000..b4779ff
--- /dev/null
@@ -0,0 +1,51 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/container.h"\r
+\r
+#include "../ui/button.h"\r
+\r
+#include "../common/context.h"\r
+\r
+void ui_set_button_label(winrt::Microsoft::UI::Xaml::Controls::Primitives::ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type);\r
+\r
+void togglebutton_register_checked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);\r
+void togglebutton_register_unchecked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);\r
+void togglebutton_register_callback(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiToggleArgs& args);\r
+\r
+extern "C" int64_t ui_toggle_button_get(UiInteger * integer);\r
+extern "C" void ui_toggle_button_set(UiInteger * integer, int64_t value);\r
+\r
+extern "C" int64_t ui_switch_get(UiInteger * integer);\r
+extern "C" void ui_switch_set(UiInteger * integer, int64_t value);\r
+\r
+extern "C" int64_t ui_radio_button_get(UiInteger * integer);\r
+extern "C" void ui_radio_button_set(UiInteger * integer, int64_t value);\r
diff --git a/ui/winui/commandbar.cpp b/ui/winui/commandbar.cpp
new file mode 100644 (file)
index 0000000..335bddb
--- /dev/null
@@ -0,0 +1,218 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "commandbar.h"\r
+\r
+#include "util.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+#include "button.h"\r
+#include "appmenu.h"\r
+#include "icons.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i);\r
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item);\r
+static void create_toggleitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item);\r
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item);\r
+\r
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i);\r
+\r
+CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu) {\r
+       \r
+       CommandBar cb = CommandBar();\r
+       cb.DefaultLabelPosition(CommandBarDefaultLabelPosition::Right);\r
+\r
+       // add pre-configured items\r
+       CxIterator i = cxListIterator(defaults);\r
+       cx_foreach(char*, def, i) {\r
+               UiToolbarItemI* item = uic_toolbar_get_item(def);\r
+               if (!item) {\r
+                       exit(-1); // TODO: maybe an error dialog?\r
+               }\r
+               create_item(obj, cb.PrimaryCommands(), item);\r
+       }\r
+\r
+       // add appmenu\r
+       if (addappmenu) {\r
+               UiToolbarMenuItem* appmenu = uic_get_appmenu();\r
+               if (appmenu) {\r
+                       create_appmenu_items(obj, cb.SecondaryCommands(), appmenu);\r
+               }\r
+       }\r
+\r
+       return cb;\r
+}\r
+\r
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i) {\r
+       switch (i->type) {\r
+               case UI_TOOLBAR_ITEM: {\r
+                       create_cmditem(obj, cb, (UiToolbarItem*)i);\r
+                       break;\r
+               }\r
+               case UI_TOOLBAR_TOGGLEITEM: {\r
+                       create_toggleitem(obj, cb, (UiToolbarToggleItem*)i);\r
+                       break;\r
+               }\r
+               case UI_TOOLBAR_MENU: {\r
+                       create_menuitem(obj, cb, (UiToolbarMenuItem*)i);\r
+                       break;\r
+               }\r
+       }\r
+}\r
+\r
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i) {\r
+       for (UiMenuItemI* mi = i->menu.items_begin; mi; mi = mi->next) {\r
+               // convert UiMenuItemI to UiToolbarItemI\r
+               switch (mi->type) {\r
+                       case UI_MENU: {\r
+                               UiMenu* mitem = (UiMenu*)mi;\r
+                               UiToolbarMenuItem tbitem;\r
+                               memset(&tbitem, 0, sizeof(UiToolbarMenuItem));\r
+                               tbitem.item.type = UI_TOOLBAR_MENU;\r
+                               tbitem.args.label = mitem->label;\r
+                               tbitem.menu.items_begin = mitem->items_begin;\r
+                               tbitem.menu.items_end = mitem->items_end;\r
+                               create_menuitem(obj, cb, &tbitem);\r
+                               break;\r
+                       }\r
+                       case UI_MENU_ITEM: {\r
+                               UiMenuItem* mitem = (UiMenuItem*)mi;\r
+                               UiToolbarItem tbitem;\r
+                               memset(&tbitem, 0, sizeof(UiToolbarItem));\r
+                               tbitem.item.type = UI_TOOLBAR_ITEM;\r
+                               tbitem.args.label = mitem->label;\r
+                               tbitem.args.onclick = mitem->callback;\r
+                               tbitem.args.onclickdata = mitem->userdata;\r
+                               create_cmditem(obj, cb, &tbitem);\r
+                               break;\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item) {\r
+       AppBarButton button = AppBarButton();\r
+       if (item->args.label) {\r
+               wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+               button.Label(wlabel);\r
+               free(wlabel);\r
+       }\r
+       if(item->args.icon) {\r
+               button.Icon(ui_get_icon(item->args.icon));\r
+       }\r
+\r
+       UIElement elm = button;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(obj->ctx, widget);\r
+       ui_set_widget_groups(obj->ctx, widget, item->args.groups);\r
+\r
+       // register callback\r
+       if (item->args.onclick) {\r
+               ui_callback cbfunc = item->args.onclick;\r
+               void* cbdata = item->args.onclickdata;\r
+               button.Click([cbfunc, cbdata, obj](Windows::Foundation::IInspectable const& sender, RoutedEventArgs) {\r
+                       UiEvent evt;\r
+                       evt.obj = obj;\r
+                       evt.window = obj->window;\r
+                       evt.document = obj->ctx->document;\r
+                       evt.eventdata = nullptr;\r
+                       evt.intval = 0;\r
+                       cbfunc(&evt, cbdata);\r
+                       });\r
+       }\r
+\r
+       cb.Append(button);\r
+}\r
+\r
+static void create_toggleitem(UiObject *obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item) {\r
+       AppBarToggleButton button = AppBarToggleButton();\r
+       if (item->args.label) {\r
+               wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+               button.Label(wlabel);\r
+               free(wlabel);\r
+       }\r
+       if (item->args.icon) {\r
+               button.Icon(ui_get_icon(item->args.icon));\r
+       }\r
+\r
+       UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER);\r
+       if (var) {      \r
+               UIElement elm = button;\r
+               UiWidget* widget = new UiWidget(elm);\r
+               ui_context_add_widget_destructor(obj->ctx, widget);\r
+               ui_set_widget_groups(obj->ctx, widget, item->args.groups);\r
+\r
+               UiInteger* value = (UiInteger*)var->value;\r
+               int64_t i = value->value;\r
+               value->get = ui_toggle_button_get;\r
+               value->set = ui_toggle_button_set;\r
+               value->obj = widget;\r
+               ui_toggle_button_set(value, i); // init togglebutton state\r
+\r
+               // listener for notifying observers\r
+               togglebutton_register_checked_observers(button, obj, var);\r
+               togglebutton_register_unchecked_observers(button, obj, var);\r
+       }\r
+\r
+       UiToggleArgs args = {};\r
+       args.onchange = item->args.onchange;\r
+       args.onchangedata = item->args.onchangedata;\r
+       togglebutton_register_callback(button, obj, args);\r
+\r
+\r
+       cb.Append(button);\r
+}\r
+\r
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item) {\r
+       AppBarButton button = AppBarButton();\r
+       if (item->args.label) {\r
+               wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+               button.Label(wlabel);\r
+               free(wlabel);\r
+       }\r
+       if (item->args.icon) {\r
+               button.Icon(ui_get_icon(item->args.icon));\r
+       }\r
+\r
+       MenuFlyoutItem mi = MenuFlyoutItem();\r
+\r
+       MenuFlyout flyout = ui_create_menu_flyout(obj, &item->menu);\r
+       button.Flyout(flyout);\r
+\r
+       cb.Append(button);\r
+}\r
diff --git a/ui/winui/commandbar.h b/ui/winui/commandbar.h
new file mode 100644 (file)
index 0000000..198e280
--- /dev/null
@@ -0,0 +1,47 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/toolbar.h"\r
+#include "../common/toolbar.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu);\r
+\r
+extern "C" int64_t ui_appbar_togglebutton_get(UiInteger * integer);\r
+extern "C" void ui_appbar_togglebutton_set(UiInteger * integer, int64_t value);
\ No newline at end of file
diff --git a/ui/winui/condvar.cpp b/ui/winui/condvar.cpp
new file mode 100644 (file)
index 0000000..d7ffd5c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pch.h"
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+    UiWinCondVar *var = new UiWinCondVar();
+    var->var.data = NULL;
+    var->var.intdata = 0;
+    var->set = 0;
+    return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    std::unique_lock<std::mutex> lock(p->mutex);
+
+    if(!p->set) {
+        p->cond.wait(lock);
+    }
+    p->set = 0;
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    std::unique_lock<std::mutex> lock(p->mutex);
+    p->var.data = data;
+    p->var.intdata = intdata;
+    p->set = 1;
+    lock.unlock();
+    p->cond.notify_one();
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+    UiWinCondVar *p = (UiWinCondVar*)var;
+    delete p;
+}
diff --git a/ui/winui/condvar.h b/ui/winui/condvar.h
new file mode 100644 (file)
index 0000000..5561c32
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WIN_CONDVAR_H
+#define UI_WIN_CONDVAR_H
+
+#include "toolkit.h"
+
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct UiWinCondVar {
+    UiCondVar var;
+    int set;
+    std::mutex mutex;
+    std::condition_variable cond;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_WIN_CONDVAR_H */
+
diff --git a/ui/winui/container.cpp b/ui/winui/container.cpp
new file mode 100644 (file)
index 0000000..b3e6353
--- /dev/null
@@ -0,0 +1,932 @@
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2023 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+*   1. Redistributions of source code must retain the above copyright\r
+*      notice, this list of conditions and the following disclaimer.\r
+*\r
+*   2. Redistributions in binary form must reproduce the above copyright\r
+*      notice, this list of conditions and the following disclaimer in the\r
+*      documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#include "pch.h"\r
+\r
+#include "container.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include "util.h"\r
+\r
+\r
+void ui_container_begin_close(UiObject* obj) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->close = 1;\r
+}\r
+\r
+int ui_container_finish(UiObject* obj) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       if (ct->close) {\r
+               ui_end(obj);\r
+               return 0;\r
+       }\r
+       return 1;\r
+}\r
+\r
+\r
+// --------------------- UiBoxContainer ---------------------\r
+\r
+static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       Grid grid = Grid();\r
+       current->container->Add(grid, true);\r
+\r
+       UIElement elm = grid;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing);\r
+       ui_context_add_container_destructor(current->ctx, newobj->container);\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) {\r
+       return ui_box(obj, args, UI_BOX_CONTAINER_VBOX);\r
+}\r
+\r
+UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) {\r
+       return ui_box(obj, args, UI_BOX_CONTAINER_HBOX);\r
+}\r
+\r
+UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) {\r
+       this->grid = grid;\r
+       this->type = type;\r
+\r
+       Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };\r
+       grid.Margin(t);\r
+       grid.ColumnSpacing((double)spacing);\r
+       grid.RowSpacing((double)spacing);\r
+\r
+       GridLength gl;\r
+       gl.Value = 1;\r
+       gl.GridUnitType = GridUnitType::Star;\r
+\r
+       // hbox needs one row def, vbox needs one col def\r
+       // all other col/row defs are created when elements are added\r
+       if (type == UI_BOX_CONTAINER_HBOX) {\r
+               boxRowDef = RowDefinition();\r
+               boxRowDef.Height(gl);\r
+               grid.RowDefinitions().Append(boxRowDef);\r
+       } else {\r
+               boxColDef = ColumnDefinition();\r
+               boxColDef.Width(gl);\r
+               grid.ColumnDefinitions().Append(boxColDef);\r
+       }\r
+\r
+       ui_reset_layout(layout);\r
+}\r
+\r
+void UiBoxContainer::Add(FrameworkElement control, UiBool fill) {\r
+       if (this->layout.fill != UI_LAYOUT_UNDEFINED) {\r
+               fill = ui_lb2bool(this->layout.fill);\r
+       }\r
+\r
+       GridLength gl;\r
+       if (fill) {\r
+               gl.Value = 1;\r
+               gl.GridUnitType = GridUnitType::Star;\r
+       }\r
+       else {\r
+               gl.Value = 0;\r
+               gl.GridUnitType = GridUnitType::Auto;\r
+       }\r
+\r
+       control.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+       control.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+       if (type == UI_CONTAINER_HBOX) {\r
+               ColumnDefinition coldef = ColumnDefinition();\r
+               coldef.Width(gl);\r
+               grid.ColumnDefinitions().Append(coldef);\r
+               grid.SetColumn(control, grid.Children().Size());\r
+               grid.SetRow(control, 0);\r
+       } else {\r
+               RowDefinition rowdef = RowDefinition();\r
+               rowdef.Height(gl);\r
+               grid.RowDefinitions().Append(rowdef);\r
+               grid.SetRow(control, grid.Children().Size());\r
+               grid.SetColumn(control, 0);\r
+       }\r
+\r
+       grid.Children().Append(control);\r
+\r
+       ui_reset_layout(layout);\r
+}\r
+\r
+\r
+// --------------------- UiGridContainer ---------------------\r
+\r
+UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) {\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       Grid grid = Grid();\r
+       current->container->Add(grid, true);\r
+\r
+       UIElement elm = grid;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing);\r
+       ui_context_add_container_destructor(current->ctx, newobj->container);\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) {\r
+       this->grid = grid;\r
+       Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };\r
+       grid.Margin(t);\r
+       grid.ColumnSpacing((double)columnspacing);\r
+       grid.RowSpacing((double)rowspacing);\r
+       ui_reset_layout(layout);\r
+}\r
+\r
+void UiGridContainer::Add(FrameworkElement control, UiBool fill) {\r
+       GridLength gl;\r
+\r
+       bool hexpand = false;\r
+       bool vexpand = false;\r
+       bool hfill = false;\r
+       bool vfill = false;\r
+       if(layout.fill != UI_LAYOUT_UNDEFINED) {\r
+               fill = ui_lb2bool(layout.fill);\r
+       }\r
+       if (layout.hexpand != UI_LAYOUT_UNDEFINED) {\r
+               hexpand = layout.hexpand;\r
+               hfill = true;\r
+       }\r
+       if (layout.vexpand != UI_LAYOUT_UNDEFINED) {\r
+               vexpand = layout.vexpand;\r
+               vfill = true;\r
+       }\r
+       if (fill) {\r
+               hfill = true;\r
+               vfill = true;\r
+       }\r
+\r
+       // create new RowDefinition for the new line\r
+       if (layout.newline || y == -1) {\r
+               x = 0;\r
+               y++;\r
+               RowDefinition rowdef = RowDefinition();\r
+               if (vexpand) {\r
+                       gl.GridUnitType = GridUnitType::Star;\r
+                       gl.Value = 1;\r
+               }\r
+               else {\r
+                       gl.GridUnitType = GridUnitType::Auto;\r
+                       gl.Value = 0;\r
+               }\r
+               rowdef.Height(gl);\r
+               grid.RowDefinitions().Append(rowdef);\r
+       } else if (vexpand) {\r
+               // adjust row\r
+               gl.GridUnitType = GridUnitType::Star;\r
+               gl.Value = 1;\r
+               grid.RowDefinitions().GetAt(y).Height(gl);\r
+       }\r
+\r
+       // create new columndefinition, if a new column is added\r
+       if (x == cols) {\r
+               if (hexpand) {\r
+                       gl.GridUnitType = GridUnitType::Star;\r
+                       gl.Value = 1;\r
+               }\r
+               else {\r
+                       gl.GridUnitType = GridUnitType::Auto;\r
+                       gl.Value = 0;\r
+               }\r
+               ColumnDefinition coldef = ColumnDefinition();\r
+               coldef.Width(gl);\r
+               grid.ColumnDefinitions().Append(coldef);\r
+               cols++;\r
+       } else if(hexpand) {\r
+               // adjust column\r
+               if (layout.colspan == 0) {\r
+                       gl.GridUnitType = GridUnitType::Star;\r
+                       gl.Value = 1;\r
+                       grid.ColumnDefinitions().GetAt(x).Width(gl);\r
+               } else {\r
+                       int adjust_col = x;\r
+                       bool adjust = true;\r
+                       for (int i = 0; i < layout.colspan; i++) {\r
+                               if (grid.ColumnDefinitions().Size() == x + i) {\r
+                                       break;\r
+                               }\r
+                               adjust_col = x + i;\r
+                               GridLength w = grid.ColumnDefinitions().GetAt(adjust_col).Width();\r
+                               if (w.GridUnitType == GridUnitType::Star) {\r
+                                       adjust = false;\r
+                                       break;\r
+                               }\r
+                       }\r
+\r
+                       if (adjust) {\r
+                               gl.GridUnitType = GridUnitType::Star;\r
+                               gl.Value = 1;\r
+                               grid.ColumnDefinitions().GetAt(adjust_col).Width(gl);\r
+                       }\r
+               }\r
+       }\r
+\r
+       // add control\r
+       if (hfill) {\r
+               control.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+       }\r
+       if (vfill) {\r
+               control.VerticalAlignment(VerticalAlignment::Stretch);\r
+       }\r
+\r
+       if (layout.colspan > 0) {\r
+               grid.SetColumnSpan(control, layout.colspan);\r
+       }\r
+       if (layout.rowspan > 0) {\r
+               grid.SetRowSpan(control, layout.rowspan);\r
+       }\r
+\r
+       grid.SetRow(control, y);\r
+       grid.SetColumn(control, x);\r
+       grid.Children().Append(control);\r
+\r
+       x++;\r
+\r
+       ui_reset_layout(layout);\r
+}\r
+\r
+// --------------------- UI Frame ---------------------\r
+\r
+UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) {\r
+       // create a grid for the frame, that contains the label and a sub-frame\r
+       Grid frame = Grid();\r
+\r
+       GridLength gl;\r
+       gl.GridUnitType = GridUnitType::Star;\r
+       gl.Value = 1;\r
+\r
+       ColumnDefinition coldef = ColumnDefinition();\r
+       coldef.Width(gl);\r
+       frame.ColumnDefinitions().Append(coldef);\r
+\r
+       RowDefinition rowdefFrame = RowDefinition();\r
+       rowdefFrame.Height(gl);\r
+\r
+       // label\r
+       int row = 0;\r
+       if (args.label) {\r
+               RowDefinition rowdefLabel = RowDefinition();\r
+               gl.GridUnitType = GridUnitType::Auto;\r
+               gl.Value = 0;\r
+               rowdefLabel.Height(gl);\r
+               frame.RowDefinitions().Append(rowdefLabel);\r
+\r
+               TextBlock label = TextBlock();\r
+               wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+               winrt::hstring hstr(wlabel);\r
+               label.Text(hstr);\r
+               free(wlabel);\r
+\r
+               frame.SetRow(label, row++);\r
+               frame.SetColumn(label, 0);\r
+               frame.Children().Append(label);\r
+       }\r
+\r
+       // workarea frame\r
+       frame.RowDefinitions().Append(rowdefFrame);\r
+\r
+       Grid workarea = Grid();\r
+       frame.SetRow(workarea, row);\r
+       frame.SetColumn(workarea, 0);\r
+       frame.Children().Append(workarea);\r
+\r
+       // some styling for the workarea\r
+       winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) };\r
+       workarea.BorderBrush(brush);\r
+       CornerRadius radius{ 8, 8, 8, 8 };\r
+       Thickness t = { 1, 1, 1, 1 };\r
+       workarea.CornerRadius(radius);\r
+       workarea.BorderThickness(t);\r
+\r
+       Thickness padding = { 10, 10, 10, 10 };\r
+       workarea.Padding(padding);\r
+\r
+       // add frame to the parent container\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+       current->container->Add(frame, true);\r
+\r
+       UIElement elm = frame;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       // sub container\r
+       UiContainer* ctn = nullptr;\r
+       switch (args.subcontainer) {\r
+               default:\r
+               case UI_CONTAINER_VBOX: {\r
+                       ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_HBOX: {\r
+                       ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_GRID: {\r
+                       ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing);\r
+                       break;\r
+               }\r
+       }\r
+       ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = ctn;\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+// --------------------- UI Expander ---------------------\r
+\r
+UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) {\r
+       Expander expander = Expander();\r
+       if (args.label) {\r
+               wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+               expander.Header(box_value(wlabel));\r
+               free(wlabel);\r
+       }\r
+       expander.IsExpanded(args.isexpanded);\r
+\r
+       // add frame to the parent container\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+       current->container->Add(expander, true);\r
+\r
+       UIElement elm = expander;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       Grid content = Grid();\r
+       expander.Content(content);\r
+\r
+       UiContainer* ctn = nullptr;\r
+       switch (args.subcontainer) {\r
+               default: \r
+               case UI_CONTAINER_VBOX: {\r
+                       ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_HBOX: {\r
+                       ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_GRID: {\r
+                       ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);\r
+                       break;\r
+               }\r
+       }\r
+       ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = ctn;\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+// --------------------- UI ScrolledWindow ---------------------\r
+\r
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {\r
+       ScrollViewer scrollW = ScrollViewer();\r
+\r
+       // add frame to the parent container\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+       current->container->Add(scrollW, true);\r
+\r
+       UIElement elm = scrollW;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       // create child container\r
+       Grid content = Grid();\r
+       scrollW.Content(content);\r
+\r
+       UiContainer* ctn = nullptr;\r
+       switch (args.subcontainer) {\r
+               default:\r
+               case UI_CONTAINER_VBOX: {\r
+                       ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_HBOX: {\r
+                       ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_GRID: {\r
+                       ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);\r
+                       break;\r
+               }\r
+       }\r
+       ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = ctn;\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+// --------------------- UI TabView ---------------------\r
+\r
+UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) {\r
+       this->tabview = tabview;\r
+}\r
+\r
+void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) {\r
+       // noop\r
+}\r
+\r
+static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) {\r
+       UiContainer* ctn = nullptr;\r
+       switch (type) {\r
+               default:\r
+               case UI_CONTAINER_VBOX: {\r
+                       ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_HBOX: {\r
+                       ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing);\r
+                       break;\r
+               }\r
+               case UI_CONTAINER_GRID: {\r
+                       ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing);\r
+                       break;\r
+               }\r
+       }\r
+       ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+       UIElement elm = subcontainer;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       UiObject* newobj = uic_object_new(current, widget);\r
+       newobj->container = ctn;\r
+       return newobj;\r
+}\r
+\r
+static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) {\r
+       Pivot pivot = Pivot();\r
+       UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args);\r
+\r
+       return tabview;\r
+}\r
+\r
+UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) {\r
+       this->current = obj;\r
+       this->pivot = pivot;\r
+       this->subcontainer = args.subcontainer;\r
+       this->margin = args.margin;\r
+       this->spacing = args.spacing;\r
+       this->columnspacing = args.columnspacing;\r
+       this->rowspacing = args.rowspacing;\r
+}\r
+\r
+UiObject* UiPivotTabView::AddTab(const char* label, int index) {\r
+       TextBlock text = TextBlock();\r
+       wchar_t* wlabel = str2wstr(label, nullptr);\r
+       winrt::hstring hstr(wlabel);\r
+       text.Text(hstr);\r
+       free(wlabel);\r
+\r
+       PivotItem item = PivotItem();\r
+       item.Header(text);\r
+\r
+       // sub container\r
+       Grid subcontainer = Grid();\r
+       item.Content(subcontainer);\r
+       pivot.Items().Append(item);\r
+\r
+       return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiPivotTabView::Remove(int index) {\r
+       pivot.Items().RemoveAt(index);\r
+}\r
+\r
+void UiPivotTabView::Select(int index) {\r
+       \r
+}\r
+\r
+FrameworkElement UiPivotTabView::GetFrameworkElement() {\r
+       return pivot;\r
+}\r
+\r
+\r
+static UiTabView* tabview_invisible_create(UiObject *obj, UiTabViewArgs args) {\r
+       Grid container = Grid();\r
+       container.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+       container.VerticalAlignment(VerticalAlignment::Stretch);\r
+       UiInvisibleTabView *tabview = new UiInvisibleTabView(obj, container, args);\r
+       return tabview;\r
+}\r
+\r
+UiInvisibleTabView::UiInvisibleTabView(UiObject* obj, Grid container, UiTabViewArgs args) {\r
+       this->current = obj;\r
+       this->container = container;\r
+       this->subcontainer = args.subcontainer;\r
+       this->margin = args.margin;\r
+       this->spacing = args.spacing;\r
+       this->columnspacing = args.columnspacing;\r
+       this->rowspacing = args.rowspacing;\r
+       this->currentIndex = -1;\r
+\r
+       GridLength gl;\r
+       gl.GridUnitType = GridUnitType::Star;\r
+       gl.Value = 1;\r
+\r
+       ColumnDefinition coldef = ColumnDefinition();\r
+       coldef.Width(gl);\r
+       container.ColumnDefinitions().Append(coldef);\r
+\r
+       RowDefinition rowdef = RowDefinition();\r
+       rowdef.Height(gl);\r
+       container.RowDefinitions().Append(rowdef);\r
+}\r
+\r
+UiObject* UiInvisibleTabView::AddTab(const char* label, int index) {\r
+       Grid subcontainer = Grid();\r
+       subcontainer.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+       subcontainer.VerticalAlignment(VerticalAlignment::Stretch);\r
+       \r
+       if (pages.size() == 0) {\r
+               container.Children().Append(subcontainer);\r
+               currentIndex = 0;\r
+       }\r
+\r
+       if (index < 0) {\r
+               pages.push_back(subcontainer);\r
+       } else {\r
+               pages.insert(pages.begin() + index, subcontainer);\r
+       }\r
+\r
+       // sub container\r
+       return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiInvisibleTabView::Remove(int index) {\r
+       \r
+}\r
+\r
+void UiInvisibleTabView::Select(int index) {\r
+       if (index >= 0 && index < pages.size()) {\r
+               if (currentIndex != -1) {\r
+                       container.Children().RemoveAt(0);\r
+               }\r
+               \r
+               container.Children().Append(pages.at(index));\r
+       }\r
+}\r
+\r
+FrameworkElement UiInvisibleTabView::GetFrameworkElement() {\r
+       return container;\r
+}\r
+\r
+\r
+static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) {\r
+       TabView tabview = TabView();\r
+       tabview.IsAddTabButtonVisible(false);\r
+       //tabview.CanDragTabs(false);\r
+       //tabview.CanReorderTabs(false);\r
+       UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args);\r
+\r
+       return uitabview;\r
+}\r
+\r
+UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) {\r
+       this->current = obj;\r
+       this->tabview = tabview;\r
+       this->subcontainer = args.subcontainer;\r
+       this->margin = args.margin;\r
+       this->spacing = args.spacing;\r
+       this->columnspacing = args.columnspacing;\r
+       this->rowspacing = args.rowspacing;\r
+}\r
+\r
+UiObject* UiMainTabView::AddTab(const char* label, int index) {\r
+       TextBlock text = TextBlock();\r
+       wchar_t* wlabel = str2wstr(label, nullptr);\r
+       winrt::hstring hstr(wlabel);\r
+       text.Text(hstr);\r
+       free(wlabel);\r
+\r
+       TabViewItem item = TabViewItem();\r
+       item.Header(text);\r
+       item.CanDrag(false);\r
+       item.IsClosable(false);\r
+\r
+       // sub container\r
+       Grid subcontainer = Grid();\r
+       item.Content(subcontainer);\r
+       tabview.TabItems().Append(item);\r
+\r
+       return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiMainTabView::Remove(int index) {\r
+       this->tabview.TabItems().RemoveAt(index);\r
+}\r
+\r
+void UiMainTabView::Select(int index) {\r
+\r
+}\r
+\r
+FrameworkElement UiMainTabView::GetFrameworkElement() {\r
+       return tabview;\r
+}\r
+\r
+\r
+static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) {\r
+       NavigationView navigationview = NavigationView();\r
+       UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type);\r
+       navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed);\r
+       navigationview.IsSettingsVisible(false);\r
+\r
+       return tabview;\r
+}\r
+\r
+UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) {\r
+       this->current = obj;\r
+       this->navigationview = navigationview;\r
+       this->type = type;\r
+       this->margin = args.margin;\r
+       this->spacing = args.spacing;\r
+       this->columnspacing = args.columnspacing;\r
+       this->rowspacing = args.rowspacing;\r
+\r
+       if (type == UI_TABVIEW_NAVIGATION_TOP) {\r
+               navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top);\r
+       }\r
+\r
+       navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged });\r
+}\r
+\r
+UiObject* UiNavigationTabView::AddTab(const char* label, int index1) {\r
+       TextBlock text = TextBlock();\r
+       wchar_t* wlabel = str2wstr(label, nullptr);\r
+       winrt::hstring hstr(wlabel);\r
+       text.Text(hstr);\r
+       free(wlabel);\r
+\r
+       NavigationViewItem item = NavigationViewItem();\r
+       item.Content(text);\r
+\r
+       // sub container\r
+       Grid subcontainer = Grid();\r
+       if (pages.size() == 0) {\r
+               navigationview.Content(subcontainer);\r
+               navigationview.SelectedItem(item);\r
+       }\r
+\r
+       navigationview.MenuItems().Append(item);\r
+       auto page = std::tuple<NavigationViewItem, FrameworkElement>{ item, subcontainer };\r
+       pages.push_back(page);\r
+\r
+       return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiNavigationTabView::Remove(int index) {\r
+       navigationview.MenuItems().RemoveAt(index);\r
+       pages.erase(pages.begin() + index);\r
+}\r
+\r
+void UiNavigationTabView::Select(int index) {\r
+\r
+}\r
+\r
+FrameworkElement UiNavigationTabView::GetFrameworkElement() {\r
+       return navigationview;\r
+}\r
+\r
+void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) {\r
+       for (auto page : pages) {\r
+               NavigationViewItem item = std::get<0>(page);\r
+               FrameworkElement elm = std::get<1>(page);\r
+               if (item == navigationview.SelectedItem()) {\r
+                       navigationview.Content(elm);\r
+                       break;\r
+               }\r
+       }\r
+}\r
+\r
+static int64_t ui_tabview_get(UiInteger *i) {\r
+       return 0;\r
+}\r
+\r
+static void ui_tabview_set(UiInteger *i, int64_t value) {\r
+       UiTabView *tabview = (UiTabView*)i->obj;\r
+       tabview->Select(value);\r
+}\r
+\r
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {\r
+       UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview;\r
+       UiTabView* tabview = nullptr;\r
+       switch (type) {\r
+               default: {\r
+                       tabview = tabview_pivot_create(obj, args);\r
+                       break;\r
+               }\r
+               case UI_TABVIEW_DOC: { \r
+                       tabview = tabview_main_create(obj, args);\r
+                       break;\r
+               }\r
+               case UI_TABVIEW_NAVIGATION_SIDE: { \r
+                       tabview = tabview_navigationview_create(obj, args, type);\r
+                       break;\r
+               }\r
+               case UI_TABVIEW_NAVIGATION_TOP: { \r
+                       tabview = tabview_navigationview_create(obj, args, type);\r
+                       break;\r
+               }\r
+               case UI_TABVIEW_NAVIGATION_TOP2: { \r
+                       tabview = tabview_pivot_create(obj, args);\r
+                       break;\r
+               }\r
+               case UI_TABVIEW_INVISIBLE: {\r
+                       tabview = tabview_invisible_create(obj, args);\r
+                       break;\r
+               }\r
+       }\r
+       UiTabViewContainer* ctn = new UiTabViewContainer(tabview);\r
+\r
+       // add frame to the parent container\r
+       UiObject* current = uic_current_obj(obj);\r
+       UI_APPLY_LAYOUT1(current, args);\r
+       current->container->Add(tabview->GetFrameworkElement(), true);\r
+\r
+       UIElement elm = tabview->GetFrameworkElement();\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+       widget->data1 = tabview;\r
+\r
+       // TODO: add tabview destructor\r
+\r
+       // bind variable\r
+       UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);\r
+       if (var) {\r
+               UiInteger *i = (UiInteger*)var->value;\r
+               i->obj = tabview;\r
+               i->get = ui_tabview_get;\r
+               i->set = ui_tabview_set;\r
+       }\r
+\r
+       UiObject* newobj = uic_object_new(obj, widget);\r
+       newobj->container = ctn;\r
+       uic_obj_add(obj, newobj);\r
+\r
+       return widget;\r
+}\r
+\r
+void ui_tab_create(UiObject* obj, const char* title) {\r
+       UiObject* current = uic_current_obj(obj);\r
+       UiTabView* tabview = (UiTabView*)current->widget->data1;\r
+       UiObject* newobj = tabview->AddTab(title);\r
+       uic_obj_add(current, newobj);\r
+}\r
+\r
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab) {\r
+       UiTabView* t = (UiTabView*)tabview->data1;\r
+       t->Select(tab);\r
+}\r
+\r
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab) {\r
+       UiTabView* t = (UiTabView*)tabview->data1;\r
+       t->Remove(tab);\r
+}\r
+\r
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {\r
+       UiTabView* t = (UiTabView*)tabview->data1;\r
+       UiObject* newobj = t->AddTab(name, tab_index);\r
+       return newobj;\r
+}\r
+\r
+\r
+\r
+// --------------------- UI Headerbar ---------------------\r
+\r
+// TODO: replace placeholder implementation\r
+\r
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {\r
+       UiContainerArgs boxargs = { };\r
+       boxargs.fill = UI_OFF;\r
+       return ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_start_create(UiObject *obj) {\r
+       UiContainerArgs boxargs = { };\r
+       boxargs.fill = UI_OFF;\r
+       ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_center_create(UiObject *obj) {\r
+       UiContainerArgs boxargs = { };\r
+       boxargs.fill = UI_OFF;\r
+       ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_end_create(UiObject *obj) {\r
+       UiContainerArgs boxargs = { };\r
+       boxargs.fill = UI_OFF;\r
+       ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+\r
+/*\r
+* -------------------- Layout Functions --------------------\r
+*\r
+* functions for setting layout attributes for the current container\r
+*\r
+*/\r
+\r
+void ui_layout_fill(UiObject* obj, UiBool fill) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.fill = ui_bool2lb(fill);\r
+}\r
+\r
+void ui_layout_hexpand(UiObject* obj, UiBool expand) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.hexpand = expand;\r
+}\r
+\r
+void ui_layout_vexpand(UiObject* obj, UiBool expand) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.vexpand = expand;\r
+}\r
+\r
+void ui_layout_hfill(UiObject* obj, UiBool fill) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.hfill = fill;\r
+}\r
+\r
+void ui_layout_vfill(UiObject* obj, UiBool fill) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.vfill = fill;\r
+}\r
+\r
+void ui_layout_width(UiObject* obj, int width) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.width = width;\r
+}\r
+\r
+void ui_layout_height(UiObject* obj, int height) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.height = height;\r
+}\r
+\r
+void ui_layout_colspan(UiObject* obj, int cols) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.colspan = cols;\r
+}\r
+\r
+void ui_layout_rowspan(UiObject* obj, int rows) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.rowspan = rows;\r
+}\r
+\r
+void ui_newline(UiObject* obj) {\r
+       UiContainer* ct = uic_get_current_container(obj);\r
+       ct->layout.newline = TRUE;\r
+}\r
+\r
diff --git a/ui/winui/container.h b/ui/winui/container.h
new file mode 100644 (file)
index 0000000..fc66d3d
--- /dev/null
@@ -0,0 +1,187 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/container.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))\r
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)\r
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)\r
+\r
+typedef struct UiLayout UiLayout;\r
+typedef enum UiLayoutBool UiLayoutBool;\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+enum UiLayoutBool {\r
+    UI_LAYOUT_UNDEFINED = 0,\r
+    UI_LAYOUT_TRUE,\r
+    UI_LAYOUT_FALSE,\r
+};\r
+\r
+struct UiLayout {\r
+    UiLayoutBool fill;\r
+    UiBool       newline;\r
+    char* label;\r
+    UiBool       hexpand;\r
+    UiBool       vexpand;\r
+    UiBool       hfill;\r
+    UiBool       vfill;\r
+    int          width;\r
+    int          height;\r
+    int          colspan;\r
+    int          rowspan;\r
+};\r
+\r
+struct UiContainer {\r
+    UiLayout layout;\r
+    int close = 0;\r
+\r
+    virtual void Add(FrameworkElement control, UiBool fill) = 0;\r
+};\r
+\r
+enum UiBoxContainerType {\r
+    UI_BOX_CONTAINER_VBOX = 0,\r
+    UI_BOX_CONTAINER_HBOX\r
+};\r
+\r
+enum UiNavigationViewType {\r
+    UI_NAVIGATIONVIEW_TOP = 0,\r
+    UI_NAVIGATIONVIEW_SIDE\r
+};\r
+\r
+struct UiBoxContainer : UiContainer {\r
+    Grid grid;\r
+    enum UiBoxContainerType type;\r
+    RowDefinition boxRowDef;\r
+    ColumnDefinition boxColDef;\r
+\r
+    UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing);\r
+\r
+    void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiGridContainer : UiContainer {\r
+    Grid grid;\r
+    int x = 0;\r
+    int y = -1;\r
+    int cols = 0;\r
+\r
+    UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing);\r
+\r
+    void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiTabView {\r
+    UiObject* current;\r
+    UiSubContainerType subcontainer;\r
+    int margin;\r
+    int spacing;\r
+    int columnspacing;\r
+    int rowspacing;\r
+\r
+    virtual UiObject* AddTab(const char* label, int index = -1) = 0;\r
+    virtual void Remove(int index) = 0;\r
+    virtual void Select(int index) = 0;\r
+    virtual FrameworkElement GetFrameworkElement() = 0;\r
+};\r
+\r
+struct UiTabViewContainer : UiContainer {\r
+    UiTabView* tabview;\r
+\r
+    UiTabViewContainer(UiTabView* tabview);\r
+\r
+    void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiPivotTabView : UiTabView {\r
+    Pivot pivot;\r
+\r
+    UiPivotTabView(UiObject *obj, Pivot pivot, UiTabViewArgs args);\r
+\r
+    UiObject* AddTab(const char* label, int index = -1);\r
+    void Remove(int index);\r
+    void Select(int index);\r
+    FrameworkElement GetFrameworkElement();\r
+};\r
+\r
+struct UiMainTabView : UiTabView {\r
+    TabView tabview;\r
+\r
+    UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args);\r
+\r
+    UiObject* AddTab(const char* label, int index = -1);\r
+    void Remove(int index);\r
+    void Select(int index);\r
+    FrameworkElement GetFrameworkElement();\r
+};\r
+\r
+struct UiNavigationTabView : UiTabView {\r
+    NavigationView navigationview;\r
+    UiTabViewType type;\r
+    std::vector<std::tuple<NavigationViewItem, FrameworkElement> > pages;\r
+\r
+    UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type);\r
+\r
+    UiObject* AddTab(const char* label, int index = -1);\r
+    void Remove(int index);\r
+    void Select(int index);\r
+    FrameworkElement GetFrameworkElement();\r
+\r
+    void SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args);\r
+};\r
+\r
+struct UiInvisibleTabView : UiTabView {\r
+    Grid container;\r
+    std::vector<FrameworkElement> pages;\r
+    int currentIndex;\r
+\r
+    UiInvisibleTabView(UiObject *obj, Grid container, UiTabViewArgs args);\r
+\r
+    UiObject* AddTab(const char* label, int index = -1);\r
+    void Remove(int index);\r
+    void Select(int index);\r
+    FrameworkElement GetFrameworkElement();\r
+};
\ No newline at end of file
diff --git a/ui/winui/dnd.cpp b/ui/winui/dnd.cpp
new file mode 100644 (file)
index 0000000..68af932
--- /dev/null
@@ -0,0 +1,91 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "dnd.h"\r
+#include "util.h"\r
+\r
+#include <thread>\r
+\r
+using namespace winrt;\r
+using namespace Windows::ApplicationModel::DataTransfer;\r
+using namespace Windows::Storage;\r
+using namespace Windows::Storage::Streams;\r
+\r
+UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) {\r
+       if (dnd->data) {\r
+               if (len < 0) {\r
+                       len = strlen(str);\r
+               }\r
+               wchar_t *wstr = str2wstr_len(str, len, nullptr);\r
+\r
+               dnd->data.SetText(wstr);\r
+\r
+               free(wstr);\r
+\r
+       }\r
+}\r
+\r
+UIEXPORT void ui_selection_seturis(UiDnD* dnd, char** uris, int nelm) {\r
+\r
+}\r
+\r
+\r
+UIEXPORT char* ui_selection_gettext(UiDnD* dnd) {\r
+       return nullptr;\r
+}\r
+\r
+\r
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) {\r
+       UiFileList flist;\r
+       flist.files = nullptr;\r
+       flist.nfiles = 0;\r
+\r
+    if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) {\r
+               UiFileList *flist_ptr = &flist;\r
+\r
+               // we need to execute this in a different thread\r
+               // this could block the main gui thread, but shouldn't happen with a simple uri list\r
+               std::thread getDataThread([dnd, flist_ptr]() {\r
+                               auto items = dnd->dataview.GetStorageItemsAsync().get();\r
+\r
+                               char **uris = (char**)calloc(items.Size(), sizeof(char*));\r
+                               flist_ptr->files = uris;\r
+                               flist_ptr->nfiles = items.Size();\r
+\r
+                               int i = 0;\r
+                               for (IStorageItem const& item : items) {\r
+                                       winrt::hstring path = item.Path();\r
+                                       uris[i++] = wchar2utf8(path.c_str(), path.size());\r
+                               }\r
+                       });\r
+               getDataThread.join();\r
+    }\r
+       return flist;\r
+}\r
diff --git a/ui/winui/dnd.h b/ui/winui/dnd.h
new file mode 100644 (file)
index 0000000..463a9d2
--- /dev/null
@@ -0,0 +1,40 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/dnd.h"\r
+\r
+struct UiDnD {\r
+       int evttype = 0;\r
+       winrt::Microsoft::UI::Xaml::DragStartingEventArgs dndstartargs = { nullptr };\r
+       winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr };\r
+       winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr };\r
+       winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr };\r
+       winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr };\r
+};\r
diff --git a/ui/winui/icons.cpp b/ui/winui/icons.cpp
new file mode 100644 (file)
index 0000000..5c9f402
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * 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 "../ui/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;
+}
diff --git a/ui/winui/icons.h b/ui/winui/icons.h
new file mode 100644 (file)
index 0000000..073d0e1
--- /dev/null
@@ -0,0 +1,76 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2017 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+\r
+\r
+\r
+struct UiIcon {\r
+       //virtual ~UiIcon() = 0;\r
+       \r
+       virtual winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon() = 0;\r
+};\r
+\r
+struct UiSymbolIcon : UiIcon {\r
+       winrt::Microsoft::UI::Xaml::Controls::Symbol symbol;\r
+\r
+       UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym);\r
+\r
+       ~UiSymbolIcon();\r
+\r
+       winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+struct UiImageIcon : UiIcon {\r
+       winrt::Windows::Foundation::Uri uri{ nullptr };\r
+\r
+       UiImageIcon(const char* uristr);\r
+\r
+       ~UiImageIcon();\r
+\r
+       winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+struct UiBitmapIcon : UiIcon {\r
+       winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap{ nullptr };\r
+\r
+       UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap);\r
+\r
+       ~UiBitmapIcon();\r
+\r
+       winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name);\r
+\r
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large);\r
+\r
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large);\r
diff --git a/ui/winui/image.cpp b/ui/winui/image.cpp
new file mode 100644 (file)
index 0000000..cb18ae3
--- /dev/null
@@ -0,0 +1,124 @@
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2024 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+*   1. Redistributions of source code must retain the above copyright\r
+*      notice, this list of conditions and the following disclaimer.\r
+*\r
+*   2. Redistributions in binary form must reproduce the above copyright\r
+*      notice, this list of conditions and the following disclaimer in the\r
+*      documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#include "pch.h"\r
+\r
+#include "image.h"\r
+\r
+#include "toolkit.h"\r
+#include "container.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+#include "util.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;\r
+using namespace winrt::Microsoft::UI::Xaml::Media;\r
+\r
+UiImageSource::UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src) : imgsrc(src) {}\r
+\r
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+    \r
+    Image image = Image();\r
+    FrameworkElement elm = image;\r
+    if (args.scrollarea) {\r
+        ScrollViewer scroll = ScrollViewer();\r
+        scroll.Content(image);\r
+        elm = scroll;\r
+    }\r
+\r
+    // create toolkit wrapper object and register destructor\r
+    UIElement uielm = image;\r
+    UiWidget* widget = new UiWidget(uielm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+    // bind variable\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);\r
+    if (var) {\r
+        UiGeneric *value = (UiGeneric*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_image_get;\r
+        value->set = ui_image_set;\r
+    }\r
+\r
+    // add button to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(elm, true);\r
+\r
+    return widget;\r
+}\r
+\r
+extern "C" void* ui_image_get(UiGeneric *g) {\r
+\r
+\r
+    return NULL;\r
+}\r
+\r
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type) {\r
+    if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {\r
+        return 1;\r
+    }\r
+\r
+    UiImageSource *imgdata = (UiImageSource*)data;\r
+    if (g->value) {\r
+        UiImageSource *prevData = (UiImageSource*)g->value;\r
+        delete prevData;\r
+    }\r
+    g->value = imgdata;\r
+\r
+    UiWidget* widget = (UiWidget*)g->obj;\r
+    Image image = widget->uielement.as<Image>();\r
+    image.Source(imgdata->imgsrc);\r
+\r
+    return 0;\r
+}\r
+\r
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path) {\r
+    wchar_t* wpath = str2wstr(path, nullptr);\r
+    std::wstring wPath = wpath;\r
+    std::wstring uriPath = L"file:///" + wPath;\r
+    Uri uri{ uriPath };\r
+    \r
+    BitmapImage bitmapImage = BitmapImage();\r
+    bitmapImage.UriSource(uri);\r
+    ImageSource src = bitmapImage;\r
+\r
+    UiImageSource *imgdata = new UiImageSource(src);\r
+    obj->set(obj, imgdata, UI_IMAGE_OBJECT_TYPE);\r
+\r
+    free(wpath);\r
+\r
+    return 0;\r
+}\r
diff --git a/ui/winui/image.h b/ui/winui/image.h
new file mode 100644 (file)
index 0000000..637ea24
--- /dev/null
@@ -0,0 +1,43 @@
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2024 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+*   1. Redistributions of source code must retain the above copyright\r
+*      notice, this list of conditions and the following disclaimer.\r
+*\r
+*   2. Redistributions in binary form must reproduce the above copyright\r
+*      notice, this list of conditions and the following disclaimer in the\r
+*      documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+#include "../ui/image.h"\r
+\r
+class UiImageSource {\r
+public:\r
+    winrt::Microsoft::UI::Xaml::Media::ImageSource imgsrc { nullptr };\r
+\r
+    UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src);\r
+};\r
+\r
+\r
+extern "C" void* ui_image_get(UiGeneric *g);\r
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type);\r
diff --git a/ui/winui/label.cpp b/ui/winui/label.cpp
new file mode 100644 (file)
index 0000000..7a2ce4a
--- /dev/null
@@ -0,0 +1,199 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "label.h"\r
+#include "text.h"\r
+#include "util.h"\r
+\r
+#include "toolkit.h"\r
+#include "container.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textbox and toolkit wrapper\r
+    TextBlock label = TextBlock();\r
+    if (args.label) {\r
+        wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+        label.Text(wlabel);\r
+        free(wlabel);\r
+    }\r
+\r
+    UIElement elm = label;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    \r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+    if (var) {\r
+        UiString* value = (UiString*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_label_get;\r
+        value->set = ui_label_set;\r
+\r
+        // listener for notifying observers\r
+        // TODO:\r
+    }\r
+\r
+    // add label to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(label, false);\r
+\r
+    return widget;\r
+}\r
+\r
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {\r
+    args.align = UI_ALIGN_LEFT;\r
+    return ui_label_create(obj, args);\r
+}\r
+\r
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {\r
+    args.align = UI_ALIGN_RIGHT;\r
+    return ui_label_create(obj, args);\r
+}\r
+\r
+\r
+\r
+char* ui_label_get(UiString* str) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    TextBlock box = widget->uielement.as<TextBlock>();\r
+    std::wstring wstr(box.Text());\r
+    return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void  ui_label_set(UiString* str, const char* newvalue) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    TextBlock box = widget->uielement.as<TextBlock>();\r
+    box.Text(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+// -------------------- progressbar -------------------------\r
+\r
+UIWIDGET ui_progressbar_create(UiObject* obj, UiProgressbarArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textbox and toolkit wrapper\r
+    ProgressBar progressbar = ProgressBar();\r
+    progressbar.Minimum(args.min);\r
+    progressbar.Maximum(args.max == 0 ? 100 : args.max);\r
+    if (args.width > 0) {\r
+        progressbar.Width(args.width);\r
+    }\r
+\r
+    UIElement elm = progressbar;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);\r
+    if (var) {\r
+        UiDouble* value = (UiDouble*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_progressbar_get;\r
+        value->set = ui_progressbar_set;\r
+\r
+        // listener for notifying observers\r
+        // TODO:\r
+    }\r
+\r
+    // add button to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(progressbar, false);\r
+\r
+    return widget;\r
+}\r
+\r
+double ui_progressbar_get(UiDouble * d) {\r
+    UiWidget* widget = (UiWidget*)d->obj;\r
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();\r
+    d->value = progressbar.Value();\r
+    return d->value;\r
+}\r
+\r
+void  ui_progressbar_set(UiDouble * d, double newvalue) {\r
+    UiWidget* widget = (UiWidget*)d->obj;\r
+    ProgressBar progressbar = widget->uielement.as<ProgressBar>();\r
+    d->value = newvalue;\r
+    progressbar.Value(newvalue);\r
+}\r
+\r
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textbox and toolkit wrapper\r
+    ProgressRing spinner = ProgressRing();\r
+    spinner.IsActive(false);\r
+\r
+    UIElement elm = spinner;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);\r
+    if (var) {\r
+        UiInteger* value = (UiInteger*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_progressspinner_get;\r
+        value->set = ui_progressspinner_set;\r
+\r
+        // listener for notifying observers\r
+        // TODO:\r
+    }\r
+\r
+    // add button to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(spinner, false);\r
+\r
+    return widget;\r
+}\r
+\r
+int64_t ui_progressspinner_get(UiInteger * i) {\r
+    UiWidget* widget = (UiWidget*)i->obj;\r
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();\r
+    i->value = spinner.IsActive();\r
+    return i->value;\r
+}\r
+\r
+void  ui_progressspinner_set(UiInteger * i, int64_t newvalue) {\r
+    UiWidget* widget = (UiWidget*)i->obj;\r
+    ProgressRing spinner = widget->uielement.as<ProgressRing>();\r
+    i->value = newvalue != 0 ? 1 : 0;\r
+    spinner.IsActive(i->value);\r
+}\r
diff --git a/ui/winui/label.h b/ui/winui/label.h
new file mode 100644 (file)
index 0000000..3911578
--- /dev/null
@@ -0,0 +1,42 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+#include "../ui/display.h"\r
+\r
+extern "C" char* ui_label_get(UiString * str);\r
+extern "C" void  ui_label_set(UiString * str, const char* newvalue);\r
+\r
+extern "C" double ui_progressbar_get(UiDouble *d);\r
+extern "C" void  ui_progressbar_set(UiDouble *d, double newvalue);\r
+\r
+extern "C" int64_t ui_progressspinner_get(UiInteger * i);\r
+extern "C" void  ui_progressspinner_set(UiInteger * i, int64_t newvalue);\r
diff --git a/ui/winui/list.cpp b/ui/winui/list.cpp
new file mode 100644 (file)
index 0000000..e9f87ca
--- /dev/null
@@ -0,0 +1,346 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "list.h"\r
+#include "container.h"\r
+#include "util.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+\r
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create listview and toolkit wrapper\r
+    ListView listview = ListView();\r
+    if (args.multiselection) {\r
+        listview.SelectionMode(ListViewSelectionMode::Extended);\r
+    }\r
+\r
+    bool clickEnabled = listview.IsItemClickEnabled();\r
+    listview.IsItemClickEnabled(true);\r
+    \r
+\r
+    UIElement elm = listview;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    widget->data1 = args.model;\r
+    widget->data2 = args.getvalue;\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    // bind var\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+    if (var) {\r
+        UiList* list = (UiList*)var->value;\r
+        list->update = ui_simple_list_update;\r
+        list->getselection = ui_listview_getselection;\r
+        list->setselection = ui_listview_setselection;\r
+        list->obj = widget;\r
+\r
+        ui_simple_list_update(list, 0);\r
+    }\r
+\r
+    if (args.onselection) {\r
+        ui_callback onselection = args.onselection;\r
+        void* cbdata = args.onselectiondata;\r
+        listview.SelectionChanged([onselection, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());\r
+\r
+            UiListSelection selection;\r
+            selection.rows = selectedRows.data();\r
+            selection.count = selectedRows.size();\r
+\r
+            UiEvent evt;\r
+            evt.obj = obj;\r
+            evt.window = obj->window;\r
+            evt.document = obj->ctx->document;\r
+            evt.eventdata = &selection;\r
+            evt.intval = 0;\r
+            onselection(&evt, cbdata);\r
+            });\r
+    }\r
+    if (args.onactivate) {\r
+        ui_callback cb = args.onactivate;\r
+        void* cbdata = args.onactivatedata;\r
+        listview.ItemClick([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+            std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());\r
+            UiListSelection selection;\r
+            selection.rows = selectedRows.data();\r
+            selection.count = selectedRows.size();\r
+\r
+            UiEvent evt;\r
+            evt.obj = obj;\r
+            evt.window = obj->window;\r
+            evt.document = obj->ctx->document;\r
+            evt.eventdata = &selection;\r
+            evt.intval = 0;\r
+            cb(&evt, cbdata);\r
+            });\r
+    }\r
+\r
+    // add listview to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(listview, false);\r
+\r
+    return widget;\r
+}\r
+\r
+\r
+UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create listview and toolkit wrapper\r
+    ComboBox combobox = ComboBox();\r
+\r
+    UIElement elm = combobox;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    widget->data1 = args.model;\r
+    widget->data2 = args.getvalue;\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    // bind var\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+    if (var) {\r
+        UiList* list = (UiList*)var->value;\r
+        list->update = ui_simple_list_update;\r
+        list->getselection = ui_dropdown_getselection;\r
+        list->setselection = ui_dropdown_setselection;\r
+        list->obj = widget;\r
+        ui_simple_list_update(list, 0);\r
+    }\r
+\r
+    if (args.onactivate) {\r
+        ui_callback cb = args.onactivate;\r
+        void* cbdata = args.onactivatedata;\r
+        combobox.SelectionChanged([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+            int selectedrow = sender.as<ComboBox>().SelectedIndex();\r
+            UiListSelection selection;\r
+            selection.count = 1;\r
+            selection.rows = &selectedrow;\r
+\r
+            UiEvent evt;\r
+            evt.obj = obj;\r
+            evt.window = obj->window;\r
+            evt.document = obj->ctx->document;\r
+            evt.eventdata = &selection;\r
+            evt.intval = selectedrow;\r
+            cb(&evt, cbdata);\r
+            });\r
+    }\r
+\r
+    // add listview to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(combobox, false);\r
+\r
+    return widget;\r
+}\r
+\r
+UiListSelection ui_listview_getselection(UiList *list) {\r
+    UiWidget *widget = (UiWidget*)list->obj;\r
+    ListView listview = widget->uielement.as<ListView>();\r
+    std::vector<int> selectedRows = ui_create_listview_selection(listview);\r
+\r
+    UiListSelection selection = { NULL, 0 };\r
+    if (selectedRows.size() > 0) {\r
+        selection.count = selectedRows.size();\r
+        int *data = selectedRows.data();\r
+        selection.rows = (int*)calloc(selection.count, sizeof(int));\r
+        memcpy(selection.rows, data, selection.count);\r
+    }\r
+\r
+    return selection;\r
+}\r
+\r
+void ui_listview_setselection(UiList *list, UiListSelection selection) {\r
+    UiWidget* widget = (UiWidget*)list->obj;\r
+    if (selection.count > 0) {\r
+        ListView listview = widget->uielement.as<ListView>();\r
+        listview.SelectedIndex(selection.rows[0]);\r
+    }\r
+}\r
+\r
+UiListSelection ui_dropdown_getselection(UiList *list) {\r
+    UiWidget* widget = (UiWidget*)list->obj;\r
+    ComboBox cb = widget->uielement.as<ComboBox>();\r
+    int index = cb.SelectedIndex();\r
+    UiListSelection selection = { NULL, 0 };\r
+    if (index >= 0) {\r
+        selection.rows = (int*)calloc(1, sizeof(int));\r
+        selection.count = 1;\r
+        selection.rows[0] = index;\r
+    }\r
+    return selection;\r
+}\r
+\r
+void ui_dropdown_setselection(UiList *list, UiListSelection selection) {\r
+    UiWidget* widget = (UiWidget*)list->obj;\r
+    if (selection.count > 0) {\r
+        ComboBox cb = widget->uielement.as<ComboBox>();\r
+        cb.SelectedIndex(selection.rows[0]);\r
+    }\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create listview and toolkit wrapper\r
+    BreadcrumbBar bcbar = BreadcrumbBar();\r
+\r
+    UIElement elm = bcbar;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    widget->data1 = args.model;\r
+    widget->data2 = args.getvalue;\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    // bind var\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+    if (var) {\r
+        UiList* list = (UiList*)var->value;\r
+        list->update = ui_breadcrumbbar_update;\r
+        list->obj = widget;\r
+        ui_breadcrumbbar_update(list, 0);\r
+    }\r
+\r
+    if (args.onactivate) {\r
+        ui_callback cb = args.onactivate;\r
+        void* cbdata = args.onactivatedata;\r
+        bcbar.ItemClicked([cb, cbdata, obj](IInspectable const& sender, BreadcrumbBarItemClickedEventArgs evtargs) {\r
+            UiEvent evt;\r
+            evt.obj = obj;\r
+            evt.window = obj->window;\r
+            evt.document = obj->ctx->document;\r
+            evt.eventdata = nullptr;\r
+            evt.intval = evtargs.Index();\r
+            cb(&evt, cbdata);\r
+            });\r
+    }\r
+\r
+    // add listview to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(bcbar, false);\r
+\r
+    return widget;\r
+}\r
+\r
+static void* getstrvalue(void* elm, int ignore) {\r
+    return elm;\r
+}\r
+\r
+void ui_simple_list_update(UiList* list, int i) {\r
+    UiWidget* widget = (UiWidget*)list->obj;\r
+    UiModel* model = (UiModel*)widget->data1;\r
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;\r
+    ItemsControl listview = widget->uielement.as<ItemsControl>();\r
+    auto items = listview.Items();\r
+\r
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)\r
+    if (getvalue == nullptr) {\r
+        if (model && model->getvalue) {\r
+            getvalue = model->getvalue;\r
+        } else {\r
+            getvalue = getstrvalue;\r
+        }\r
+    }\r
+\r
+    // add list elements to listview.Items\r
+    items.Clear();\r
+    void* elm = list->first(list);\r
+    while (elm) {\r
+        char* value = (char*)getvalue(elm, 0);\r
+        wchar_t* wstr = str2wstr(value, nullptr);\r
+        items.Append(box_value(wstr));\r
+        free(wstr);\r
+\r
+        elm = list->next(list);\r
+    }\r
+}\r
+\r
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i) {\r
+    UiWidget* widget = (UiWidget*)list->obj;\r
+    UiModel* model = (UiModel*)widget->data1;\r
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;\r
+\r
+    // priority: getvalue, model.getvalue, getstrvalue (fallback)\r
+    if (getvalue == nullptr) {\r
+        if (model && model->getvalue) {\r
+            getvalue = model->getvalue;\r
+        }\r
+        else {\r
+            getvalue = getstrvalue;\r
+        }\r
+    }\r
+\r
+    BreadcrumbBar bar = widget->uielement.as<BreadcrumbBar>();\r
+    \r
+    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> items { winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };\r
+    void* elm = list->first(list);\r
+    while (elm) {\r
+        char* value = (char*)getvalue(elm, 0);\r
+        wchar_t* wstr = str2wstr(value, nullptr);\r
+        items.Append(box_value(wstr));\r
+        free(wstr);\r
+\r
+        elm = list->next(list);\r
+    }\r
+\r
+    bar.ItemsSource(items);\r
+}\r
+\r
+\r
+std::vector<int> ui_create_listview_selection(ListView listview) {\r
+    std::vector<int> selection;\r
+    int p = 0;\r
+    auto ranges = listview.SelectedRanges();\r
+    for (auto range : ranges) {\r
+        int begin = range.FirstIndex();\r
+        int end = range.LastIndex();\r
+        for (int i = begin; i <= end; i++) {\r
+            selection.push_back(i);\r
+        }\r
+    }\r
+    return selection;\r
+}\r
+\r
diff --git a/ui/winui/list.h b/ui/winui/list.h
new file mode 100644 (file)
index 0000000..bbb63f6
--- /dev/null
@@ -0,0 +1,47 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/tree.h"\r
+#include "toolkit.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+\r
+extern "C" void ui_simple_list_update(UiList * list, int i);\r
+\r
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i);\r
+\r
+std::vector<int> ui_create_listview_selection(winrt::Microsoft::UI::Xaml::Controls::ListView listview);\r
+\r
+extern "C" UiListSelection ui_listview_getselection(UiList *list);\r
+extern "C" void ui_listview_setselection(UiList *list, UiListSelection selection);\r
+\r
+extern "C" UiListSelection ui_dropdown_getselection(UiList *list);\r
+extern "C" void ui_dropdown_setselection(UiList *list, UiListSelection selection);\r
diff --git a/ui/winui/packages.config b/ui/winui/packages.config
new file mode 100644 (file)
index 0000000..fc8fa30
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.240405.15" targetFramework="native" />\r
+  <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240803.1" targetFramework="native" />\r
+  <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.1742" targetFramework="native" />\r
+  <package id="Microsoft.WindowsAppSDK" version="1.5.241001000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
diff --git a/ui/winui/pch.cpp b/ui/winui/pch.cpp
new file mode 100644 (file)
index 0000000..83ab966
--- /dev/null
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
diff --git a/ui/winui/pch.h b/ui/winui/pch.h
new file mode 100644 (file)
index 0000000..5ed2dd1
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+#include <windows.h>\r
+#include <unknwn.h>\r
+#include <restrictederrorinfo.h>\r
+#include <hstring.h>\r
+\r
+// Undefine GetCurrentTime macro to prevent\r
+// conflict with Storyboard::GetCurrentTime\r
+#undef GetCurrentTime\r
+\r
+#include <winrt/Windows.Foundation.h>\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.ApplicationModel.Activation.h>\r
+#include <winrt/Microsoft.UI.Composition.h>\r
+#include <winrt/Microsoft.UI.Windowing.h>\r
+#include <winrt/Microsoft.UI.Xaml.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.Data.h>\r
+#include <winrt/Microsoft.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>\r
+#include <winrt/Microsoft.UI.Xaml.Navigation.h>\r
+#include <winrt/Microsoft.UI.Xaml.Shapes.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Dispatching.h>\r
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>\r
+#include <wil/cppwinrt_helpers.h>\r
+#include <winrt/Microsoft.UI.Xaml.Input.h>\r
+#include <winrt/Microsoft.UI.Input.h>\r
+#include <winrt/Windows.UI.Core.h>\r
+#include <winrt/Windows.ApplicationModel.h>\r
+#include <winrt/Windows.Storage.Pickers.h>\r
+\r
+#include <winrt\Microsoft.UI.Dispatching.h>\r
+\r
+#include <winrt/Windows.Storage.Streams.h>\r
+\r
+#include <Microsoft.UI.Xaml.Window.h>\r
+\r
+#include <shobjidl_core.h>\r
diff --git a/ui/winui/readme.txt b/ui/winui/readme.txt
new file mode 100644 (file)
index 0000000..e0c958e
--- /dev/null
@@ -0,0 +1,27 @@
+========================================================================\r
+    winui Project Overview\r
+========================================================================\r
+\r
+This project demonstrates how to get started writing WinUI3 apps directly\r
+with standard C++, using the Windows App SDK and C++/WinRT packages and\r
+XAML compiler support to generate implementation headers from interface\r
+(IDL) files. These headers can then be used to implement the local\r
+Windows Runtime classes referenced in the app's XAML pages.\r
+\r
+Steps:\r
+1. Create an interface (IDL) file to define any local Windows Runtime\r
+    classes referenced in the app's XAML pages.\r
+2. Build the project once to generate implementation templates under\r
+    the "Generated Files" folder, as well as skeleton class definitions\r
+    under "Generated Files\sources".\r
+3. Use the skeleton class definitions for reference to implement your\r
+    Windows Runtime classes.\r
+\r
+========================================================================\r
+Learn more about Windows App SDK here:\r
+https://docs.microsoft.com/windows/apps/windows-app-sdk/\r
+Learn more about WinUI3 here:\r
+https://docs.microsoft.com/windows/apps/winui/winui3/\r
+Learn more about C++/WinRT here:\r
+http://aka.ms/cppwinrt/\r
+========================================================================\r
diff --git a/ui/winui/stock.cpp b/ui/winui/stock.cpp
new file mode 100644 (file)
index 0000000..740b12f
--- /dev/null
@@ -0,0 +1,5 @@
+\r
+\r
+#include "pch.h"\r
+\r
+#include "stock.h"
\ No newline at end of file
diff --git a/ui/winui/stock.h b/ui/winui/stock.h
new file mode 100644 (file)
index 0000000..79e8ef9
--- /dev/null
@@ -0,0 +1,32 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2017 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/stock.h"\r
+\r
diff --git a/ui/winui/table.cpp b/ui/winui/table.cpp
new file mode 100644 (file)
index 0000000..3641f99
--- /dev/null
@@ -0,0 +1,648 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "table.h"\r
+#include "container.h"\r
+#include "util.h"\r
+#include "icons.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+#include "../common/types.h"\r
+\r
+#include <winrt/Microsoft.UI.Xaml.Data.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.h>\r
+#include <winrt/Microsoft.UI.Xaml.Input.h>\r
+#include <winrt/Windows.UI.Core.h>\r
+#include <winrt/Windows.ApplicationModel.h>\r
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Windows::UI::Xaml::Input;\r
+\r
+static UINT ui_double_click_time = GetDoubleClickTime();\r
+\r
+extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) {\r
+       // TODO:\r
+}\r
+\r
+static void textblock_set_str(TextBlock& t, const char* str) {\r
+       if (str) {\r
+               wchar_t* wstr = str2wstr(str, nullptr);\r
+               t.Text(winrt::hstring(wstr));\r
+               free(wstr);\r
+       }\r
+}\r
+\r
+static void textblock_set_int(TextBlock& t, int i) {\r
+       wchar_t buf[16];\r
+       swprintf(buf, 16, L"%d", i);\r
+       t.Text(winrt::hstring(buf));\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) {\r
+       if (!args.model) {\r
+               return nullptr;\r
+       }\r
+\r
+       UiObject* current = uic_current_obj(obj);\r
+\r
+       // create widgets and wrapper obj\r
+       ScrollViewer scrollW = ScrollViewer();\r
+       Grid grid = Grid();\r
+       scrollW.Content(grid);\r
+       UiTable* uitable = new UiTable(obj, scrollW, grid);\r
+       reg_table_destructor(current->ctx, uitable);\r
+       \r
+       uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue;\r
+       uitable->onselection = args.onselection;\r
+       uitable->onselectiondata = args.onselectiondata;\r
+       uitable->onactivate = args.onactivate;\r
+       uitable->onactivatedata = args.onactivatedata;\r
+       uitable->ondragstart = args.ondragstart;\r
+       uitable->ondragstartdata = args.ondragstartdata;\r
+       uitable->ondragcomplete = args.ondragcomplete;\r
+       uitable->ondrop = args.ondrop;\r
+       uitable->ondropdata = args.ondropsdata;\r
+\r
+       // grid styling\r
+       winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color\r
+       SolidColorBrush brush = SolidColorBrush(bg);\r
+       grid.Background(brush);\r
+\r
+       // add columns from args.model\r
+       uitable->add_header(args.model);\r
+       \r
+       // bind var\r
+       UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+       if (var) {\r
+               UiList* list = (UiList*)var->value;\r
+               list->update = ui_table_update;\r
+               list->getselection = ui_table_selection;\r
+               list->obj = uitable;\r
+               uitable->update(list, 0);\r
+       }\r
+\r
+       // create toolkit wrapper object and register destructor\r
+       UIElement elm = scrollW;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+       // add scrollW to current container\r
+       UI_APPLY_LAYOUT1(current, args);\r
+\r
+       current->container->Add(scrollW, false);\r
+\r
+       return widget;\r
+}\r
+\r
+extern "C" void ui_table_update(UiList * list, int i) {\r
+       UiTable* table = (UiTable*)list->obj;\r
+       table->clear();\r
+       table->update(list, i);\r
+}\r
+\r
+extern "C" UiListSelection ui_table_selection(UiList * list) {\r
+       UiTable* table = (UiTable*)list->obj;\r
+       return table->uiselection();\r
+}\r
+\r
+UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) {\r
+       this->obj = obj;\r
+\r
+       this->scrollw = scrollw;\r
+       this->grid = grid;\r
+\r
+       winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 };\r
+       highlightBrush = SolidColorBrush(highlightBg);\r
+\r
+       winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default\r
+       defaultBrush = SolidColorBrush(defaultBg);\r
+\r
+       winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color\r
+       selectedBrush = SolidColorBrush(selectedBg);\r
+\r
+       winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color\r
+       selectedBorderBrush = SolidColorBrush(selectedFg);\r
+\r
+       grid.KeyDown(\r
+               winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(\r
+                       [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) {\r
+                               // key event for hanling the table cursor or enter\r
+                       })\r
+       );\r
+}\r
+\r
+UiTable::~UiTable() {\r
+       ui_model_free(NULL, model);\r
+}\r
+\r
+void UiTable::add_header(UiModel* model) {\r
+       this->model = ui_model_copy(NULL, model);\r
+\r
+       GridLength gl;\r
+       gl.Value = 0;\r
+       gl.GridUnitType = GridUnitType::Auto;\r
+\r
+       // add header row definition\r
+       auto headerRowDef = RowDefinition();\r
+       headerRowDef.Height(gl);\r
+       grid.RowDefinitions().Append(headerRowDef);\r
+\r
+       winrt::Windows::UI::Color borderColor = { 63, 0, 0, 0 };\r
+       SolidColorBrush borderBrush = SolidColorBrush(borderColor);\r
+\r
+\r
+       for (int i = 0; i < model->columns;i++) {\r
+               char* title = model->titles[i];\r
+               UiModelType type = model->types[i];\r
+\r
+               // add grid column definition\r
+               auto colDef = ColumnDefinition();\r
+               colDef.Width(gl);\r
+               grid.ColumnDefinitions().Append(colDef);\r
+\r
+               // header column border\r
+               Border headerBorder = Border();\r
+               Thickness border = { 0,0,1,0 };\r
+               headerBorder.BorderThickness(border);\r
+               headerBorder.BorderBrush(borderBrush);\r
+\r
+               // add text\r
+               auto hLabel = TextBlock();\r
+               textblock_set_str(hLabel, title);\r
+               Thickness cellpadding = { 10,4,4,4 };\r
+               hLabel.Padding(cellpadding);\r
+               hLabel.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+               // event handler for highlighting and column resizing\r
+               headerBorder.PointerPressed(\r
+                       winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                               [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                       // the last column doesn't need resize capabilities\r
+                                       if (i + 1 < model->columns) {\r
+                                               double width = headerBorder.ActualWidth();\r
+                                               auto point = args.GetCurrentPoint(headerBorder);\r
+                                               auto position = point.Position();\r
+                                               if (position.X + 4 >= width) {\r
+                                                       this->resize = true;\r
+                                                       this->resizedCol = headerBorder;\r
+                                               }\r
+                                       }\r
+                               })\r
+               );\r
+               headerBorder.PointerReleased(\r
+                       winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                               [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                       this->resize = false;\r
+                               })\r
+               );\r
+               headerBorder.PointerMoved(\r
+                       winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                               [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                       if (this->resize) {\r
+                                               auto point = args.GetCurrentPoint(this->resizedCol);\r
+                                               auto position = point.Position();\r
+                                               if (position.X > 1) {\r
+                                                       this->resizedCol.Width(position.X);\r
+                                               }\r
+                                       }\r
+                               })\r
+               );\r
+               headerBorder.PointerEntered(\r
+                       winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                               [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                       // TODO: background\r
+                               })\r
+               );\r
+               headerBorder.PointerExited(\r
+                       winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                               [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                       // TODO: background\r
+                               })\r
+               );\r
+\r
+\r
+\r
+               // add controls\r
+               headerBorder.Child(hLabel);\r
+\r
+               grid.SetColumn(headerBorder, i);\r
+               grid.SetRow(headerBorder, 0);\r
+               grid.Children().Append(headerBorder);\r
+\r
+               UiTableColumn h;\r
+               h.header = headerBorder;\r
+               header.push_back(h);\r
+       }\r
+\r
+       maxrows = 1;\r
+}\r
+\r
+static ULONG64 getsystime() {\r
+       SYSTEMTIME st;\r
+       GetSystemTime(&st);\r
+       return st.wYear   * 10000000000000 +\r
+                  st.wMonth  * 100000000000   +\r
+                  st.wDay    * 1000000000     +\r
+                  st.wHour   * 10000000       +\r
+                  st.wMinute * 100000         +\r
+                  st.wSecond * 1000           +\r
+                  st.wMilliseconds;\r
+}\r
+\r
+void UiTable::update(UiList* list,  int i) {\r
+       if (getvalue == nullptr) {\r
+               return;\r
+       }\r
+\r
+       Thickness b1 = { 1, 1, 0, 1 }; // first col\r
+       Thickness b2 = { 0, 1, 0, 1 }; // middle\r
+       Thickness b3 = { 0, 1, 1, 1 }; // last col\r
+\r
+       GridLength gl;\r
+       gl.Value = 0;\r
+       gl.GridUnitType = GridUnitType::Auto;\r
+\r
+       // iterate model\r
+       int row = 1;\r
+       void* elm = list->first(list);\r
+       while (elm) {\r
+               if (row >= maxrows) {\r
+                       auto rowdef = RowDefinition();\r
+                       rowdef.Height(gl);\r
+                       grid.RowDefinitions().Append(rowdef);\r
+                       maxrows = row;\r
+               }\r
+\r
+               Thickness cellpadding = { 10,0,4,0 };\r
+\r
+               // model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model\r
+               int model_col = 0;\r
+               for (int col = 0; col < header.size(); col++, model_col++) {\r
+                       // create ui elements with the correct cell border\r
+                       // dependeing on the column\r
+                       Border cellBorder = Border();\r
+                       cellBorder.Background(defaultBrush);\r
+                       cellBorder.BorderBrush(defaultBrush);\r
+                       if (col == 0) {\r
+                               cellBorder.BorderThickness(b1);\r
+                       }\r
+                       else if (col + 1 == header.size()) {\r
+                               cellBorder.BorderThickness(b3);\r
+                       }\r
+                       else {\r
+                               cellBorder.BorderThickness(b2);\r
+                       }\r
+\r
+                       // dnd\r
+                       if (ondragstart) {\r
+                               cellBorder.CanDrag(true);\r
+                               cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) {\r
+                                               UiDnD dnd;\r
+                                               dnd.evttype = 0;\r
+                                               dnd.dndstartargs = args;\r
+                                               dnd.dndcompletedargs = { nullptr };\r
+                                               dnd.drageventargs = { nullptr };\r
+                                               dnd.data = args.Data();\r
+\r
+                                               UiEvent evt;\r
+                                               evt.obj = this->obj;\r
+                                               evt.window = evt.obj->window;\r
+                                               evt.document = obj->ctx->document;\r
+                                               evt.eventdata = &dnd;\r
+                                               evt.intval = 0;\r
+                                       \r
+                                               this->ondragstart(&evt, this->ondragstartdata);\r
+                                       });\r
+                               cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) {\r
+                                               UiDnD dnd;\r
+                                               dnd.evttype = 1;\r
+                                               dnd.dndstartargs = { nullptr };\r
+                                               dnd.dndcompletedargs = args;\r
+                                               dnd.drageventargs = { nullptr };\r
+                                               dnd.data = { nullptr };\r
+\r
+                                               UiEvent evt;\r
+                                               evt.obj = this->obj;\r
+                                               evt.window = evt.obj->window;\r
+                                               evt.document = obj->ctx->document;\r
+                                               evt.eventdata = &dnd;\r
+                                               evt.intval = 0;\r
+\r
+                                               if (this->ondragcomplete) {\r
+                                                       this->ondragcomplete(&evt, this->ondragcompletedata);\r
+                                               }\r
+                                       });\r
+                       }\r
+                       if (ondrop) {\r
+                               cellBorder.AllowDrop(true);\r
+                               cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){\r
+                                               UiDnD dnd;\r
+                                               dnd.evttype = 2;\r
+                                               dnd.dndstartargs = { nullptr };\r
+                                               dnd.dndcompletedargs = { nullptr };\r
+                                               dnd.drageventargs = args;\r
+                                               dnd.dataview = args.DataView();\r
+\r
+                                               UiEvent evt;\r
+                                               evt.obj = this->obj;\r
+                                               evt.window = evt.obj->window;\r
+                                               evt.document = obj->ctx->document;\r
+                                               evt.eventdata = &dnd;\r
+                                               evt.intval = 0;\r
+\r
+                                               this->ondrop(&evt, this->ondropdata);\r
+                                       }));\r
+                               cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){\r
+                                       args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);\r
+                                       }));\r
+                       }\r
+\r
+                       // set the cell value\r
+                       // depending on the type, we create different cell controls\r
+                       UiModelType type = model->types[col];\r
+                       switch (type) {\r
+                               case UI_STRING_FREE:\r
+                               case UI_STRING: {\r
+                                       TextBlock cell = TextBlock();\r
+                                       cell.Padding(cellpadding);\r
+                                       cell.VerticalAlignment(VerticalAlignment::Stretch);\r
+                                       char *val = (char*)getvalue(elm, model_col);\r
+                                       textblock_set_str(cell, val);\r
+                                       cellBorder.Child(cell);\r
+                                       if (type == UI_STRING_FREE && val) {\r
+                                               free(val);\r
+                                       }\r
+\r
+                                       break;\r
+                               }\r
+                               case UI_INTEGER: {\r
+                                       TextBlock cell = TextBlock();\r
+                                       cell.Padding(cellpadding);\r
+                                       cell.VerticalAlignment(VerticalAlignment::Stretch);\r
+                                       int *value = (int*)getvalue(elm, model_col);\r
+                                       if (value) {\r
+                                               textblock_set_int(cell, *value);\r
+                                       }\r
+                                       cellBorder.Child(cell);\r
+                                       break;\r
+                               }\r
+                               case UI_ICON: {\r
+                                       UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col);\r
+                                       if (iconConstr) {\r
+                                               IconElement icon = iconConstr->getIcon();\r
+                                               cellBorder.Child(icon);\r
+                                       }\r
+                                       break;\r
+                               }\r
+                               case UI_ICON_TEXT_FREE:\r
+                               case UI_ICON_TEXT: {\r
+                                       StackPanel cellPanel = StackPanel();\r
+                                       cellPanel.Spacing(2);\r
+                                       cellPanel.Padding(cellpadding);\r
+                                       cellPanel.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+                                       cellPanel.Orientation(Orientation::Horizontal);\r
+                                       UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++);\r
+                                       char* str = (char*)getvalue(elm, model_col);\r
+                                       if (iconConstr) {\r
+                                               IconElement icon = iconConstr->getIcon();\r
+                                               cellPanel.Children().Append(icon);\r
+                                       }\r
+                                       TextBlock cell = TextBlock();\r
+                                       textblock_set_str(cell, str);\r
+                                       cellPanel.Children().Append(cell);\r
+                                       cellBorder.Child(cellPanel);\r
+                                       if (type == UI_ICON_TEXT_FREE && str) {\r
+                                               free(str);\r
+                                       }\r
+                                       break;\r
+                               }\r
+                       }\r
+\r
+                       // event handler\r
+                       cellBorder.PointerPressed(\r
+                               winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                                       [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                               winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers();\r
+                                               bool update_selection = true;\r
+\r
+                                               if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) {\r
+                                                       // add/remove current row\r
+                                                       if (!is_row_selected(row)) {\r
+                                                               row_background(row, selectedBrush, selectedBorderBrush);\r
+                                                               selection.push_back(row);\r
+                                                       }\r
+                                                       else {\r
+                                                               row_background(row, highlightBrush, highlightBrush);\r
+                                                               remove_from_selection(row);\r
+                                                       }\r
+                                               }\r
+                                               else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) {\r
+                                                       // no modifier or shift is pressed but there is no selection\r
+                                                       if (selection.size() > 0) {\r
+                                                               change_rows_bg(selection, defaultBrush, defaultBrush);\r
+                                                       }\r
+                                                       \r
+                                                       row_background(row, selectedBrush, selectedBorderBrush);\r
+                                                       selection = { row };\r
+                                                       if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) {\r
+                                                               SYSTEMTIME st;\r
+                                                               GetSystemTime(&st);\r
+                                                               \r
+                                                               ULONG64 now = getsystime();\r
+                                                               ULONG64 tdiff = now - lastPointerPress;\r
+                                                               if (tdiff < ui_double_click_time && onactivate != nullptr) {\r
+                                                                       // two pointer presse events in short time and we have an onactivate handler\r
+                                                                       update_selection = false; // we don't want an additional selection event\r
+                                                                       lastPointerPress = 0; // reset double-click\r
+\r
+                                                                       int selectedrow = row - 1; // subtract header row\r
+\r
+                                                                       UiListSelection selection;\r
+                                                                       selection.count = 1;\r
+                                                                       selection.rows = &selectedrow;\r
+\r
+                                                                       UiEvent evt;\r
+                                                                       evt.obj = obj;\r
+                                                                       evt.window = obj->window;\r
+                                                                       evt.document = obj->ctx->document;\r
+                                                                       evt.eventdata = &selection;\r
+                                                                       evt.intval = selectedrow;\r
+                                                                       onactivate(&evt, onactivatedata);\r
+                                                               }\r
+                                                               else {\r
+                                                                       lastPointerPress = now;\r
+                                                               }\r
+                                                       }\r
+                                               }\r
+                                               else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) {\r
+                                                       // select everything between the first selection and the current row\r
+                                                       std::sort(selection.begin(), selection.end());\r
+                                                       int first = selection.front();\r
+                                                       int last = row;\r
+                                                       if (first > row) {\r
+                                                               last = first;\r
+                                                               first = row;\r
+                                                       }\r
+\r
+                                                       // clear previous selection\r
+                                                       change_rows_bg(selection, defaultBrush, defaultBrush);\r
+\r
+                                                       // create new selection\r
+                                                       std::vector<int> newselection;\r
+                                                       for (int s = first; s <= last; s++) {\r
+                                                               newselection.push_back(s);\r
+                                                       }\r
+                                                       selection = newselection;\r
+                                                       change_rows_bg(selection, selectedBrush, selectedBorderBrush);\r
+                                               }\r
+\r
+                                               if (update_selection) {\r
+                                                       call_handler(onselection, onselectiondata);\r
+                                               }\r
+                                       })\r
+                       );\r
+                       cellBorder.PointerReleased(\r
+                               winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                                       [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+\r
+                                       })\r
+                       );\r
+                       cellBorder.PointerEntered(\r
+                               winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                                       [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                               if (!is_row_selected(row)) {\r
+                                                       row_background(row, highlightBrush, highlightBrush);\r
+                                               }\r
+                                       })\r
+                       );\r
+                       cellBorder.PointerExited(\r
+                               winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+                                       [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                                               if (!is_row_selected(row)) {\r
+                                                       row_background(row, defaultBrush, defaultBrush);\r
+                                               }\r
+                                       })\r
+                       );\r
+\r
+                       grid.SetColumn(cellBorder, col);\r
+                       grid.SetRow(cellBorder, row);\r
+                       grid.Children().Append(cellBorder);\r
+               }\r
+\r
+               row++;\r
+               elm = list->next(list);\r
+       }\r
+}\r
+\r
+void UiTable::clear() {\r
+       for (int i = grid.Children().Size()-1; i >= 0; i--) {\r
+               FrameworkElement elm = grid.Children().GetAt(i).as<FrameworkElement>();\r
+               int child_row = grid.GetRow(elm);\r
+               if (child_row > 0) {\r
+                       grid.Children().RemoveAt(i);\r
+               }\r
+       }\r
+\r
+       // TODO: should we clean row definitions?\r
+}\r
+\r
+void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {\r
+       Thickness b1 = { 1, 1, 0, 1 }; // first col\r
+       Thickness b2 = { 0, 1, 0, 1 }; // middle\r
+       Thickness b3 = { 0, 1, 1, 1 }; // last col\r
+       \r
+       for (auto child : grid.Children()) {\r
+               FrameworkElement elm = child.as<FrameworkElement>();\r
+               int child_row = grid.GetRow(elm);\r
+               if (child_row == row) {\r
+                       Border b = elm.as<Border>();\r
+                       b.Background(brush);\r
+                       b.BorderBrush(borderBrush);\r
+               }\r
+       }\r
+}\r
+\r
+void UiTable::change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {\r
+       std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); });\r
+}\r
+\r
+bool UiTable::is_row_selected(int row) {\r
+       return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false;\r
+}\r
+\r
+void UiTable::remove_from_selection(int row) {\r
+       selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end());\r
+       selection.shrink_to_fit();\r
+}\r
+\r
+UiListSelection UiTable::uiselection() {\r
+       std::sort(selection.begin(), selection.end());\r
+\r
+       UiListSelection selobj;\r
+       selobj.count = selection.size();\r
+       selobj.rows = nullptr;\r
+       if (selobj.count > 0) {\r
+               selobj.rows = (int*)calloc(selobj.count, sizeof(int));\r
+               memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int));\r
+               for (int i = 0; i < selobj.count; i++) {\r
+                       selobj.rows[i]--;\r
+               }\r
+       }\r
+       return selobj;\r
+}\r
+\r
+void UiTable::call_handler(ui_callback cb, void* cbdata) {\r
+       if (!cb) {\r
+               return;\r
+       }\r
+\r
+       UiListSelection selobj = uiselection();\r
+\r
+       UiEvent evt;\r
+       evt.obj = obj;\r
+       evt.window = obj->window;\r
+       evt.document = obj->ctx->document;\r
+       evt.eventdata = &selobj;\r
+       evt.intval = 0;\r
+       cb(&evt, cbdata);\r
+\r
+       if (selobj.rows) {\r
+               free(selobj.rows);\r
+       }\r
+}\r
diff --git a/ui/winui/table.h b/ui/winui/table.h
new file mode 100644 (file)
index 0000000..51fa97d
--- /dev/null
@@ -0,0 +1,98 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/tree.h"\r
+#include "toolkit.h"\r
+#include "dnd.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+\r
+typedef struct UiTableColumn {\r
+       winrt::Microsoft::UI::Xaml::Controls::Border header;\r
+\r
+} UiTableColumn;\r
+\r
+typedef struct UiTable {\r
+       winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollw;\r
+       winrt::Microsoft::UI::Xaml::Controls::Grid grid;\r
+       winrt::Microsoft::UI::Xaml::Media::SolidColorBrush defaultBrush;\r
+       winrt::Microsoft::UI::Xaml::Media::SolidColorBrush highlightBrush;\r
+       winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBrush;\r
+       winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBorderBrush;\r
+\r
+       winrt::Microsoft::UI::Xaml::Controls::Border resizedCol{ nullptr };\r
+       bool resize = false;\r
+\r
+       UiObject* obj;\r
+       ui_callback onactivate;\r
+       void* onactivatedata;\r
+       ui_callback onselection;\r
+       void* onselectiondata;\r
+       ui_callback ondragstart;\r
+       void* ondragstartdata;\r
+       ui_callback ondragcomplete;\r
+       void* ondragcompletedata;\r
+       ui_callback ondrop;\r
+       void* ondropdata;\r
+       UiModel* model = nullptr;\r
+       std::vector<UiTableColumn> header;\r
+       ui_getvaluefunc getvalue = nullptr;\r
+       int maxrows = 0;\r
+       int lastSelection = 0;\r
+       ULONG64 lastPointerPress = 0;\r
+       std::vector<int> selection;\r
+\r
+       UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid);\r
+\r
+       ~UiTable();\r
+       \r
+       void add_header(UiModel* model);\r
+\r
+       void update(UiList* list, int i);\r
+\r
+       void clear();\r
+\r
+       void row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);\r
+\r
+       void change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);\r
+\r
+       bool is_row_selected(int row);\r
+\r
+       void remove_from_selection(int row);\r
+\r
+       UiListSelection uiselection();\r
+\r
+       void call_handler(ui_callback cb, void *cbdata);\r
+} UiTable;\r
+\r
+extern "C" void ui_table_update(UiList * list, int i);\r
+\r
+extern "C" UiListSelection ui_table_selection(UiList * list);\r
diff --git a/ui/winui/text.cpp b/ui/winui/text.cpp
new file mode 100644 (file)
index 0000000..ce983ff
--- /dev/null
@@ -0,0 +1,609 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "text.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include <cx/string.h>\r
+#include <cx/allocator.h>\r
+\r
+#include "util.h"\r
+#include "container.h"\r
+\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Windows::UI::Xaml::Input;\r
+\r
+\r
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textarea and toolkit wrapper\r
+    TextBox textarea = TextBox();\r
+    textarea.AcceptsReturn(true);\r
+    ScrollViewer::SetVerticalScrollBarVisibility(textarea, ScrollBarVisibility::Auto);\r
+    UIElement elm = textarea;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);\r
+    if (var) {\r
+        UiText* value = (UiText*)var->value;\r
+        value->obj = widget;\r
+        value->undomgr = NULL;\r
+        value->set = ui_textarea_set;\r
+        value->get = ui_textarea_get;\r
+        value->getsubstr = ui_textarea_getsubstr;\r
+        value->insert = ui_textarea_insert;\r
+        value->setposition = ui_textarea_setposition;\r
+        value->position = ui_textarea_position;\r
+        value->selection = ui_textarea_selection;\r
+        value->length = ui_textarea_length;\r
+        value->remove = ui_textarea_remove;\r
+    }\r
+\r
+    // add textarea to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(textarea, true);\r
+\r
+    return widget;\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {\r
+    return textarea;\r
+}\r
+\r
+UIEXPORT void ui_text_undo(UiText *value) {\r
+\r
+}\r
+\r
+UIEXPORT void ui_text_redo(UiText *value) {\r
+\r
+}\r
+\r
+// -------------------------- getter/setter for textarea UiText --------------------------\r
+\r
+char* ui_wtext_get(UiText *text, std::wstring &value) {\r
+    if (text->value.ptr) {\r
+        text->value.free(text->value.ptr);\r
+    }\r
+\r
+    text->value.ptr = wchar2utf8(value.c_str(), value.length());\r
+    text->value.free = free;\r
+\r
+    return text->value.ptr;\r
+}\r
+\r
+std::wstring ui_wtext_set(UiText *text, const char* value) {\r
+    if (text->value.ptr) {\r
+        text->value.free(text->value.ptr);\r
+    }\r
+\r
+    text->value.ptr = _strdup(value);\r
+    text->value.free = free;\r
+\r
+    int len;\r
+    wchar_t* wstr = str2wstr(value, &len);\r
+    std::wstring s(wstr);\r
+    free(wstr);\r
+\r
+    return s;\r
+}\r
+\r
+extern "C" char* ui_textarea_get(UiText *text) {\r
+    UiWidget* widget = (UiWidget*)text->obj;\r
+    TextBox box = widget->uielement.as<TextBox>();\r
+    std::wstring wstr(box.Text());\r
+    return ui_wtext_get(text, wstr);\r
+}\r
+\r
+extern "C" void  ui_textarea_set(UiText *text, const char *newvalue) {\r
+    UiWidget* widget = (UiWidget*)text->obj;\r
+    TextBox box = widget->uielement.as<TextBox>();\r
+    box.Text(ui_wtext_set(text, newvalue));\r
+}\r
+\r
+extern "C" char* ui_textarea_getsubstr(UiText *text, int begin, int end) {\r
+    return NULL;\r
+}\r
+\r
+extern "C" void  ui_textarea_insert(UiText *text, int pos, char *str) {\r
+\r
+}\r
+\r
+extern "C" void  ui_textarea_setposition(UiText *text, int pos) {\r
+\r
+}\r
+\r
+extern "C" int   ui_textarea_position(UiText *text) {\r
+    return 0;\r
+}\r
+\r
+extern "C" void  ui_textarea_selection(UiText *text, int *begin, int *end) {\r
+\r
+}\r
+\r
+extern "C" int   ui_textarea_length(UiText *text) {\r
+    return 0;\r
+}\r
+\r
+extern "C" void  ui_textarea_remove(UiText *text, int begin, int end) {\r
+\r
+}\r
+\r
+\r
+\r
+\r
+UIWIDGET ui_textfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textbox and toolkit wrapper\r
+    TextBox textfield = TextBox();\r
+    UIElement elm = textfield;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+    if (var) {\r
+        UiString* value = (UiString*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_textfield_get;\r
+        value->set = ui_textfield_set;\r
+\r
+        // listener for notifying observers\r
+        // TODO:\r
+    }\r
+    \r
+    // add textfield to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(textfield, false);\r
+\r
+    return widget;\r
+}\r
+\r
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+    return ui_textfield_create(obj, args);\r
+}\r
+\r
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create textbox and toolkit wrapper\r
+    PasswordBox textfield = PasswordBox();\r
+    UIElement elm = textfield;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+    ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+    if (var) {\r
+        UiString* value = (UiString*)var->value;\r
+        value->obj = widget;\r
+        value->get = ui_passwordfield_get;\r
+        value->set = ui_passwordfield_set;\r
+\r
+        // listener for notifying observers\r
+        // TODO:\r
+    }\r
+\r
+    // add textfield to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(textfield, false);\r
+\r
+    return widget;\r
+}\r
+\r
+\r
+// -------------------------- getter/setter for textfield UiString --------------------------\r
+\r
+char* ui_wstring_get(UiString* str, std::wstring &value) {\r
+    if (str->value.ptr) {\r
+        str->value.free(str->value.ptr);\r
+    }\r
+\r
+    str->value.ptr = wchar2utf8(value.c_str(), value.length());\r
+    str->value.free = free;\r
+\r
+    return str->value.ptr;\r
+}\r
+\r
+std::wstring ui_wstring_set(UiString* str, const char* value) {\r
+    if (str->value.ptr) {\r
+        str->value.free(str->value.ptr);\r
+    }\r
+\r
+    str->value.ptr = _strdup(value);\r
+    str->value.free = free;\r
+\r
+    int len;\r
+    wchar_t* wstr = str2wstr(value, &len);\r
+    std::wstring s(wstr);\r
+    free(wstr);\r
+\r
+    return s;\r
+}\r
+\r
+char* ui_textfield_get(UiString * str) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    TextBox box = widget->uielement.as<TextBox>();\r
+    std::wstring wstr(box.Text());\r
+    return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void  ui_textfield_set(UiString * str, const char* newvalue) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    TextBox box = widget->uielement.as<TextBox>();\r
+    box.Text(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+char* ui_passwordfield_get(UiString * str) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    PasswordBox box = widget->uielement.as<PasswordBox>();\r
+    std::wstring wstr(box.Password());\r
+    return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void  ui_passwordfield_set(UiString * str, const char* newvalue) {\r
+    UiWidget* widget = (UiWidget*)str->obj;\r
+    PasswordBox box = widget->uielement.as<PasswordBox>();\r
+    box.Password(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+// ------------------------ path textfield --------------------------------------\r
+\r
+extern "C" static void destroy_ui_pathtextfield(void* ptr) {\r
+    UiPathTextField* pb = (UiPathTextField*)ptr;\r
+    delete pb;\r
+}\r
+\r
+static void ui_context_add_pathtextfield_destructor(UiContext* ctx, UiPathTextField* pb) {\r
+    cxMempoolRegister(ctx->mp, pb, destroy_ui_pathtextfield);\r
+}\r
+\r
+static void ui_pathtextfield_clear(StackPanel& buttons) {\r
+    for (int i = buttons.Children().Size() - 1; i >= 0; i--) {\r
+        buttons.Children().RemoveAt(i);\r
+    }\r
+}\r
+\r
+static void ui_pathfield_free_pathelms(UiPathElm* elms, size_t nelm) {\r
+    if (!elms) {\r
+        return;\r
+    }\r
+    for (int i = 0; i < nelm; i++) {\r
+        UiPathElm e = elms[i];\r
+        free(e.name);\r
+        free(e.path);\r
+    }\r
+    free(elms);\r
+}\r
+\r
+UiPathTextField::~UiPathTextField() {\r
+    ui_pathfield_free_pathelms(this->current_path, this->current_path_nelms);\r
+}\r
+\r
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {\r
+    cxstring *pathelms;\r
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);\r
+\r
+    if (nelm == 0) {\r
+        *ret_nelm = 0;\r
+        return nullptr;\r
+    }\r
+\r
+    UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));\r
+    size_t n = nelm;\r
+    int j = 0;\r
+    for (int i = 0; i < nelm; i++) {\r
+        cxstring c = pathelms[i];\r
+        if (c.length == 0) {\r
+            if (i == 0) {\r
+                c.length = 1;\r
+            }\r
+            else {\r
+                n--;\r
+                continue;\r
+            }\r
+        }\r
+\r
+        cxmutstr m = cx_strdup(c);\r
+        elms[j].name = m.ptr;\r
+        elms[j].name_len = m.length;\r
+        \r
+        size_t elm_path_len = c.ptr + c.length - full_path;\r
+        cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));\r
+        elms[j].path = elm_path.ptr;\r
+        elms[j].path_len = elm_path.length;\r
+\r
+        j++;\r
+    }\r
+    *ret_nelm = n;\r
+\r
+    return elms;\r
+}\r
+\r
+int ui_pathtextfield_update(UiPathTextField* pb, const char *full_path) {\r
+    Grid grid = pb->grid;\r
+\r
+    ui_pathelm_func getpathelm = pb->getpathelm;\r
+    void* getpathelmdata = pb->getpathelmdata;\r
+\r
+    size_t full_path_len = full_path ? strlen(full_path) : 0;\r
+\r
+    size_t nelm = 0;\r
+    UiPathElm* path_elm = getpathelm(full_path, full_path_len, &nelm, getpathelmdata);\r
+    if (!path_elm) {\r
+        return 1;\r
+    }\r
+\r
+    // hide textbox, show button panel\r
+    pb->textbox.Visibility(Visibility::Collapsed);\r
+    pb->buttons.Visibility(Visibility::Visible);\r
+\r
+    // clear old buttons\r
+    ui_pathtextfield_clear(pb->buttons); \r
+\r
+    ui_pathfield_free_pathelms(pb->current_path, pb->current_path_nelms);\r
+    pb->current_path = path_elm;\r
+    pb->current_path_nelms = nelm;\r
+\r
+    // add new buttons\r
+    int j = 0;\r
+    for (int i = 0; i < nelm;i++) {\r
+        UiPathElm elm = path_elm[i];\r
+        wchar_t* wstr = str2wstr_len(elm.name, elm.name_len, nullptr);\r
+        Button button = Button();\r
+        button.Content(box_value(wstr));\r
+        free(wstr);\r
+\r
+        if (pb->onactivate) {\r
+            button.Click([pb, j, elm](IInspectable const& sender, RoutedEventArgs) {\r
+                // copy elm.path because it could be a non-terminated string\r
+                cxmutstr elmpath = cx_strdup(cx_strn(elm.path, elm.path_len));\r
+\r
+                UiEvent evt;\r
+                evt.obj = pb->obj;\r
+                evt.window = evt.obj->window;\r
+                evt.document = evt.obj->ctx->document;\r
+                evt.eventdata = elmpath.ptr;\r
+                evt.intval = j;\r
+                pb->onactivate(&evt, pb->onactivatedata);\r
+\r
+                free(elmpath.ptr);\r
+                });\r
+        }\r
+\r
+        Thickness t = { 0, 0, 1, 0 };\r
+        CornerRadius c = { 0 ,0, 0, 0 };\r
+        button.BorderThickness(t);\r
+        button.CornerRadius(c);\r
+\r
+        pb->buttons.Children().Append(button);\r
+\r
+        j++;\r
+    }\r
+\r
+    return 0;\r
+}\r
+\r
+char* ui_path_textfield_get(UiString * str) {\r
+    UiPathTextField* widget = (UiPathTextField*)str->obj;\r
+    TextBox box = widget->textbox;\r
+    std::wstring wstr(box.Text());\r
+    return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void  ui_path_textfield_set(UiString* str, const char* newvalue) {\r
+    UiPathTextField* widget = (UiPathTextField*)str->obj;\r
+    TextBox box = widget->textbox;\r
+    box.Text(ui_wstring_set(str, newvalue));\r
+    ui_pathtextfield_update(widget, newvalue);\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {\r
+    UiObject* current = uic_current_obj(obj);\r
+\r
+    // create view and toolkit wrapper\r
+    Border pathbar = Border();\r
+\r
+    IInspectable bgRes = Application::Current().Resources().Lookup(box_value(L"TextControlBackground"));\r
+    IInspectable borderThicknessRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderThemeThickness"));\r
+    IInspectable borderBrushRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderBrush"));\r
+    // IInspectable cornerRes = Application::Current().Resources().Lookup(box_value(L"TextControlCornerRadius"));\r
+\r
+    Brush bgBrush = unbox_value<Brush>(bgRes);\r
+    Thickness border = unbox_value<Thickness>(borderThicknessRes);\r
+    Brush borderBrush = unbox_value<Brush>(borderBrushRes);\r
+    CornerRadius cornerRadius = { 4, 4, 4, 4 }; //unbox_value<CornerRadius>(cornerRes);\r
+\r
+    pathbar.Background(bgBrush);\r
+    pathbar.BorderBrush(borderBrush);\r
+    pathbar.BorderThickness(border);\r
+    pathbar.CornerRadius(cornerRadius);\r
+\r
+    Grid content = Grid();\r
+    pathbar.Child(content);\r
+\r
+    GridLength gl;\r
+    gl.Value = 0;\r
+    gl.GridUnitType = GridUnitType::Auto;\r
+\r
+    ColumnDefinition coldef = ColumnDefinition();\r
+    coldef.Width(gl);\r
+    content.ColumnDefinitions().Append(coldef);\r
+\r
+    gl.Value = 1;\r
+    gl.GridUnitType = GridUnitType::Star;\r
+\r
+    ColumnDefinition coldef2 = ColumnDefinition();\r
+    coldef2.Width(gl);\r
+    content.ColumnDefinitions().Append(coldef2);\r
+\r
+    TextBox pathTextBox = TextBox();\r
+    Thickness t = { 0, 0, 0, 0 };\r
+    CornerRadius c = { 0 ,0, 0, 0 };\r
+    pathTextBox.BorderThickness(t);\r
+    //pathTextBox.CornerRadius(c);\r
+\r
+\r
+    pathTextBox.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+    content.SetColumn(pathTextBox, 0);\r
+    content.SetColumnSpan(pathTextBox, 2);\r
+\r
+    content.Children().Append(pathTextBox);\r
+\r
+    // stackpanel for buttons\r
+    StackPanel buttons = StackPanel();\r
+    buttons.Orientation(Orientation::Horizontal);\r
+    buttons.Visibility(Visibility::Collapsed);\r
+    content.SetColumn(buttons, 0);\r
+    content.Children().Append(buttons);\r
+\r
+    TextBlock filler = TextBlock();\r
+    filler.VerticalAlignment(VerticalAlignment::Stretch);\r
+    //filler.Text(winrt::hstring(L"hello filler"));\r
+\r
+    filler.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+    filler.VerticalAlignment(VerticalAlignment::Stretch);\r
+    content.SetColumn(filler, 1);\r
+    content.Children().Append(filler);\r
+\r
+    filler.PointerPressed(\r
+        winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+            [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+                pathTextBox.Visibility(Visibility::Visible);\r
+                buttons.Visibility(Visibility::Collapsed);\r
+                filler.Visibility(Visibility::Collapsed);\r
+                pathTextBox.SelectionStart(pathTextBox.Text().size());\r
+                pathTextBox.SelectionLength(0);\r
+                pathTextBox.Focus(FocusState::Keyboard);\r
+            })\r
+    );\r
+\r
+    //pathTextBox.Visibility(Visibility::Collapsed);\r
+\r
+    UiPathTextField* uipathbar = new UiPathTextField;\r
+    ui_context_add_pathtextfield_destructor(current->ctx, uipathbar);\r
+    uipathbar->grid = content;\r
+    uipathbar->buttons = buttons;\r
+    uipathbar->textbox = pathTextBox;\r
+    uipathbar->filler = filler;\r
+    uipathbar->obj = obj;\r
+    uipathbar->getpathelm = args.getpathelm ? args.getpathelm : default_pathelm_func;\r
+    uipathbar->getpathelmdata = args.getpathelmdata;\r
+    uipathbar->onactivate = args.onactivate;\r
+    uipathbar->onactivatedata = args.onactivatedata;\r
+    uipathbar->ondragstart = args.ondragstart;\r
+    uipathbar->ondragstartdata = args.ondragstartdata;\r
+    uipathbar->ondragcomplete = args.ondragcomplete;\r
+    uipathbar->ondragcompletedata = args.ondragcompletedata;\r
+    uipathbar->ondrop = args.ondrop;\r
+    uipathbar->ondropdata = args.ondropsdata;\r
+\r
+\r
+    pathTextBox.KeyDown(\r
+        winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(\r
+            [=](winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) {\r
+                auto key = e.Key();\r
+                bool showButtons = false;\r
+                bool update = false;\r
+                if (key == Windows::System::VirtualKey::Escape) {\r
+                    showButtons = true;\r
+                }\r
+                else if (key == Windows::System::VirtualKey::Enter) {\r
+                    showButtons = true;\r
+                    update = true;\r
+                }\r
+\r
+                if (showButtons) {\r
+                    pathTextBox.Visibility(Visibility::Collapsed);\r
+                    buttons.Visibility(Visibility::Visible);\r
+                    filler.Visibility(Visibility::Visible);\r
+                    if (update) {\r
+                        std::wstring value(pathTextBox.Text());\r
+                        char* full_path = wchar2utf8(value.c_str(), value.length());\r
+\r
+                        if (!ui_pathtextfield_update(uipathbar, full_path)) {\r
+                            UiEvent evt;\r
+                            evt.obj = obj;\r
+                            evt.window = obj->window;\r
+                            evt.document = obj->ctx->document;\r
+                            evt.eventdata = full_path;\r
+                            evt.intval = -1;\r
+                            args.onactivate(&evt, args.onactivatedata);\r
+                        } \r
+\r
+                        free(full_path);\r
+                    }\r
+\r
+                    //buttons.Focus(FocusState::Keyboard);\r
+                }\r
+            })\r
+    );\r
+\r
+\r
+    UIElement elm = pathbar;\r
+    UiWidget* widget = new UiWidget(elm);\r
+    widget->data1 = uipathbar;\r
+    ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+    // bind var\r
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+    if (var) {\r
+        UiString* value = (UiString*)var->value;\r
+        value->obj = uipathbar;\r
+        value->get = ui_path_textfield_get;\r
+        value->set = ui_path_textfield_set;\r
+    }\r
+\r
+    // add listview to current container\r
+    UI_APPLY_LAYOUT1(current, args);\r
+\r
+    current->container->Add(pathbar, false);\r
+\r
+    return widget;\r
+}\r
diff --git a/ui/winui/text.h b/ui/winui/text.h
new file mode 100644 (file)
index 0000000..66ec6e1
--- /dev/null
@@ -0,0 +1,86 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/text.h"\r
+#include "toolkit.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+struct UiPathTextField {\r
+    winrt::Microsoft::UI::Xaml::Controls::Grid grid = { nullptr };\r
+    winrt::Microsoft::UI::Xaml::Controls::StackPanel buttons = { nullptr };\r
+    winrt::Microsoft::UI::Xaml::Controls::TextBox textbox = { nullptr };\r
+    winrt::Microsoft::UI::Xaml::Controls::TextBlock filler = { nullptr };\r
+\r
+    ~UiPathTextField();\r
+\r
+    UiPathElm* current_path = nullptr;\r
+    size_t current_path_nelms = 0;\r
+\r
+    UiObject* obj;\r
+\r
+    ui_pathelm_func getpathelm;\r
+    void* getpathelmdata;\r
+    \r
+    ui_callback onactivate;\r
+    void* onactivatedata;\r
+\r
+    ui_callback ondragstart;\r
+    void* ondragstartdata;\r
+    ui_callback ondragcomplete;\r
+    void* ondragcompletedata;\r
+    ui_callback ondrop;\r
+    void* ondropdata;\r
+};\r
+\r
+char* ui_wtext_get(UiText *text, std::wstring &value);\r
+std::wstring ui_wtext_set(UiText *text, const char* value);\r
+\r
+char* ui_wstring_get(UiString* str, std::wstring& value);\r
+std::wstring ui_wstring_set(UiString* str, const char* value);\r
+\r
+extern "C" char* ui_textarea_get(UiText *text);\r
+extern "C" void  ui_textarea_set(UiText *text, const char *newvalue);\r
+extern "C" char* ui_textarea_getsubstr(UiText*, int, int);\r
+extern "C" void  ui_textarea_insert(UiText*, int, char*);\r
+extern "C" void  ui_textarea_setposition(UiText*,int);\r
+extern "C" int   ui_textarea_position(UiText*);\r
+extern "C" void  ui_textarea_selection(UiText*, int*, int*);\r
+extern "C" int   ui_textarea_length(UiText*);\r
+extern "C" void  ui_textarea_remove(UiText*, int, int);\r
+\r
+extern "C" char* ui_textfield_get(UiString *str);\r
+extern "C" void  ui_textfield_set(UiString *str, const char *newvalue);\r
+\r
+extern "C" char* ui_passwordfield_get(UiString * str);\r
+extern "C" void  ui_passwordfield_set(UiString * str, const char* newvalue);\r
+\r
+extern "C" char* ui_path_textfield_get(UiString * str);\r
+extern "C" void  ui_path_textfield_set(UiString * str, const char* newvalue);\r
diff --git a/ui/winui/toolkit.cpp b/ui/winui/toolkit.cpp
new file mode 100644 (file)
index 0000000..3b684dd
--- /dev/null
@@ -0,0 +1,382 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "toolkit.h"\r
+\r
+#include <cx/allocator.h>\r
+#include <cx/mempool.h>\r
+\r
+#include "../common/context.h"\r
+#include "../common/document.h"\r
+#include "../common/toolbar.h"\r
+#include "../common/properties.h"\r
+\r
+#include "icons.h"\r
+\r
+#include "MainWindow.xaml.h"\r
+\r
+#include "App.xaml.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Windows::UI::Core;\r
+\r
+static const char* application_name;\r
+\r
+static ui_callback   startup_func;\r
+static void* startup_data;\r
+\r
+static ui_callback   open_func;\r
+void* open_data;\r
+\r
+static ui_callback   exit_func;\r
+void* exit_data;\r
+\r
+static ui_callback   appclose_fnc;\r
+\r
+static void* appclose_udata;\r
+\r
+\r
+static UiObject* active_window;\r
+\r
+static winrt::Microsoft::UI::Dispatching::DispatcherQueue uiDispatcherQueue = { nullptr };\r
+\r
+void ui_app_run_startup() {\r
+       uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();\r
+       \r
+       if (startup_func) {\r
+               startup_func(NULL, startup_data);\r
+       }\r
+}\r
+\r
+class App : public ApplicationT<App, IXamlMetadataProvider> {\r
+public:\r
+       void OnLaunched(LaunchActivatedEventArgs const&) {\r
+               Resources().MergedDictionaries().Append(XamlControlsResources());\r
+               if (startup_func) {\r
+                       startup_func(NULL, startup_data);\r
+               }\r
+\r
+               //auto window = make<winui::implementation::MainWindow>();\r
+               //window.Activate();\r
+       }\r
+       IXamlType GetXamlType(TypeName const& type) {\r
+               return provider.GetXamlType(type);\r
+       }\r
+       IXamlType GetXamlType(hstring const& fullname) {\r
+               return provider.GetXamlType(fullname);\r
+       }\r
+       com_array<XmlnsDefinition> GetXmlnsDefinitions() {\r
+               return provider.GetXmlnsDefinitions();\r
+       }\r
+private:\r
+       XamlControlsXamlMetaDataProvider provider;\r
+};\r
+\r
+UiWidget::UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm) : uielement(elm) {}\r
+\r
+extern "C" void destroy_ui_window_wrapper(void* ptr) {\r
+       UiWindow* win = (UiWindow*)ptr;\r
+       delete win;\r
+}\r
+\r
+extern "C" void destroy_ui_widget_wrapper(void* ptr) {\r
+       UiWidget* widget = (UiWidget*)ptr;\r
+       delete widget;\r
+}\r
+\r
+extern "C" void destroy_ui_container_wrapper(void* ptr) {\r
+       UiContainer* ctn = (UiContainer*)ptr;\r
+       delete ctn;\r
+}\r
+\r
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win) {\r
+       cxMempoolRegister(ctx->mp, win, destroy_ui_window_wrapper);\r
+}\r
+\r
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget) {\r
+       cxMempoolRegister(ctx->mp, widget, destroy_ui_widget_wrapper);\r
+}\r
+\r
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container) {\r
+       cxMempoolRegister(ctx->mp, container, destroy_ui_container_wrapper);\r
+}\r
+\r
+\r
+UiEvent ui_create_int_event(UiObject* obj, int64_t i) {\r
+       UiEvent evt;\r
+       evt.obj = obj;\r
+       evt.window = obj->window;\r
+       evt.document = obj->ctx->document;\r
+       evt.eventdata = nullptr;\r
+       evt.intval = i;\r
+       return evt;\r
+}\r
+\r
+\r
+#include <MddBootstrap.h>\r
+\r
+void ui_appsdk_bootstrap(void) {\r
+       const UINT32 majorMinorVersion{ 0x00010002 };\r
+       PCWSTR versionTag{ L"" };\r
+       const PACKAGE_VERSION minVersion{};\r
+\r
+       const HRESULT hr = MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion);\r
+       if (FAILED(hr)) {\r
+               exit(102);\r
+       }\r
+}\r
+\r
+void ui_init(const char* appname, int argc, char** argv) {\r
+       application_name = appname;\r
+\r
+       //ui_appsdk_bootstrap();\r
+\r
+       uic_init_global_context();\r
+       uic_docmgr_init();\r
+        uic_menu_init();\r
+       uic_toolbar_init();\r
+       \r
+       uic_load_app_properties();\r
+}\r
+\r
+const char* ui_appname() {\r
+       return application_name;\r
+}\r
+\r
+void ui_onstartup(ui_callback f, void* userdata) {\r
+       startup_func = f;\r
+       startup_data = userdata;\r
+}\r
+\r
+void ui_onopen(ui_callback f, void* userdata) {\r
+       open_func = f;\r
+       open_data = userdata;\r
+}\r
+\r
+void ui_onexit(ui_callback f, void* userdata) {\r
+       exit_func = f;\r
+       exit_data = userdata;\r
+}\r
+\r
+void ui_main() {\r
+       /*\r
+       init_apartment();\r
+       //Application::Start([](auto&&) {make<App>(); });\r
+\r
+       ::winrt::Microsoft::UI::Xaml::Application::Start(\r
+               [](auto&&)\r
+               {\r
+                       ::winrt::make<::winrt::winui::implementation::App>();\r
+               });\r
+               */\r
+       {\r
+               void (WINAPI * pfnXamlCheckProcessRequirements)();\r
+               auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll");\r
+               if (module)\r
+               {\r
+                       pfnXamlCheckProcessRequirements = reinterpret_cast<decltype(pfnXamlCheckProcessRequirements)>(GetProcAddress(module, "XamlCheckProcessRequirements"));\r
+                       if (pfnXamlCheckProcessRequirements)\r
+                       {\r
+                               (*pfnXamlCheckProcessRequirements)();\r
+                       }\r
+\r
+                       ::FreeLibrary(module);\r
+               }\r
+       }\r
+\r
+       winrt::init_apartment(winrt::apartment_type::single_threaded);\r
+       ::winrt::Microsoft::UI::Xaml::Application::Start(\r
+               [](auto&&)\r
+               {\r
+                       ::winrt::make<::winrt::winui::implementation::App>();\r
+               });\r
+}\r
+\r
+class UiWin {\r
+public:\r
+       Window window;\r
+};\r
+\r
+void ui_show(UiObject* obj) {\r
+       if (obj->wobj) {\r
+               obj->wobj->window.Activate();\r
+       } else if(obj->widget && obj->widget->Show) {\r
+               obj->widget->Show();\r
+       }\r
+}\r
+\r
+void ui_close(UiObject* obj) {\r
+       if (obj->wobj) {\r
+               obj->wobj->window.Close();\r
+       }\r
+}\r
+\r
+static void ui_job_thread(UiJob* job) {\r
+       if (!job->job_func(job->job_data) && job->finish_callback) {\r
+               bool isQueued = uiDispatcherQueue.TryEnqueue([job]()\r
+               {\r
+                       UiEvent event;\r
+                       event.obj = job->obj;\r
+                       event.window = job->obj->window;\r
+                       event.document = job->obj->ctx->document;\r
+                       event.intval = 0;\r
+                       event.eventdata = NULL;\r
+                       job->finish_callback(&event, job->finish_data);\r
+                       delete job;\r
+               });\r
+               if (!isQueued) {\r
+                       // TODO: error or try again?\r
+                       exit(-1);\r
+               }\r
+       }\r
+       else {\r
+               delete job;\r
+       }\r
+}\r
+\r
+UIEXPORT void ui_job(UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {\r
+       UiJob* job = new UiJob;\r
+       job->obj = obj;\r
+       job->job_func = tf;\r
+       job->job_data = td;\r
+       job->finish_callback = f;\r
+       job->finish_data = fd;\r
+\r
+       std::thread jobThread(ui_job_thread, job);\r
+       jobThread.detach();\r
+}\r
+\r
+UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td) {\r
+       bool isQueued = uiDispatcherQueue.TryEnqueue([tf, td]()\r
+       {\r
+               (void)tf(td);\r
+       });\r
+       if (!isQueued) {\r
+               // TODO: error or try again?\r
+               exit(-1);\r
+       }\r
+}\r
+\r
+static UiJob kill_job; // &kill_job indicates to stop the thread\r
+\r
+static void ui_threadpool_run(UiThreadpool* pool) {\r
+       for (;;) {\r
+               UiJob* job = pool->GetJob();\r
+               if (job == &kill_job) {\r
+                       return;\r
+               }\r
+               else if (job) {\r
+                       ui_job_thread(job);\r
+               }\r
+       }\r
+}\r
+\r
+UiThreadpool::UiThreadpool(int nthreads) {\r
+       for (int i = 0; i < nthreads; i++) {\r
+               std::thread thread(ui_threadpool_run, this);\r
+               thread.detach();\r
+       }\r
+}\r
+\r
+void UiThreadpool::EnqueueJob(UiJob* job)\r
+{\r
+       std::unique_lock<std::mutex> lock(mutex);\r
+       queue.push(job);\r
+       lock.unlock();\r
+       condition.notify_one();\r
+}\r
+\r
+UiJob* UiThreadpool::GetJob() {\r
+       std::unique_lock<std::mutex> lock(mutex);\r
+\r
+       UiJob* job = nullptr;\r
+       while (!job) {\r
+               if (queue.empty()) {\r
+                       condition.wait(lock);\r
+                       continue;\r
+               }\r
+               else\r
+               {\r
+                       job = queue.front();\r
+                       queue.pop();\r
+               }\r
+       }\r
+\r
+       return job;\r
+}\r
+\r
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads) {\r
+       return new UiThreadpool(nthreads);\r
+}\r
+\r
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool) {\r
+       // TODO\r
+}\r
+\r
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {\r
+       UiJob* job = new UiJob;\r
+       job->obj = obj;\r
+       job->job_func = tf;\r
+       job->job_data = td;\r
+       job->finish_callback = f;\r
+       job->finish_data = fd;\r
+       pool->EnqueueJob(job);\r
+}\r
+\r
+\r
+\r
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups) {\r
+       if(!groups) {\r
+               return;\r
+       }\r
+       size_t ngroups = uic_group_array_size(groups);\r
+       ui_set_widget_ngroups(ctx, widget, groups, ngroups);\r
+}\r
+\r
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups) {\r
+       if(ngroups > 0) {\r
+               uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);\r
+               ui_set_enabled(widget, FALSE);\r
+       }\r
+}\r
+\r
+\r
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled) {\r
+       Control ctrl = widget->uielement.as<Control>();\r
+       if (ctrl) {\r
+               ctrl.IsEnabled(enabled);\r
+       }\r
+}\r
diff --git a/ui/winui/toolkit.h b/ui/winui/toolkit.h
new file mode 100644 (file)
index 0000000..ab421ec
--- /dev/null
@@ -0,0 +1,72 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+\r
+#include <queue>\r
+#include <mutex>\r
+#include <condition_variable>\r
+\r
+typedef struct UiJob {\r
+    UiObject* obj;\r
+    ui_threadfunc job_func;\r
+    void* job_data;\r
+    ui_callback   finish_callback;\r
+    void* finish_data;\r
+} UiJob;\r
+\r
+struct UiThreadpool\r
+{\r
+       std::queue<UiJob*> queue;\r
+       std::mutex mutex;\r
+       std::condition_variable condition;\r
+\r
+       UiThreadpool(int nthreads);\r
+\r
+       void EnqueueJob(UiJob* job);\r
+\r
+       UiJob* GetJob();\r
+};\r
+\r
+typedef void(*ui_eventfunc)(void*, void*);\r
+\r
+void ui_app_run_startup();\r
+\r
+extern "C" void destroy_ui_window_wrapper(void* ptr);\r
+extern "C" void destroy_ui_widget_wrapper(void* ptr);\r
+\r
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win);\r
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget);\r
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container);\r
+\r
+UiEvent ui_create_int_event(UiObject* obj, int64_t i);\r
+\r
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups);\r
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups);\r
diff --git a/ui/winui/util.cpp b/ui/winui/util.cpp
new file mode 100644 (file)
index 0000000..f550244
--- /dev/null
@@ -0,0 +1,46 @@
+#include "pch.h"\r
+\r
+#include "util.h"\r
+\r
+#include <stdlib.h>\r
+\r
+\r
+wchar_t* str2wstr(const char* str, int* newlen) {\r
+    size_t len = strlen(str);\r
+\r
+    return str2wstr_len(str, len, newlen);\r
+}\r
+\r
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen) {\r
+    wchar_t* wstr = (wchar_t*)calloc(len + 1, sizeof(wchar_t));\r
+    int wlen = MultiByteToWideChar(\r
+        CP_UTF8,\r
+        0,\r
+        str,\r
+        len,\r
+        wstr,\r
+        len + 1\r
+    );\r
+    if (newlen) {\r
+        *newlen = wlen;\r
+    }\r
+    wstr[wlen] = 0;\r
+\r
+    return wstr;\r
+}\r
+\r
+char* wchar2utf8(const wchar_t* wstr, size_t wlen) {\r
+    size_t maxlen = wlen * 4;\r
+    char* ret = (char*)malloc(maxlen + 1);\r
+    int ret_len = WideCharToMultiByte(\r
+        CP_UTF8,\r
+        0,\r
+        wstr,\r
+        wlen,\r
+        ret,\r
+        maxlen,\r
+        NULL,\r
+        NULL);\r
+    ret[ret_len] = 0;\r
+    return ret;\r
+}\r
diff --git a/ui/winui/util.h b/ui/winui/util.h
new file mode 100644 (file)
index 0000000..592eeb4
--- /dev/null
@@ -0,0 +1,9 @@
+#pragma once\r
+\r
+#include <stdlib.h>\r
+\r
+wchar_t* str2wstr(const char* str, int* newlen);\r
+\r
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen);\r
+\r
+char* wchar2utf8(const wchar_t* wstr, size_t wlen);\r
diff --git a/ui/winui/window.cpp b/ui/winui/window.cpp
new file mode 100644 (file)
index 0000000..208c23c
--- /dev/null
@@ -0,0 +1,637 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+\r
+#include "window.h"\r
+\r
+#include "appmenu.h"\r
+#include "commandbar.h"\r
+#include "container.h"\r
+#include "util.h"\r
+#include "button.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include <stdlib.h>\r
+\r
+#include <cx/mempool.h>\r
+\r
+#include "MainWindow.xaml.h"\r
+\r
+\r
+#include <Windows.h>\r
+#include <shobjidl.h>\r
+#include <iostream>\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Windows::Storage::Pickers;\r
+\r
+UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}\r
+\r
+UiObject* ui_window(const char* title, void* window_data) {\r
+       UiObject* obj = ui_simple_window(title, window_data);\r
+\r
+       /*\r
+       if (uic_get_menu_list()) {\r
+               // create/add menubar\r
+               MenuBar mb = ui_create_menubar(obj);\r
+               mb.VerticalAlignment(VerticalAlignment::Top);\r
+               obj->container->Add(mb, false);\r
+       }\r
+       */\r
+\r
+       if (uic_toolbar_isenabled()) {\r
+               // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto"\r
+               Grid toolbar_grid = Grid();\r
+               GridLength gl;\r
+               gl.Value = 0;\r
+               gl.GridUnitType = GridUnitType::Auto;\r
+\r
+               ColumnDefinition coldef0 = ColumnDefinition();\r
+               coldef0.Width(gl);\r
+               toolbar_grid.ColumnDefinitions().Append(coldef0);\r
+\r
+               gl.Value = 1;\r
+               gl.GridUnitType = GridUnitType::Star;\r
+               ColumnDefinition coldef1 = ColumnDefinition();\r
+               coldef1.Width(gl);\r
+               toolbar_grid.ColumnDefinitions().Append(coldef1);\r
+\r
+               gl.Value = 0;\r
+               gl.GridUnitType = GridUnitType::Auto;\r
+               ColumnDefinition coldef2 = ColumnDefinition();\r
+               coldef2.Width(gl);\r
+               toolbar_grid.ColumnDefinitions().Append(coldef2);\r
+\r
+               // rowdef\r
+               gl.Value = 0;\r
+               gl.GridUnitType = GridUnitType::Auto;\r
+               RowDefinition rowdef = RowDefinition();\r
+               rowdef.Height(gl);\r
+               toolbar_grid.RowDefinitions().Append(rowdef);\r
+\r
+\r
+               // create commandbar\r
+               CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);\r
+               CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);\r
+               CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);\r
+\r
+               bool addappmenu = true;\r
+               if (cxListSize(def_r) > 0) {\r
+                       CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu);\r
+                       toolbar_grid.SetColumn(toolbar_r, 2);\r
+                       toolbar_grid.SetRow(toolbar_r, 0);\r
+                       toolbar_grid.Children().Append(toolbar_r);\r
+                       addappmenu = false;\r
+               }\r
+               if (cxListSize(def_c) > 0) {\r
+                       CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu);\r
+                       toolbar_c.HorizontalAlignment(HorizontalAlignment::Center);\r
+                       toolbar_grid.SetColumn(toolbar_c, 1);\r
+                       toolbar_grid.SetRow(toolbar_c, 0);\r
+                       toolbar_grid.Children().Append(toolbar_c);\r
+                       addappmenu = false;\r
+               }\r
+               if (cxListSize(def_l) > 0) {\r
+                       CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu);\r
+                       toolbar_grid.SetColumn(toolbar_l, 0);\r
+                       toolbar_grid.SetRow(toolbar_l, 0);\r
+                       toolbar_grid.Children().Append(toolbar_l);\r
+               }\r
+\r
+               toolbar_grid.VerticalAlignment(VerticalAlignment::Top);\r
+               obj->container->Add(toolbar_grid, false);\r
+       }\r
+\r
+       return obj;\r
+}\r
+\r
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) {\r
+       CxMempool* mp = cxBasicMempoolCreate(256);\r
+       UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));\r
+\r
+       obj->ctx = uic_context(obj, mp);\r
+       obj->window = window_data;\r
+\r
+       Window window = Window();\r
+       //Window window = make<winui::implementation::MainWindow>();\r
+\r
+       winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };\r
+       Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);\r
+\r
+       window.ExtendsContentIntoTitleBar(true);\r
+\r
+       Grid grid = Grid();\r
+       window.Content(grid);\r
+\r
+       StackPanel titleBar = StackPanel();\r
+       Thickness titleBarPadding = { 10, 5, 5, 10 };\r
+       titleBar.Padding(titleBarPadding);\r
+       titleBar.Orientation(Orientation::Horizontal);\r
+       TextBlock titleLabel = TextBlock();\r
+       titleBar.Children().Append(titleLabel);\r
+\r
+       if (title) {\r
+               wchar_t* wtitle = str2wstr(title, nullptr);\r
+               window.Title(wtitle);\r
+               titleLabel.Text(hstring(wtitle));\r
+               free(wtitle);\r
+       }\r
+\r
+       window.SetTitleBar(titleBar);\r
+\r
+       obj->wobj = new UiWindow(window);\r
+       ui_context_add_window_destructor(obj->ctx, obj->wobj);\r
+\r
+       window.Closed([obj](IInspectable const& sender, WindowEventArgs) {\r
+               if (obj->ctx->close_callback) {\r
+                       UiEvent evt;\r
+                       evt.obj = obj;\r
+                       evt.document = obj->ctx->document;\r
+                       evt.window = obj->window;\r
+                       evt.eventdata = NULL;\r
+                       evt.intval = 0;\r
+                       obj->ctx->close_callback(&evt, obj->ctx->close_data);\r
+               } else {\r
+                       ui_context_destroy(obj->ctx);\r
+               }\r
+               });\r
+\r
+       obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);\r
+\r
+       titleBar.VerticalAlignment(VerticalAlignment::Top);\r
+       obj->container->Add(titleBar, false);\r
+\r
+       obj->window = window_data;\r
+\r
+       return obj;\r
+}\r
+\r
+static void dialog_button_add_callback(ContentDialog dialog, Button button, int num, UiObject *obj, ui_callback onclick, void *onclickdata) {\r
+       button.Click([dialog, num, obj, onclick, onclickdata](IInspectable const& sender, RoutedEventArgs) {\r
+               if (onclick) {\r
+                       UiEvent evt;\r
+                       evt.obj = obj;\r
+                       evt.window = obj->window;\r
+                       evt.document = obj->ctx->document;\r
+                       evt.eventdata = nullptr;\r
+                       evt.intval = num;\r
+                       onclick(&evt, onclickdata);\r
+               }\r
+               dialog.Hide();\r
+       });\r
+}\r
+\r
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {\r
+       UiWindow *window = parent->wobj;\r
+       if (!window) {\r
+               return NULL;\r
+       }\r
+       \r
+       CxMempool* mp = cxBasicMempoolCreate(256);\r
+       UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));\r
+       \r
+       obj->ctx = uic_context(obj, mp);\r
+       \r
+       ContentDialog dialog = ContentDialog();\r
+       UIElement elm = dialog;\r
+       UiWidget* widget = new UiWidget(elm);\r
+       ui_context_add_widget_destructor(obj->ctx, widget);\r
+       obj->widget = widget;\r
+\r
+       if (args.title) {\r
+               wchar_t* wtitle = str2wstr(args.title, nullptr);\r
+               dialog.Title(box_value(wtitle));\r
+               free(wtitle);\r
+       }\r
+\r
+       \r
+       Grid dialogContent = Grid();\r
+       GridLength gl;\r
+\r
+       // content row\r
+       gl.Value = 1;\r
+       gl.GridUnitType = GridUnitType::Star;\r
+       RowDefinition rowdef0 = RowDefinition();\r
+       rowdef0.Height(gl);\r
+       dialogContent.RowDefinitions().Append(rowdef0);\r
+\r
+       // button row\r
+       gl.Value = 0;\r
+       gl.GridUnitType = GridUnitType::Auto;\r
+       RowDefinition rowdef1 = RowDefinition();\r
+       rowdef1.Height(gl);\r
+       dialogContent.RowDefinitions().Append(rowdef1);\r
+\r
+       // coldef\r
+       gl.Value = 1;\r
+       gl.GridUnitType = GridUnitType::Star;\r
+       ColumnDefinition coldef = ColumnDefinition();\r
+       coldef.Width(gl);\r
+       dialogContent.ColumnDefinitions().Append(coldef);\r
+\r
+       // content\r
+       Grid grid = Grid();\r
+       grid.SetRow(grid, 0);\r
+       grid.SetColumn(grid, 0);\r
+       dialogContent.Children().Append(grid);\r
+       obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);\r
+\r
+       // buttons\r
+       Grid buttons = Grid();\r
+       Thickness btnsMargin = { (double)0, (double)10, (double)0, (double)0 };\r
+       buttons.Margin(btnsMargin);\r
+\r
+       RowDefinition btnrowdef = RowDefinition();\r
+       gl.Value = 0;\r
+       gl.GridUnitType = GridUnitType::Auto;\r
+       btnrowdef.Height(gl);\r
+       buttons.RowDefinitions().Append(btnrowdef);\r
+\r
+       gl.Value = 1;\r
+       gl.GridUnitType = GridUnitType::Auto;\r
+       int c = 0;\r
+       if (args.lbutton1) {\r
+               ColumnDefinition bcoldef = ColumnDefinition();\r
+               bcoldef.Width(gl);\r
+               buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+               Button btn = Button();\r
+               ui_set_button_label(btn, args.lbutton1, NULL, NULL, UI_LABEL_TEXT);\r
+               Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+               btn.Margin(margin);\r
+               btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+               dialog_button_add_callback(dialog, btn, 1, obj, args.onclick, args.onclickdata);\r
+\r
+               buttons.SetRow(btn, 0);\r
+               buttons.SetColumn(btn, c++);\r
+               buttons.Children().Append(btn);\r
+       }\r
+       if (args.lbutton2) {\r
+               ColumnDefinition bcoldef = ColumnDefinition();\r
+               bcoldef.Width(gl);\r
+               buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+               Button btn = Button();\r
+               ui_set_button_label(btn, args.lbutton2, NULL, NULL, UI_LABEL_TEXT);\r
+               Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+               btn.Margin(margin);\r
+               btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+               dialog_button_add_callback(dialog, btn, 2, obj, args.onclick, args.onclickdata);\r
+\r
+               buttons.SetRow(btn, 0);\r
+               buttons.SetColumn(btn, c++);\r
+               buttons.Children().Append(btn);\r
+       }\r
+       if (args.rbutton3) {\r
+               ColumnDefinition bcoldef = ColumnDefinition();\r
+               bcoldef.Width(gl);\r
+               buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+               Button btn = Button();\r
+               ui_set_button_label(btn, args.rbutton3, NULL, NULL, UI_LABEL_TEXT);\r
+               Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+               btn.Margin(margin);\r
+               btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+               dialog_button_add_callback(dialog, btn, 3, obj, args.onclick, args.onclickdata);\r
+\r
+               buttons.SetRow(btn, 0);\r
+               buttons.SetColumn(btn, c++);\r
+               buttons.Children().Append(btn);\r
+       }\r
+       if (args.rbutton4) {\r
+               ColumnDefinition bcoldef = ColumnDefinition();\r
+               bcoldef.Width(gl);\r
+               buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+               Button btn = Button();\r
+               ui_set_button_label(btn, args.rbutton4, NULL, NULL, UI_LABEL_TEXT);\r
+               Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+               btn.Margin(margin);\r
+               btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+               dialog_button_add_callback(dialog, btn, 4, obj, args.onclick, args.onclickdata);\r
+\r
+               buttons.SetRow(btn, 0);\r
+               buttons.SetColumn(btn, c++);\r
+               buttons.Children().Append(btn);\r
+       }\r
+\r
+       dialogContent.SetRow(buttons, 1);\r
+       dialogContent.SetColumn(buttons, 0);\r
+       dialogContent.Children().Append(buttons);\r
+\r
+\r
+       dialog.Content(dialogContent);\r
+       dialog.XamlRoot(window->window.Content().XamlRoot());\r
+\r
+       obj->widget->Show = [dialog]() {\r
+               dialog.ShowAsync();\r
+       };\r
+\r
+       return obj;\r
+}\r
+\r
+void ui_window_size(UiObject *obj, int width, int height) {\r
+       UIWINDOW win = obj->wobj;\r
+       if (win) {\r
+               winrt::Windows::Graphics::SizeInt32 wsize;\r
+               wsize.Width = width;\r
+               wsize.Height = height;\r
+               win->window.AppWindow().Resize(wsize);\r
+       }\r
+}\r
+\r
+\r
+\r
+\r
+static Windows::Foundation::IAsyncAction create_dialog_async(UiObject *obj, UiDialogArgs args) {\r
+       UiObject* current = uic_current_obj(obj);\r
+       Window parentWindow = current->wobj->window;\r
+\r
+       ContentDialog dialog = ContentDialog();\r
+       dialog.XamlRoot(parentWindow.Content().XamlRoot());\r
+\r
+       if (args.title) {\r
+               wchar_t *str = str2wstr(args.title, nullptr);\r
+               dialog.Title(winrt::box_value(str));\r
+               free(str);\r
+       }\r
+\r
+       TextBox textfield{ nullptr };\r
+       PasswordBox password{ nullptr };\r
+       if(args.input || args.password) {\r
+               StackPanel panel = StackPanel();\r
+               panel.Orientation(Orientation::Vertical);\r
+               if (args.content) {\r
+                       wchar_t *str = str2wstr(args.content, nullptr);\r
+                       TextBlock label = TextBlock();\r
+                       label.Text(str);\r
+                       panel.Children().Append(label);\r
+                       free(str);\r
+               }\r
+\r
+               Thickness margin = { 0, 5, 0, 0 };\r
+               if (args.password) {\r
+                       password = PasswordBox();\r
+                       password.Margin(margin);\r
+                       panel.Children().Append(password);\r
+               } else {\r
+                       textfield = TextBox();\r
+                       textfield.Margin(margin);\r
+                       panel.Children().Append(textfield);\r
+               }\r
+               \r
+               panel.Margin(margin);\r
+\r
+               dialog.Content(panel);\r
+\r
+       } else {\r
+               if (args.content) {\r
+                       wchar_t *str = str2wstr(args.content, nullptr);\r
+                       dialog.Content(winrt::box_value(str));\r
+                       free(str);\r
+               }\r
+       }\r
+\r
+       if (args.button1_label) {\r
+               wchar_t *str = str2wstr(args.button1_label, nullptr);\r
+               dialog.PrimaryButtonText(winrt::hstring(str));\r
+               free(str);\r
+               dialog.DefaultButton(ContentDialogButton::Primary);\r
+       }\r
+       if (args.button2_label) {\r
+               wchar_t *str = str2wstr(args.button2_label, nullptr);\r
+               dialog.SecondaryButtonText(winrt::hstring(str));\r
+               free(str);\r
+       }\r
+       if (args.closebutton_label) {\r
+               wchar_t *str = str2wstr(args.closebutton_label, nullptr);\r
+               dialog.CloseButtonText(winrt::hstring(str));\r
+               free(str);\r
+       }\r
+\r
+       ContentDialogResult result = co_await dialog.ShowAsync();\r
+\r
+       if (args.result) {\r
+               UiEvent evt;\r
+               evt.obj = current;\r
+               evt.document = current->ctx->document;\r
+               evt.window = current->window;\r
+               evt.eventdata = NULL;\r
+               evt.intval = 0;\r
+               if (result == ContentDialogResult::Primary) {\r
+                       evt.intval = 1;\r
+               } else if (result == ContentDialogResult::Secondary) {\r
+                       evt.intval = 2;\r
+               }\r
+\r
+               if (args.password) {\r
+                       std::wstring wstr(password.Password());\r
+                       char *text = wchar2utf8(wstr.c_str(), wstr.length());\r
+                       evt.eventdata = text;\r
+               } else if (args.input) {\r
+                       std::wstring wstr(textfield.Text());\r
+                       char *text = wchar2utf8(wstr.c_str(), wstr.length());\r
+                       evt.eventdata = text;\r
+               }\r
+\r
+               args.result(&evt, args.resultdata);\r
+\r
+               if (evt.eventdata) {\r
+                       free(evt.eventdata);\r
+               }\r
+       }\r
+}\r
+\r
+UIEXPORT void ui_dialog_create(UiObject *obj, UiDialogArgs args) {\r
+       create_dialog_async(obj, args);\r
+}\r
+\r
+\r
+\r
+// --------------------------------------- File Dialog ---------------------------------------\r
+\r
+static void filedialog_callback(\r
+       UiObject *obj,\r
+       ui_callback file_selected_callback,\r
+       void *cbdata,\r
+       winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Storage::StorageFile> result)\r
+{\r
+       UiFileList flist;\r
+       flist.nfiles = result.Size();\r
+       flist.files = new char*[flist.nfiles];\r
+\r
+       int i = 0;\r
+       for (auto const& file : result) {\r
+               winrt::hstring path = file.Path();\r
+               flist.files[i++] = wchar2utf8(path.c_str(), path.size());\r
+       }\r
+\r
+       UiEvent evt;\r
+       evt.obj = obj;\r
+       evt.document = obj->ctx->document;\r
+       evt.window = obj->window;\r
+       evt.eventdata = &flist;\r
+       evt.intval = 0;\r
+       file_selected_callback(&evt, cbdata);\r
+\r
+       for (int i = 0; i < flist.nfiles;i++) {\r
+               free(flist.files[i]);\r
+       }\r
+       delete[] flist.files;\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {\r
+       FileOpenPicker openFileDialog = FileOpenPicker();\r
+       auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>()\r
+       };\r
+\r
+       HWND hwnd{ nullptr };\r
+       winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));\r
+\r
+       initializeWithWindow->Initialize(hwnd);\r
+\r
+       openFileDialog.FileTypeFilter().Append(L"*");\r
+\r
+       if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {\r
+               auto files = co_await openFileDialog.PickMultipleFilesAsync();\r
+               filedialog_callback(obj, file_selected_callback, cbdata, files);\r
+       } else {\r
+               auto file = co_await openFileDialog.PickSingleFileAsync();\r
+               auto files = single_threaded_vector<winrt::Windows::Storage::StorageFile>();\r
+               files.Append(file);\r
+               filedialog_callback(obj, file_selected_callback, cbdata, files.GetView());\r
+       }\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction save_filedialog_async(UiObject *obj, char *name, ui_callback file_selected_callback, void *cbdata) {\r
+       IFileSaveDialog *saveFileDialog;\r
+\r
+       HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast<void**>(&saveFileDialog));\r
+       if (FAILED(hr))\r
+       {\r
+               co_return;\r
+       }\r
+\r
+       if (name) {\r
+               wchar_t *wname = str2wstr(name, NULL);\r
+               saveFileDialog->SetFileName(wname);\r
+               free(wname);\r
+               free(name);\r
+       }\r
+       \r
+\r
+       hr = saveFileDialog->Show(NULL);\r
+       if (SUCCEEDED(hr)) {\r
+               IShellItem *item;\r
+               hr = saveFileDialog->GetResult(&item);\r
+               if (SUCCEEDED(hr)) {\r
+                       PWSTR wpath;\r
+                       hr = item->GetDisplayName(SIGDN_FILESYSPATH, &wpath);\r
+\r
+                       if (SUCCEEDED(hr)) {\r
+                               char *path = wchar2utf8(wpath, lstrlen(wpath));\r
+                               CoTaskMemFree(wpath);\r
+\r
+                               UiFileList flist;\r
+                               flist.nfiles = 1;\r
+                               flist.files = new char*[1];\r
+                               flist.files[0] = path;\r
+\r
+                               UiEvent evt;\r
+                               evt.obj = obj;\r
+                               evt.document = obj->ctx->document;\r
+                               evt.window = obj->window;\r
+                               evt.eventdata = &flist;\r
+                               evt.intval = 0;\r
+                               file_selected_callback(&evt, cbdata);\r
+\r
+                               free(path);\r
+                               delete[] flist.files;\r
+                       }\r
+                       item->Release();\r
+               }\r
+       }\r
+\r
+       // cleanup\r
+       saveFileDialog->Release();\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) {\r
+       FolderPicker folderPicker = FolderPicker();\r
+       auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>()\r
+       };\r
+\r
+       HWND hwnd{ nullptr };\r
+       winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));\r
+\r
+       initializeWithWindow->Initialize(hwnd);\r
+\r
+       folderPicker.FileTypeFilter().Append(L"*");\r
+\r
+       auto folder = co_await folderPicker.PickSingleFolderAsync();\r
+       if (folder) {\r
+               winrt::hstring hpath = folder.Path();\r
+               char *cpath =  wchar2utf8(hpath.c_str(), hpath.size());\r
+               \r
+               UiFileList flist;\r
+               flist.nfiles = 1;\r
+               flist.files = &cpath;\r
+\r
+               UiEvent evt;\r
+               evt.obj = obj;\r
+               evt.document = obj->ctx->document;\r
+               evt.window = obj->window;\r
+               evt.eventdata = &flist;\r
+               evt.intval = 0;\r
+               file_selected_callback(&evt, cbdata);\r
+       }\r
+}\r
+\r
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {\r
+       if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {\r
+               folderdialog_async(obj, file_selected_callback, cbdata);\r
+       } else {\r
+               open_filedialog_async(obj, mode, file_selected_callback, cbdata);\r
+       }\r
+}\r
+\r
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {\r
+       char *n = name ? _strdup(name) : NULL;\r
+       save_filedialog_async(obj, n, file_selected_callback, cbdata);\r
+}\r
diff --git a/ui/winui/window.h b/ui/winui/window.h
new file mode 100644 (file)
index 0000000..308b5f6
--- /dev/null
@@ -0,0 +1,44 @@
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+\r
+#include "../ui/window.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
diff --git a/ui/winui/winui.vcxproj b/ui/winui/winui.vcxproj
new file mode 100644 (file)
index 0000000..9f72b38
--- /dev/null
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" />\r
+  <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" />\r
+  <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" />\r
+  <PropertyGroup Label="Globals">\r
+    <CppWinRTOptimized>true</CppWinRTOptimized>\r
+    <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>\r
+    <MinimalCoreWin>true</MinimalCoreWin>\r
+    <ProjectGuid>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</ProjectGuid>\r
+    <ProjectName>winui</ProjectName>\r
+    <RootNamespace>winui</RootNamespace>\r
+    <!--\r
+      $(TargetName) should be same as $(RootNamespace) so that the produced binaries (.exe/.pri/etc.)\r
+      have a name that matches the .winmd\r
+    -->\r
+    <TargetName>$(RootNamespace)</TargetName>\r
+    <DefaultLanguage>de-DE</DefaultLanguage>\r
+    <MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>\r
+    <AppContainerApplication>false</AppContainerApplication>\r
+    <AppxPackage>false</AppxPackage>\r
+    <ApplicationType>Windows Store</ApplicationType>\r
+    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>\r
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+    <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>\r
+    <UseWinUI>true</UseWinUI>\r
+    <EnableMsixTooling>true</EnableMsixTooling>\r
+    <WindowsPackageType>None</WindowsPackageType>\r
+    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <PlatformToolset>v143</PlatformToolset>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <DesktopCompatible>true</DesktopCompatible>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">\r
+    <UseDebugLibraries>true</UseDebugLibraries>\r
+    <LinkIncremental>true</LinkIncremental>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">\r
+    <UseDebugLibraries>false</UseDebugLibraries>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <LinkIncremental>true</LinkIncremental>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <ItemDefinitionGroup>\r
+    <ClCompile>\r
+      <PrecompiledHeader>Use</PrecompiledHeader>\r
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>\r
+    </ClCompile>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">\r
+    <ClCompile>\r
+      <PreprocessorDefinitions>_DEBUG;DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdc17</LanguageStandard_C>\r
+      <RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebugDLL</RuntimeLibrary>\r
+    </ClCompile>\r
+    <Link>\r
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">\r
+    <ClCompile>\r
+      <PreprocessorDefinitions>DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <SDLCheck Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</SDLCheck>\r
+      <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Release|x64'">stdc17</LanguageStandard_C>\r
+    </ClCompile>\r
+    <Link>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup Condition="'$(WindowsPackageType)'!='None' and Exists('Package.appxmanifest')">\r
+    <AppxManifest Include="Package.appxmanifest">\r
+      <SubType>Designer</SubType>\r
+    </AppxManifest>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="..\common\context.h" />\r
+    <ClInclude Include="..\common\document.h" />\r
+    <ClInclude Include="..\common\menu.h" />\r
+    <ClInclude Include="..\common\object.h" />\r
+    <ClInclude Include="..\common\properties.h" />\r
+    <ClInclude Include="..\common\toolbar.h" />\r
+    <ClInclude Include="..\common\types.h" />\r
+    <ClInclude Include="..\common\ucx_properties.h" />\r
+    <ClInclude Include="..\ui\button.h" />\r
+    <ClInclude Include="..\ui\container.h" />\r
+    <ClInclude Include="..\ui\display.h" />\r
+    <ClInclude Include="..\ui\dnd.h" />\r
+    <ClInclude Include="..\ui\entry.h" />\r
+    <ClInclude Include="..\ui\graphics.h" />\r
+    <ClInclude Include="..\ui\icons.h" />\r
+    <ClInclude Include="..\ui\image.h" />\r
+    <ClInclude Include="..\ui\menu.h" />\r
+    <ClInclude Include="..\ui\properties.h" />\r
+    <ClInclude Include="..\ui\range.h" />\r
+    <ClInclude Include="..\ui\stock.h" />\r
+    <ClInclude Include="..\ui\text.h" />\r
+    <ClInclude Include="..\ui\toolbar.h" />\r
+    <ClInclude Include="..\ui\toolkit.h" />\r
+    <ClInclude Include="..\ui\tree.h" />\r
+    <ClInclude Include="..\ui\ui.h" />\r
+    <ClInclude Include="..\ui\window.h" />\r
+    <ClInclude Include="appmenu.h" />\r
+    <ClInclude Include="button.h" />\r
+    <ClInclude Include="commandbar.h" />\r
+    <ClInclude Include="condvar.h" />\r
+    <ClInclude Include="container.h" />\r
+    <ClInclude Include="dnd.h" />\r
+    <ClInclude Include="icons.h" />\r
+    <ClInclude Include="image.h" />\r
+    <ClInclude Include="label.h" />\r
+    <ClInclude Include="list.h" />\r
+    <ClInclude Include="pch.h" />\r
+    <ClInclude Include="App.xaml.h">\r
+      <DependentUpon>App.xaml</DependentUpon>\r
+    </ClInclude>\r
+    <ClInclude Include="MainWindow.xaml.h">\r
+      <DependentUpon>MainWindow.xaml</DependentUpon>\r
+    </ClInclude>\r
+    <ClInclude Include="stock.h" />\r
+    <ClInclude Include="table.h" />\r
+    <ClInclude Include="text.h" />\r
+    <ClInclude Include="toolkit.h" />\r
+    <ClInclude Include="util.h" />\r
+    <ClInclude Include="window.h" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ApplicationDefinition Include="App.xaml" />\r
+    <Page Include="MainWindow.xaml" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="..\common\context.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\document.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\menu.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\object.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\properties.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\toolbar.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\types.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\ucx_properties.c">\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="appmenu.cpp" />\r
+    <ClCompile Include="button.cpp" />\r
+    <ClCompile Include="commandbar.cpp" />\r
+    <ClCompile Include="condvar.cpp" />\r
+    <ClCompile Include="container.cpp" />\r
+    <ClCompile Include="dnd.cpp" />\r
+    <ClCompile Include="icons.cpp" />\r
+    <ClCompile Include="image.cpp" />\r
+    <ClCompile Include="label.cpp" />\r
+    <ClCompile Include="list.cpp" />\r
+    <ClCompile Include="pch.cpp">\r
+      <PrecompiledHeader>Create</PrecompiledHeader>\r
+    </ClCompile>\r
+    <ClCompile Include="App.xaml.cpp">\r
+      <DependentUpon>App.xaml</DependentUpon>\r
+    </ClCompile>\r
+    <ClCompile Include="MainWindow.xaml.cpp">\r
+      <DependentUpon>MainWindow.xaml</DependentUpon>\r
+    </ClCompile>\r
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />\r
+    <ClCompile Include="stock.cpp" />\r
+    <ClCompile Include="table.cpp" />\r
+    <ClCompile Include="text.cpp" />\r
+    <ClCompile Include="toolkit.cpp" />\r
+    <ClCompile Include="util.cpp" />\r
+    <ClCompile Include="window.cpp" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Midl Include="App.idl">\r
+      <SubType>Code</SubType>\r
+      <DependentUpon>App.xaml</DependentUpon>\r
+    </Midl>\r
+    <Midl Include="MainWindow.idl">\r
+      <SubType>Code</SubType>\r
+      <DependentUpon>MainWindow.xaml</DependentUpon>\r
+    </Midl>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Text Include="readme.txt">\r
+      <DeploymentContent>false</DeploymentContent>\r
+    </Text>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Image Include="Assets\LockScreenLogo.scale-200.png" />\r
+    <Image Include="Assets\SplashScreen.scale-200.png" />\r
+    <Image Include="Assets\Square150x150Logo.scale-200.png" />\r
+    <Image Include="Assets\Square44x44Logo.scale-200.png" />\r
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />\r
+    <Image Include="Assets\StoreLogo.png" />\r
+    <Image Include="Assets\Wide310x150Logo.scale-200.png" />\r
+  </ItemGroup>\r
+  <!--\r
+    Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging\r
+    Tools extension to be activated for this project even if the Windows App SDK Nuget\r
+    package has not yet been restored.\r
+  -->\r
+  <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">\r
+    <ProjectCapability Include="Msix" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\..\make\vs\ucx\ucx.vcxproj">\r
+      <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+    </ProjectReference>\r
+  </ItemGroup>\r
+  <!--\r
+    Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution\r
+    Explorer "Package and Publish" context menu entry to be enabled for this project even if\r
+    the Windows App SDK Nuget package has not yet been restored.\r
+  -->\r
+  <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">\r
+    <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+    <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" />\r
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" />\r
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />\r
+    <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" />\r
+  </ImportGroup>\r
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+    <PropertyGroup>\r
+      <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>\r
+    </PropertyGroup>\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props'))" />\r
+    <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets'))" />\r
+  </Target>\r
+</Project>
\ No newline at end of file
diff --git a/ui/winui/winui.vcxproj.filters b/ui/winui/winui.vcxproj.filters
new file mode 100644 (file)
index 0000000..7340b7f
--- /dev/null
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup>\r
+    <ApplicationDefinition Include="App.xaml" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Page Include="MainWindow.xaml" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Midl Include="App.idl" />\r
+    <Midl Include="MainWindow.idl" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="pch.cpp" />\r
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />\r
+    <ClCompile Include="appmenu.cpp" />\r
+    <ClCompile Include="button.cpp" />\r
+    <ClCompile Include="commandbar.cpp" />\r
+    <ClCompile Include="container.cpp" />\r
+    <ClCompile Include="list.cpp" />\r
+    <ClCompile Include="table.cpp" />\r
+    <ClCompile Include="text.cpp" />\r
+    <ClCompile Include="toolkit.cpp" />\r
+    <ClCompile Include="util.cpp" />\r
+    <ClCompile Include="window.cpp" />\r
+    <ClCompile Include="stock.cpp" />\r
+    <ClCompile Include="icons.cpp" />\r
+    <ClCompile Include="label.cpp" />\r
+    <ClCompile Include="dnd.cpp" />\r
+    <ClCompile Include="condvar.cpp" />\r
+    <ClCompile Include="image.cpp" />\r
+    <ClCompile Include="..\common\context.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\document.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\menu.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\object.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\properties.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\toolbar.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\types.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+    <ClCompile Include="..\common\ucx_properties.c">\r
+      <Filter>common</Filter>\r
+    </ClCompile>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ClInclude Include="pch.h" />\r
+    <ClInclude Include="appmenu.h" />\r
+    <ClInclude Include="button.h" />\r
+    <ClInclude Include="commandbar.h" />\r
+    <ClInclude Include="container.h" />\r
+    <ClInclude Include="list.h" />\r
+    <ClInclude Include="table.h" />\r
+    <ClInclude Include="text.h" />\r
+    <ClInclude Include="toolkit.h" />\r
+    <ClInclude Include="util.h" />\r
+    <ClInclude Include="window.h" />\r
+    <ClInclude Include="..\ui\button.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\container.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\display.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\dnd.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\entry.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\graphics.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\image.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\menu.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\properties.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\range.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\stock.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\text.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\toolbar.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\toolkit.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\tree.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\ui.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\ui\window.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="stock.h" />\r
+    <ClInclude Include="icons.h" />\r
+    <ClInclude Include="label.h" />\r
+    <ClInclude Include="dnd.h" />\r
+    <ClInclude Include="condvar.h" />\r
+    <ClInclude Include="image.h" />\r
+    <ClInclude Include="..\ui\icons.h">\r
+      <Filter>public</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\context.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\document.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\menu.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\object.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\properties.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\toolbar.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\types.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="..\common\ucx_properties.h">\r
+      <Filter>common</Filter>\r
+    </ClInclude>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Image Include="Assets\Wide310x150Logo.scale-200.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\StoreLogo.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\Square150x150Logo.scale-200.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\Square44x44Logo.scale-200.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\SplashScreen.scale-200.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+    <Image Include="Assets\LockScreenLogo.scale-200.png">\r
+      <Filter>Assets</Filter>\r
+    </Image>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Filter Include="Assets">\r
+      <UniqueIdentifier>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</UniqueIdentifier>\r
+    </Filter>\r
+    <Filter Include="public">\r
+      <UniqueIdentifier>{2b58fe46-d27b-4335-b63c-13ec2204fa24}</UniqueIdentifier>\r
+    </Filter>\r
+    <Filter Include="common">\r
+      <UniqueIdentifier>{35c88d5c-b36f-41b3-86c0-784227845ae9}</UniqueIdentifier>\r
+    </Filter>\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Text Include="readme.txt" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Manifest Include="app.manifest" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <None Include="packages.config" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+  </ItemGroup>\r
+</Project>
\ No newline at end of file