add existing code (build system, libs, initial mizucp code)
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 5 Jun 2021 07:39:33 +0000 (09:39 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 5 Jun 2021 07:39:33 +0000 (09:39 +0200)
172 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
configure [new file with mode: 0755]
libidav/Makefile [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/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/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_unix.sh [new file with mode: 0755]
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/windows.mk [new file with mode: 0644]
mizucp/Makefile [new file with mode: 0644]
mizucp/main.c [new file with mode: 0644]
mizucp/main.h [new file with mode: 0644]
mizunara/Makefile [new file with mode: 0644]
mizunara/main.c [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/README [new file with mode: 0644]
ucx/allocator.c [new file with mode: 0644]
ucx/array.c [new file with mode: 0644]
ucx/avl.c [new file with mode: 0644]
ucx/buffer.c [new file with mode: 0644]
ucx/list.c [new file with mode: 0644]
ucx/logging.c [new file with mode: 0644]
ucx/map.c [new file with mode: 0644]
ucx/mempool.c [new file with mode: 0644]
ucx/properties.c [new file with mode: 0644]
ucx/stack.c [new file with mode: 0644]
ucx/string.c [new file with mode: 0644]
ucx/test.c [new file with mode: 0644]
ucx/ucx.c [new file with mode: 0644]
ucx/ucx/allocator.h [new file with mode: 0644]
ucx/ucx/array.h [new file with mode: 0644]
ucx/ucx/avl.h [new file with mode: 0644]
ucx/ucx/buffer.h [new file with mode: 0644]
ucx/ucx/list.h [new file with mode: 0644]
ucx/ucx/logging.h [new file with mode: 0644]
ucx/ucx/map.h [new file with mode: 0644]
ucx/ucx/mempool.h [new file with mode: 0644]
ucx/ucx/properties.h [new file with mode: 0644]
ucx/ucx/stack.h [new file with mode: 0644]
ucx/ucx/string.h [new file with mode: 0644]
ucx/ucx/test.h [new file with mode: 0644]
ucx/ucx/ucx.h [new file with mode: 0644]
ucx/ucx/utils.h [new file with mode: 0644]
ucx/utils.c [new file with mode: 0644]
ui/Makefile [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/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/types.c [new file with mode: 0644]
ui/common/types.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/image.c [new file with mode: 0644]
ui/gtk/image.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/model.c [new file with mode: 0644]
ui/gtk/model.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/tree.c [new file with mode: 0644]
ui/gtk/tree.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/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/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]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5a5d528
--- /dev/null
@@ -0,0 +1,2 @@
+build
+config.mk
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/configure b/configure
new file mode 100755 (executable)
index 0000000..44f8e8a
--- /dev/null
+++ b/configure
@@ -0,0 +1,728 @@
+#!/bin/sh
+
+
+PREFIX=/usr
+EPREFIX=$PREFIX
+
+BINDIR=
+SBINDIR=
+LIBDIR=
+LIBEXECDIR=
+DATADIR=
+SYSCONFDIR=
+SHAREDSTATEDIR=
+LOCALSTATEDIR=
+INCLUDEDIR=
+INFODIR=
+MANDIR=
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+       echo "Cannot create tmp dir"
+       echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+
+# help text
+printhelp()
+{
+       echo "Usage: $0 [OPTIONS]..."
+       cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+
+Options:
+  --toolkit=(gtk3|motif)
+
+__EOF__
+}
+
+#
+# parse arguments 
+#
+for ARG in $@
+do
+    case "$ARG" in
+               "--prefix="*)         PREFIX=${ARG#--prefix=} ;;
+               "--exec-prefix="*)    EPREFIX=${ARG#--exec-prefix=} ;;
+               "--bindir="*)         BINDIR=${ARG#----bindir=} ;;
+               "--sbindir="*)        SBINDIR=${ARG#--sbindir=} ;;
+               "--libdir="*)         LIBDIR=${ARG#--libdir=} ;;
+               "--libexecdir="*)     LIBEXECDIR=${ARG#--libexecdir=} ;;
+               "--datadir="*)        DATADIR=${ARG#--datadir=} ;;
+               "--sysconfdir="*)     SYSCONFDIR=${ARG#--sysconfdir=} ;;
+               "--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;;
+               "--localstatedir="*)  LOCALSTATEDIR=${ARG#--localstatedir=} ;;
+               "--includedir="*)     INCLUDEDIR=${ARG#--includedir=} ;;
+               "--infodir="*)        INFODIR=${ARG#--infodir=} ;;
+               "--mandir"*)          MANDIR=${ARG#--mandir} ;;
+               "--help"*) printhelp; exit 1 ;;
+       "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+               "-"*) echo "unknown option: $ARG"; exit 1 ;;
+       esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+       BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+       SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+       LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+       LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+       DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+       SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+       SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+       LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+       INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+       INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+       MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+       PLATFORM_NAME=$p
+       break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+
+PREFIX=$PREFIX
+EPREFIX=$EPREFIX
+
+BINDIR=$BINDIR
+SBINDIR=$SBINDIR
+LIBDIR=$LIBDIR
+LIBEXECDIR=$LIBEXECDIR
+DATADIR=$DATADIR
+SYSCONFDIR=$SYSCONFDIR
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+LOCALSTATEDIR=$LOCALSTATEDIR
+INCLUDEDIR=$INCLUDEDIR
+INFODIR=$INFODIR
+MANDIR=$MANDIR
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+dependency_curl()
+{
+    printf "checking for curl... "
+    # dependency curl platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        CFLAGS="$CFLAGS -I/mingw/include"    
+        LDFLAGS="$LDFLAGS -lcurl"    
+               echo yes
+        return 0
+    done
+       
+    # dependency curl platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        curl-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `curl-config --cflags`"
+        else
+            break
+        fi
+        curl-config --ldflags > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `curl-config --ldflags`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency curl 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG libcurl
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libcurl`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libcurl`"
+               echo yes
+        return 0
+    done
+       
+    # dependency curl 
+    while true
+    do
+        curl-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `curl-config --cflags`"
+        else
+            break
+        fi
+        curl-config --ldflags > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `curl-config --ldflags`"
+        else
+            break
+        fi
+        which curl-config > /dev/null
+        if [ $? -ne 0 ]; then
+               break
+        fi
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_gtk3()
+{
+    printf "checking for gtk3... "
+    # dependency gtk3 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG gtk+-3.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-3.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-3.0`"
+        CFLAGS="$CFLAGS -DUI_GTK3"    
+        LDFLAGS="$LDFLAGS -lpthread"    
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_openssl()
+{
+    printf "checking for openssl... "
+    # dependency openssl platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -lssl -lcrypto"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -framework CoreFoundation"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl platform="bsd"
+    while true
+    do
+       if isnotplatform "bsd"; then
+            break
+        fi
+               if isplatform "macos"; then
+            break
+        fi
+        LDFLAGS="$LDFLAGS -lssl -lcrypto"    
+               echo yes
+        return 0
+    done
+       
+    # dependency openssl 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG openssl
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags openssl`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs openssl`"
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_motif()
+{
+    printf "checking for motif... "
+    # dependency motif 
+    while true
+    do
+        CFLAGS="$CFLAGS -DUI_MOTIF"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+dependency_libxml2()
+{
+    printf "checking for libxml2... "
+    # dependency libxml2 platform="windows"
+    while true
+    do
+       if isnotplatform "windows"; then
+            break
+        fi
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 platform="macos"
+    while true
+    do
+       if isnotplatform "macos"; then
+            break
+        fi
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+               $PKG_CONFIG libxml-2.0
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libxml-2.0`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libxml-2.0`"
+               echo yes
+        return 0
+    done
+       
+    # dependency libxml2 
+    while true
+    do
+        xml2-config --cflags > /dev/null
+        if [ $? -eq 0 ]; then
+            CFLAGS="$CFLAGS `xml2-config --cflags`"
+        else
+            break
+        fi
+        xml2-config --libs > /dev/null
+        if [ $? -eq 0 ]; then
+            LDFLAGS="$LDFLAGS `xml2-config --libs`"
+        else
+            break
+        fi
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# general dependencies
+CFLAGS=
+LDFLAGS=
+while true
+do
+    if isnotplatform "unix"; then
+        break
+    fi
+    if isplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+               cat >> $TEMP_DIR/make.mk << __EOF__
+OBJ_EXT = o
+LIB_EXT = a
+PACKAGE_SCRIPT = package_unix.sh
+
+__EOF__
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    while true
+    do
+        
+        LDFLAGS="$LDFLAGS -lpthread"    
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    if isnotplatform "bsd"; then
+        break
+    fi
+    if isplatform "macos"; then
+        break
+    fi
+    while true
+    do
+        
+        CFLAGS="$CFLAGS -I/usr/local/include"    
+        LDFLAGS="$LDFLAGS -L/usr/local/lib"    
+        
+        break
+    done
+    
+    break
+done
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# OPTION VALUES
+#
+checkopt_toolkit_gtk3()
+{
+       VERR=0
+       dependency_gtk3
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+
+__EOF__
+       return 0
+}
+checkopt_toolkit_motif()
+{
+       VERR=0
+       dependency_motif
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       cat >> $TEMP_DIR/make.mk << __EOF__
+TOOLKIT = motif
+
+__EOF__
+       return 0
+}
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+# Target: tk
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+
+# Features
+
+# Option: --toolkit
+if [ -z $OPT_TOOLKIT ]; then
+       SAVED_ERROR=$ERROR
+       SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+       ERROR=0
+       while true
+       do
+               checkopt_toolkit_motif
+               if [ $? -eq 0 ]; then
+                       echo "  toolkit: motif" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               checkopt_toolkit_gtk3
+               if [ $? -eq 0 ]; then
+                       echo "  toolkit: gtk3" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               break
+       done
+       if [ $ERROR -ne 0 ]; then
+               SAVED_ERROR=1
+       fi
+       ERROR=$SAVED_ERROR
+       DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+       if false; then
+               false
+       elif [ $OPT_TOOLKIT = "gtk3" ]; then
+               echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+               checkopt_toolkit_gtk3
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       elif [ $OPT_TOOLKIT = "motif" ]; then
+               echo "  toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+               checkopt_toolkit_motif
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       fi
+fi
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "TK_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "TK_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "TK_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+# Target: dav
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+dependency_curl
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED curl "
+       ERROR=1
+fi
+dependency_libxml2
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libxml2 "
+       ERROR=1
+fi
+dependency_openssl
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED openssl "
+       ERROR=1
+fi
+
+# Features
+
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "DAV_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "DAV_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "DAV_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+if [ $ERROR -ne 0 ]; then
+       echo
+       echo "Error: Unresolved dependencies"
+       echo $DEPENDENCIES_FAILED
+       rm -Rf $TEMP_DIR
+       exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "Options:"
+cat $TEMP_DIR/options
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
diff --git a/libidav/Makefile b/libidav/Makefile
new file mode 100644 (file)
index 0000000..659d0c5
--- /dev/null
@@ -0,0 +1,53 @@
+#
+# 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.
+#
+
+BUILD_ROOT=../
+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
+
+OBJ = $(SRC:%.c=../build/libidav/%.$(OBJ_EXT))
+
+all: ../build/ucx ../build/lib/libidav.$(LIB_EXT)
+
+../build/lib/libidav.$(LIB_EXT): $(OBJ)
+       $(AR) $(ARFLAGS) $(AOFLAGS)../build/lib/libidav.$(LIB_EXT) $(OBJ)
+
+../build/libidav/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(DAV_CFLAGS) -c -I../ucx -o $@ $<
+
diff --git a/libidav/crypto.c b/libidav/crypto.c
new file mode 100644 (file)
index 0000000..e9d25c3
--- /dev/null
@@ -0,0 +1,1526 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#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 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);
+    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);
+        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);
+        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;
+    unsigned char *out = malloc(outlen);
+       
+    // decrypt
+    if(clen > 0) {
+        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);
+        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
+
+
+
+UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) {
+    UcxBuffer *encbuf = ucx_buffer_new(
+            NULL,
+            in->size+16,
+            UCX_BUFFER_AUTOEXTEND);
+    
+    AESEncrypter *enc = aes_encrypter_new(
+            key,
+            in,
+            (dav_read_func)ucx_buffer_read,
+            NULL);
+    if(!enc) {
+        ucx_buffer_free(encbuf);
+        return NULL;
+    }
+    
+    char buf[1024];
+    size_t r;
+    while((r = aes_read(buf, 1, 1024, enc)) > 0) {
+        ucx_buffer_write(buf, 1, r, encbuf);
+    }
+    aes_encrypter_close(enc);
+    
+    encbuf->pos = 0;
+    return encbuf;
+}
+
+UcxBuffer* aes_decrypt_buffer(UcxBuffer *in, DavKey *key) {
+    UcxBuffer *decbuf = ucx_buffer_new(
+            NULL,
+            in->size,
+            UCX_BUFFER_AUTOEXTEND);
+    AESDecrypter *dec = aes_decrypter_new(
+            key,
+            decbuf,
+            (dav_write_func)ucx_buffer_write);
+    
+    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..7ea6597
--- /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 <ucx/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);
+
+UcxBuffer* aes_encrypt_buffer(UcxBuffer *buf, DavKey *key);
+UcxBuffer* aes_decrypt_buffer(UcxBuffer *buf, 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..9eb6ec5
--- /dev/null
@@ -0,0 +1,1466 @@
+/*
+ * 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 <ucx/utils.h>
+#include <ucx/map.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;
+    
+    DavQLArg *cur = NULL;
+    UCX_FOREACH(elm, st->args) {
+        intptr_t type = (intptr_t)elm->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;
+}
+
+sstr_t dav_format_string(UcxAllocator *a, sstr_t fstr, DavQLArgList *ap, davqlerror_t *error) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    
+    int placeholder = 0;
+    for(int i=0;i<fstr.length;i++) {
+        char c = fstr.ptr[i];
+        if(placeholder) {
+            if(c == '%') {
+                // no placeholder, %% transposes to %
+                ucx_buffer_putc(buf, c);
+            } else {
+                // detect placeholder type and insert arg
+                int err = 0;
+                switch(c) {
+                    case 's': {
+                        char *arg = dav_ql_getarg_str(ap);
+                        ucx_buffer_puts(buf, arg);
+                        break;
+                    }
+                    case 'd': {
+                        int arg = dav_ql_getarg_int(ap);
+                        ucx_bprintf(buf, "%d", arg);
+                        break;
+                    }
+                    case 'u': {
+                        unsigned int arg = dav_ql_getarg_uint(ap);
+                        ucx_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) {
+                    ucx_buffer_free(buf);
+                    sstr_t n;
+                    n.ptr = NULL;
+                    n.length = 0;
+                    return n;
+                }
+            }
+            placeholder = 0;
+        } else {
+            if(c == '%') {
+                placeholder = 1;
+            } else {
+                ucx_buffer_putc(buf, c);
+            }
+        }
+    }
+    *error = DAVQL_OK;
+    
+    sstr_t ret = sstrdup_a(a, sstrn(buf->space, buf->size));
+    ucx_buffer_free(buf);
+    return ret;
+}
+
+static int fl_add_properties(DavSession *sn, UcxMempool *mp, UcxMap *map, DavQLExpression *expression) {
+    if(!expression) {
+        return 0;
+    }
+    
+    if(expression->type == DAVQL_IDENTIFIER) {
+        DavProperty *property = ucx_mempool_malloc(mp, sizeof(DavProperty));
+
+        char *name;
+        DavNamespace *ns = dav_get_property_namespace(
+                sn->context,
+                sstrdup_a(mp->allocator, expression->srctext).ptr,
+                &name);
+        if(!ns) {
+            return -1;
+        }
+        
+        property->ns = ns;
+        property->name = name;
+        property->value = NULL;
+        
+        ucx_map_sstr_put(map, expression->srctext, property);
+    }
+    
+    if(expression->left) {
+        if(fl_add_properties(sn, mp, map, expression->left)) {
+            return -1;
+        }
+    }
+    if(expression->right) {
+        if(fl_add_properties(sn, mp, map, expression->right)) {
+            return -1;
+        }
+    }
+    
+    return 0;
+}
+
+static UcxBuffer* fieldlist2propfindrequest(DavSession *sn, UcxMempool *mp, UcxList *fields, int *isallprop) {
+    UcxMap *properties = ucx_map_new(32);
+    *isallprop = 0;
+    
+    UCX_FOREACH(elm, fields) {
+        DavQLField *field = elm->data;
+        if(!sstrcmp(field->name, S("*"))) {
+            ucx_map_free(properties);
+            *isallprop = 1;
+            return create_allprop_propfind_request();
+        } else if(!sstrcmp(field->name, S("-"))) {
+            ucx_map_free(properties);
+            return create_propfind_request(sn, NULL, "propfind", 0);
+        } else {
+            if(fl_add_properties(sn, mp, properties, field->expr)) {
+                // TODO: set error
+                ucx_map_free(properties);
+                return NULL;
+            }
+        }
+    }
+    
+    UcxMapIterator i = ucx_map_iterator(properties);
+    UcxKey key;
+    DavProperty *value;
+    UcxList *list = NULL;
+    UCX_MAP_FOREACH(key, value, i) {
+        list = ucx_list_append(list, value);
+    }
+    
+    UcxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0);
+    ucx_list_free(list);
+    ucx_map_free(properties);
+    return reqbuf;
+}
+
+static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, UcxList *fields) {
+    UcxMap *new_properties = ucx_map_new_a(sn->mp->allocator, 32);
+    DavResourceData *data = (DavResourceData*)res->data;
+    
+    // add basic properties
+    void *value;
+    
+    sstr_t cl_keystr = dav_property_key("DAV:", "getcontentlength");
+    UcxKey cl_key = ucx_key(cl_keystr.ptr, cl_keystr.length);
+    value = ucx_map_get(data->properties, cl_key);
+    if(value) {
+        ucx_map_put(new_properties, cl_key, value);
+    }
+    
+    sstr_t cd_keystr = dav_property_key("DAV:", "creationdate");
+    UcxKey cd_key = ucx_key(cd_keystr.ptr, cd_keystr.length);
+    value = ucx_map_get(data->properties, cd_key);
+    if(value) {
+        ucx_map_put(new_properties, cd_key, value);
+    }
+    
+    sstr_t lm_keystr = dav_property_key("DAV:", "getlastmodified");
+    UcxKey lm_key = ucx_key(lm_keystr.ptr, lm_keystr.length);
+    value = ucx_map_get(data->properties, lm_key);
+    if(value) {
+        ucx_map_put(new_properties, lm_key, value);
+    }
+    
+    sstr_t ct_keystr = dav_property_key("DAV:", "getcontenttype");
+    UcxKey ct_key = ucx_key(ct_keystr.ptr, ct_keystr.length);
+    value = ucx_map_get(data->properties, ct_key);
+    if(value) {
+        ucx_map_put(new_properties, ct_key, value);
+    }
+    
+    sstr_t rt_keystr = dav_property_key("DAV:", "resourcetype");
+    UcxKey rt_key = ucx_key(rt_keystr.ptr, rt_keystr.length);
+    value = ucx_map_get(data->properties, rt_key);
+    if(value) {
+        ucx_map_put(new_properties, rt_key, value);
+    }
+    
+    sstr_t cn_keystr = dav_property_key(DAV_NS, "crypto-name");
+    UcxKey cn_key = ucx_key(cn_keystr.ptr, cn_keystr.length);
+    value = ucx_map_get(data->properties, cn_key);
+    if(value) {
+        ucx_map_put(new_properties, cn_key, value);
+    }
+    
+    sstr_t ck_keystr = dav_property_key(DAV_NS, "crypto-key");
+    UcxKey ck_key = ucx_key(ck_keystr.ptr, ck_keystr.length);
+    value = ucx_map_get(data->properties, ck_key);
+    if(value) {
+        ucx_map_put(new_properties, ck_key, value);
+    }
+    
+    sstr_t ch_keystr = dav_property_key(DAV_NS, "crypto-hash");
+    UcxKey ch_key = ucx_key(ch_keystr.ptr, ch_keystr.length);
+    value = ucx_map_get(data->properties, ch_key);
+    if(value) {
+        ucx_map_put(new_properties, ch_key, value);
+    }
+    
+    // add properties from field list
+    UCX_FOREACH(elm, fields) {
+        DavCompiledField *field = elm->data;
+        DavQLStackObj field_result;
+        if(!dav_exec_expr(field->code, res, &field_result)) {
+            sstr_t str;
+            str.ptr = NULL;
+            str.length = 0;
+            DavXmlNode *node = NULL;
+            if(field_result.type == 0) {
+                str = ucx_asprintf(
+                        sn->mp->allocator,
+                        "%d",
+                        field_result.data.integer);
+            } else if(field_result.type == 1) {
+                if(field_result.data.string) {
+                    str = sstrdup_a(sn->mp->allocator, sstrn(
+                            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) {
+                sstr_t 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;
+                
+                ucx_map_sstr_put(new_properties, key, prop);
+                free(key.ptr);
+            }
+        } else {
+            // TODO: error
+            resource_free_properties(sn, new_properties);
+            return -1;
+        }
+    }
+    
+    ucx_map_remove(data->properties, cl_key);
+    ucx_map_remove(data->properties, cd_key);
+    ucx_map_remove(data->properties, lm_key);
+    ucx_map_remove(data->properties, ct_key);
+    ucx_map_remove(data->properties, rt_key);
+    ucx_map_remove(data->properties, cn_key);
+    ucx_map_remove(data->properties, ck_key);
+    ucx_map_remove(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) {
+    UcxMempool *mp = ucx_mempool_new(128);
+    DavResult result;
+    result.result = NULL;
+    result.status = 1;
+    
+    DavQLArgList *args = dav_ql_get_args(st, ap);
+    if(!args) {
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, args, (ucx_destructor)dav_ql_free_arglist);
+    
+    int isallprop;
+    UcxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp, st->fields, &isallprop);
+    if(!rqbuf) {
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, rqbuf, (ucx_destructor)ucx_buffer_free);
+    
+    // compile field list
+    UcxList *cfieldlist = NULL;
+    UCX_FOREACH(elm, st->fields) {
+        DavQLField *field = elm->data;
+        if(sstrcmp(field->name, S("*")) && sstrcmp(field->name, S("-"))) {
+            // compile field expression
+            UcxBuffer *code = dav_compile_expr(
+                    sn->context,
+                    mp->allocator,
+                    field->expr,
+                    args);
+            if(!code) {
+                // TODO: set error string
+                return result;
+            }
+            ucx_mempool_reg_destr(mp, code, (ucx_destructor)ucx_buffer_free);
+            DavCompiledField *cfield = ucx_mempool_malloc(
+                    mp,
+                    sizeof(DavCompiledField));
+            
+            char *ns;
+            char *name;
+            dav_get_property_namespace_str(
+                sn->context,
+                sstrdup_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;
+            cfieldlist = ucx_list_append_a(mp->allocator, cfieldlist, cfield);
+        } 
+    }
+    
+    // get path string
+    davqlerror_t error;
+    sstr_t path = dav_format_string(mp->allocator, st->path, args, &error);
+    if(error) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    
+    int depth = st->depth == DAV_DEPTH_PLACEHOLDER ?
+            dav_ql_getarg_int(args) : st->depth;
+    
+    UcxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args);
+    if(st->where && !where) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    if(where) {
+        ucx_mempool_reg_destr(mp, where, (ucx_destructor)ucx_buffer_free);
+    }
+    
+    // compile order criterion
+    UcxList *ordercr = NULL;
+    UCX_FOREACH(elm, st->orderby) {
+        DavQLOrderCriterion *oc = elm->data;
+        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;
+            sstr_t propertyname = sstrchr(column->srctext, ':');
+            if(propertyname.length > 0) {
+                char *ns;
+                char *name;
+                dav_get_property_namespace_str(
+                        sn->context,
+                        sstrdup_a(mp->allocator, column->srctext).ptr,
+                        &ns,
+                        &name);
+                if(ns && name) {
+                    DavOrderCriterion *cr = ucx_mempool_malloc(mp, sizeof(DavOrderCriterion));
+                    cr->type = 1;
+                    sstr_t keystr = dav_property_key_a(mp->allocator, ns, name);
+                    cr->column.property = ucx_key(keystr.ptr, keystr.length);
+                    cr->descending = oc->descending;
+                    ordercr = ucx_list_append_a(mp->allocator, ordercr, cr);
+                } else {
+                    // error
+                    // TODO: cleanup
+                    ucx_mempool_destroy(mp);
+                    return result;
+                }
+            } else if(dav_identifier2resprop(column->srctext, &resprop)) {
+                DavOrderCriterion *cr = ucx_mempool_malloc(mp, sizeof(DavOrderCriterion));
+                cr->type = 0;
+                cr->column.resprop = resprop;
+                cr->descending = oc->descending;
+                ordercr = ucx_list_append_a(mp->allocator, ordercr, cr);
+            } else {
+                // error
+                // TODO: cleanup
+                ucx_mempool_destroy(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
+            ucx_mempool_destroy(mp);
+            return result;
+        }
+    }
+    
+    DavResource *selroot = dav_resource_new(sn, path.ptr);
+    
+    UcxList *stack = NULL; // stack with DavResource* elements
+    // initialize the stack with the requested resource
+    DavQLRes *res = ucx_mempool_malloc(mp, sizeof(DavQLRes));
+    res->resource = selroot;
+    res->depth = 0;
+    stack = ucx_list_prepend(stack, res);
+    
+    // reuseable response buffer
+    UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    if(!rpbuf) {
+        // TODO: cleanup
+        ucx_mempool_destroy(mp);
+        return result;
+    }
+    ucx_mempool_reg_destr(mp, rpbuf, (ucx_destructor)ucx_buffer_free);
+    
+    result.result = selroot;
+    result.status = 0;
+    
+    // do a propfind request for each resource on the stack
+    while(stack) {
+        DavQLRes *sr = stack->data; // get first element from the stack
+        stack = ucx_list_remove(stack, stack); // remove first element
+        DavResource *root = sr->resource;
+        
+        util_set_url(sn, dav_resource_get_href(sr->resource));
+        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 %s\n%.*s\n\n", sr->resource->path, sr->resource->href, rpbuf->pos, rpbuf->space);
+        
+        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);
+            // TODO: test if parser is null
+            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);
+                        ucx_list_free(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 > sr->depth+1))
+                                {
+                                    DavQLRes *rs = ucx_mempool_malloc(
+                                            mp,
+                                            sizeof(DavQLRes));
+                                    rs->resource = child;
+                                    rs->depth = sr->depth + 1;
+                                    stack = ucx_list_prepend(stack, 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
+        ucx_buffer_seek(rpbuf, SEEK_SET, 0);
+    }
+    
+    ucx_mempool_destroy(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(sstr_t src, davqlresprop_t *prop) {
+    if(!sstrcmp(src, S("name"))) {
+        *prop = DAVQL_RES_NAME;
+    } else if(!sstrcmp(src, S("path"))) {
+        *prop = DAVQL_RES_PATH;
+    } else if(!sstrcmp(src, S("href"))) {
+        *prop = DAVQL_RES_HREF;
+    } else if(!sstrcmp(src, S("contentlength"))) {
+        *prop = DAVQL_RES_CONTENTLENGTH;
+    } else if(!sstrcmp(src, S("contenttype"))) {
+        *prop = DAVQL_RES_CONTENTTYPE;
+    } else if(!sstrcmp(src, S("creationdate"))) {
+        *prop = DAVQL_RES_CREATIONDATE;
+    } else if(!sstrcmp(src, S("lastmodified"))) {
+        *prop = DAVQL_RES_LASTMODIFIED;
+    } else if(!sstrcmp(src, S("iscollection"))) {
+        *prop = DAVQL_RES_ISCOLLECTION;
+    } else {
+        return 0;
+    }
+    return 1;
+}
+
+static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) {
+    if(!expr) {
+        return 0;
+    }
+     
+    int numcmd = 1;
+    DavQLCmd cmd;
+    memset(&cmd, 0, sizeof(DavQLCmd));
+    davqlerror_t error;
+    
+    sstr_t 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)) {
+                ucx_buffer_write(&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);
+            ucx_buffer_write(&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);
+                ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+            } else {
+                // error
+                return -1;
+            }
+            break;
+        }
+        case DAVQL_IDENTIFIER: {
+            sstr_t propertyname = sstrchr(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,
+                        sstrdup_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(!sstrcmp(src, S("true"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 1;
+                } else if(!sstrcmp(src, S("false"))) {
+                    cmd.type = DAVQL_CMD_INT;
+                    cmd.data.integer = 0;
+                } else {
+                    // error, unknown identifier
+                    return -1;
+                }
+            }
+            ucx_buffer_write(&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;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEG: {
+                    cmd.type = DAVQL_CMD_OP_UNARY_NEG;
+                    ucx_buffer_write(&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;
+            }
+            ucx_buffer_write(&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;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LAND: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_AND;
+                    ucx_buffer_write(&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);
+                    ucx_buffer_write(&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;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    numcmd += nleft + nright;
+                    break;
+                }
+                case DAVQL_LXOR: {
+                    cmd.type = DAVQL_CMD_OP_LOGICAL_XOR;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_EQ: {
+                    cmd.type = DAVQL_CMD_OP_EQ;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_NEQ: {
+                    cmd.type = DAVQL_CMD_OP_NEQ;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LT: {
+                    cmd.type = DAVQL_CMD_OP_LT;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GT: {
+                    cmd.type = DAVQL_CMD_OP_GT;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LE: {
+                    cmd.type = DAVQL_CMD_OP_LE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_GE: {
+                    cmd.type = DAVQL_CMD_OP_GE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_LIKE: {
+                    cmd.type = DAVQL_CMD_OP_LIKE;
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    break;
+                }
+                case DAVQL_UNLIKE: {
+                    cmd.type = DAVQL_CMD_OP_UNLIKE;
+                    ucx_buffer_write(&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);
+                    ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode);
+                    
+                    // TODO: resolve function name
+                    cmd.type = DAVQL_CMD_CALL;
+                    cmd.data.func = NULL;
+                    ucx_buffer_write(&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;
+}
+
+UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList  *ap) {
+    UcxBuffer *bcode = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    if(!bcode) {
+        return NULL;
+    }
+    
+    if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) {
+        ucx_buffer_free(bcode);
+        return NULL;
+    }
+    
+    return bcode;
+}
+
+static int cmd_str_cmp(DavQLStackObj obj1, DavQLStackObj obj2, davqlcmdtype_t cmd) {
+    sstr_t s1 = obj1.type == 1 ?
+        sstrn(obj1.data.string, obj1.length) :
+        ucx_sprintf("%" PRId64, obj1.data.integer);
+    sstr_t s2 = obj1.type == 1 ?
+        sstrn(obj2.data.string, obj2.length) :
+        ucx_sprintf("%" PRId64, obj2.data.integer);
+    
+    int res = 0;
+    switch(cmd) {
+        case DAVQL_CMD_OP_EQ: {
+            res = sstrcmp(s1, s2) == 0;
+            break;
+        }
+        case DAVQL_CMD_OP_NEQ: {
+            res = sstrcmp(s1, s2) != 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LT: {
+            res = sstrcmp(s1, s2) < 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GT: {
+            res = sstrcmp(s1, s2) > 0;
+            break;
+        }
+        case DAVQL_CMD_OP_LE: {
+            res  = sstrcmp(s1, s2) <= 0;
+            break;
+        }
+        case DAVQL_CMD_OP_GE: {
+            res = sstrcmp(s1, s2) >= 0;
+            break;
+        }
+        default: break;
+    }
+    
+    if(obj1.type == 0) {
+        free(s1.ptr);
+    }
+    if(obj2.type == 0) {
+        free(s2.ptr);
+    }
+    
+    return res;
+}
+
+int dav_exec_expr(UcxBuffer *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..11a6258
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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 <ucx/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
+} 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;
+        sstr_t         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;
+    UcxBuffer *code;
+} DavCompiledField;
+
+typedef struct DavOrderCriterion {
+    int type; // 0: resprop, 1: property
+    union DavQLColumn {
+        davqlresprop_t resprop;
+        UcxKey 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);
+
+UcxBuffer* dav_path_string(sstr_t src, DavQLArgList *args, davqlerror_t *error);
+sstr_t dav_format_string(UcxAllocator *a, sstr_t fstr, DavQLArgList *ap, davqlerror_t *error);
+
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap);
+
+int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop);
+
+UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap);
+
+int dav_exec_expr(UcxBuffer *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..38f5694
--- /dev/null
@@ -0,0 +1,1817 @@
+/*
+ * 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 <ucx/utils.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: ");
+        UCX_FOREACH(field, stmt->fields) {
+            DavQLField *f = field->data;
+            printf("%.*s, ", sfmtarg(f->name));
+        }
+        printf("\b\b  \b\b\n");
+    }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+    // Basic information
+    size_t fieldcount = ucx_list_size(stmt->fields);
+    int specialfield = 0;
+    if (stmt->fields) {
+        DavQLField* firstfield = (DavQLField*)stmt->fields->data;
+        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",
+        sfmtarg(stmt->srctext),
+        _map_querytype(stmt->type),
+        fieldcount,
+        _map_specialfield(specialfield));
+    
+    dav_debug_ql_fnames_print(stmt);
+    printf("Path: %.*s\nHas where clause: %s\n",
+        sfmtarg(stmt->path),
+        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) {
+        UCX_FOREACH(crit, stmt->orderby) {
+            DavQLOrderCriterion *critdata = crit->data;
+            printf("%.*s %s%s", sfmtarg(critdata->column->srctext),
+                critdata->descending ? "desc" : "asc",
+                crit->next ? ", " : "\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)) {
+        sstr_t empty = ST("(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;
+    UcxList *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) {
+                DavQLField* field = ((DavQLField*)stmt->fields->data);
+                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 ?
+                ((DavQLOrderCriterion*)stmt->orderby->data)->column : NULL;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            if (examineelem) {
+                UcxList *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) (((DavQLToken*)(token)->data)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, UcxList *token) {
+    
+    // we try to achieve two things: get as many information as possible
+    // and recover the concrete source string (and not the token strings)
+    sstr_t emptystring = ST("");
+    sstr_t prev = token->prev ? (token->prev->prev ?
+        token_sstr(token->prev->prev) : token_sstr(token->prev))
+        : emptystring;
+    sstr_t tokenstr = token_sstr(token);
+    sstr_t 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;
+    char *pn = tokenstr.ptr + tokenstr.length;
+    int ln = next.ptr+next.length - pn;
+    
+    stmt->errorcode = errorcode;
+    stmt->errormessage = ucx_sprintf(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)
+
+#define dqlsec_list_append_or_free(stmt, list, data)            \
+    do {                                                        \
+        UcxList *_dqlsecbak_ = list;                            \
+        list = ucx_list_append(list, data);                     \
+        if (!list) {                                            \
+            free(data);                                         \
+            data = NULL;                                        \
+            (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;      \
+            list = _dqlsecbak_;                                 \
+            return 0;                                           \
+        }                                                       \
+    } while(0)
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+    sstr_t keywords[] ={ST("select"), ST("set"), ST("from"), ST("at"), ST("as"),
+        ST("where"), ST("anywhere"), ST("like"), ST("unlike"), ST("and"),
+        ST("or"), ST("not"), ST("xor"), ST("with"), ST("infinity"),
+        ST("order"), ST("by"), ST("asc"), ST("desc")
+    };
+    for (int i = 0 ; i < sizeof(keywords)/sizeof(sstr_t) ; i++) {
+        if (!sstrcasecmp(token->value, keywords[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+    sstr_t operators[] = {ST("and"), ST("or"), ST("not"), ST("xor"),
+        ST("like"), ST("unlike")
+    };
+    for (int i = 0 ; i < sizeof(operators)/sizeof(sstr_t) ; i++) {
+        if (!sstrcasecmp(token->value, operators[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static UcxList* dav_parse_add_token(UcxList *tokenlist, 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;
+        }
+    }
+    
+    
+    UcxList *ret = ucx_list_append(tokenlist, token);
+    if (ret) {
+        return ret;
+    } else {
+        ucx_list_free(tokenlist);
+        return NULL;
+    }
+}
+
+static UcxList* dav_parse_tokenize(sstr_t src) {
+#define alloc_token() do {token = malloc(sizeof(DavQLToken));\
+        if(!token) {ucx_list_free(tokens); return NULL;}} while(0)
+#define add_token() do {tokens = dav_parse_add_token(tokens, token); \
+        if(!tokens) {return NULL;}} while(0)
+    UcxList *tokens = 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 = S("");
+    UcxList *ret = ucx_list_append(tokens, token);
+    if (ret) {
+        return ret;
+    } else {
+        ucx_list_free(tokens);
+        return NULL;
+    }
+#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);
+    }
+    free(crit);
+}
+
+#define token_is(token, expectedclass) (token && \
+    (((DavQLToken*)(token)->data)->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+    !sstrcasecmp(((DavQLToken*)(token)->data)->value, S(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,UcxList*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* 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 = ucx_list_get(token, 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 dav_add_fmt_args(DavQLStatement *stmt, sstr_t str) {
+    int placeholder = 0;
+    for (size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        if (placeholder) {
+            if (c != '%') {
+                stmt->args = ucx_list_append(
+                        stmt->args,
+                        (void*)(intptr_t)c);
+            }
+            placeholder = 0;
+        } else if (c == '%') {
+            placeholder = 1;
+        }
+    }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, UcxList* 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
+        stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)expr->srctext.ptr[1]);
+    } else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, UcxList* token,
+        DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, UcxList* 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;
+    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 = ucx_list_get(token, 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, UcxList* 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 = ucx_list_get(token, 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, UcxList* token,
+        DavQLExpression* expr) {
+    
+    UcxList *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 = ucx_list_get(token, 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) {
+        sstr_t lasttoken =
+            token_sstr(ucx_list_get(firsttoken, 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, UcxList* 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, UcxList* 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, UcxList* 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, UcxList *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 = ucx_list_get(token, 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, UcxList *token) {
+    
+    // RULE:    "-"
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        dqlsec_list_append_or_free(stmt, stmt->fields, field);
+        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);
+        dqlsec_list_append_or_free(stmt, stmt->fields, field);
+        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 = ucx_list_get(token, 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 *field;
+                    dqlsec_malloc(stmt, field, DavQLField);
+                    memcpy(field, &localfield, sizeof(DavQLField));
+                    dqlsec_list_append_or_free(stmt, stmt->fields, field);
+                }                
+            } 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));
+                dqlsec_list_append_or_free(stmt, stmt->fields, field);
+                token = ucx_list_get(token, 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);
+                dqlsec_list_append_or_free(stmt, stmt->fields, field);
+
+                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, UcxList *token,
+        DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *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 = ucx_list_get(token, total_consumed);
+
+    UcxList* 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, UcxList *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) {
+            sstr_t lasttok = token_sstr(ucx_list_get(token, 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 = ucx_list_get(token->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, UcxList *token,
+        DavQLExpression *expr) {
+    
+    UcxList *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 = ucx_list_get(token, 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 = ucx_list_get(token, 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;
+        sstr_t lasttok = token_sstr(ucx_list_get(firsttoken, total_consumed-1));
+        expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, UcxList *token) {
+    dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+    
+    return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *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 {
+                            sstr_t 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, UcxList *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 = ucx_list_get(token, 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, UcxList *token) {
+    
+    int total_consumed = 0, consumed;
+    
+    DavQLOrderCriterion crit;
+    
+    // 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 = ucx_list_get(token, consumed);
+        total_consumed += consumed;
+        
+        DavQLOrderCriterion *criterion;
+        dqlsec_malloc(stmt, criterion, DavQLOrderCriterion);
+        memcpy(criterion, &crit, sizeof(DavQLOrderCriterion));
+        dqlsec_list_append_or_free(stmt, stmt->orderby, criterion);
+        
+        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, UcxList *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 = ucx_list_get(token, consumed);
+            total_consumed += consumed;
+            
+            // Add assignment to list and check if there's another one
+            dqlsec_list_append_or_free(stmt, stmt->fields, field);
+            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, UcxList *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)) {
+            sstr_t 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;
+        stmt->args = ucx_list_append(stmt->args, (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, UcxList *tokens) {
+    stmt->type = DAVQL_SELECT;
+
+    // Consume field list
+    tokens = ucx_list_get(tokens, 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 = ucx_list_get(tokens, 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 = ucx_list_get(tokens,
+            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 = ucx_list_get(tokens,
+            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 = ucx_list_get(tokens,
+                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, UcxList *tokens) {
+    stmt->type = DAVQL_SET;
+    
+    // Consume assignments
+    tokens = ucx_list_get(tokens, 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 = ucx_list_get(tokens, 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 = ucx_list_get(tokens,
+            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 = ucx_list_get(tokens,
+            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(sstr_t 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 = sstrtrim(srctext);
+    
+    if (stmt->srctext.length) {   
+        // tokenization
+        UcxList* 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
+            UCX_FOREACH(token, tokens) {
+                free(token->data);
+            }
+            ucx_list_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) {
+    UCX_FOREACH(expr, stmt->fields) {
+        dav_free_field(expr->data);
+    }
+    ucx_list_free(stmt->fields);
+    
+    if (stmt->where) {
+        dav_free_expression(stmt->where);
+    }
+    if (stmt->errormessage) {
+        free(stmt->errormessage);
+    }
+    UCX_FOREACH(crit, stmt->orderby) {
+        dav_free_order_criterion(crit->data);
+    }
+    ucx_list_free(stmt->orderby);
+    ucx_list_free(stmt->args);
+    free(stmt);
+}
diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h
new file mode 100644 (file)
index 0000000..fa52910
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * 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 "ucx/string.h"
+#include "ucx/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 {
+    davqltokenclass_t tokenclass;
+    sstr_t value;
+} DavQLToken;
+
+/**
+ * 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.
+     */
+    sstr_t 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>
+     */
+    sstr_t 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.
+     */
+    sstr_t 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.
+     */
+    UcxList* fields;
+    /**
+     * A string that denotes the queried path.
+     */
+    sstr_t 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.
+     */
+    UcxList* 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
+     */
+    UcxList* 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(sstr_t stmt);
+
+/**
+ * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement.
+ */
+#define dav_parse_cstr_statement(stmt) dav_parse_statement(S(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..e54da5d
--- /dev/null
@@ -0,0 +1,1350 @@
+/*
+ * 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 <ucx/utils.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+
+int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin) {
+    return ucx_buffer_seek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
+}
+
+/* ----------------------------- PROPFIND ----------------------------- */
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        UcxBuffer *request,
+        UcxBuffer *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, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    UcxMap *respheaders = ucx_map_new(32);
+    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 = ucx_map_cstr_get(respheaders, "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);
+    ucx_map_free_content(respheaders, free);
+    ucx_map_free(respheaders);
+       
+    return ret;
+}
+
+UcxBuffer* create_allprop_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOFREE);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propfind xmlns:D=\"DAV:\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:allprop/></D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+UcxBuffer* create_cryptoprop_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOFREE);
+    scstr_t s;
+    
+    s = SC("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = SC("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = SC("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    int add_crypto_name = 1;
+    int add_crypto_key = 1;
+    int add_crypto_hash = 1;
+    char *crypto_ns = "idav";
+    UcxMap *namespaces = ucx_map_new(8);
+    UCX_FOREACH(elm, properties) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            ucx_map_cstr_put(namespaces, 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;
+        ucx_map_cstr_put(namespaces, "idav", &idav_ns);
+    }
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    ucx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
+    
+    UcxMapIterator mapi = ucx_map_iterator(namespaces);
+    UcxKey key;
+    DavNamespace *ns;
+    UCX_MAP_FOREACH(key, ns, mapi) {
+        s = S(" xmlns:");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(ns->prefix);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S("=\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(ns->name);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S("\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    s = S(">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // default properties
+    s = S("<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:creationdate />\n<D:getlastmodified />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:getcontentlength />\n<D:getcontenttype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:resourcetype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // crypto properties
+    if(DAV_CRYPTO(sn) && !nocrypt) {
+        if(add_crypto_name) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-name />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_key) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-key />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        if(add_crypto_hash) {
+            ucx_buffer_putc(buf, '<');
+            ucx_buffer_puts(buf, crypto_ns);
+            s = S(":crypto-hash />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+    }
+    
+    // extra properties
+    UCX_FOREACH(elm, properties) {
+        DavProperty *prop = elm->data;
+        s = S("<");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(prop->ns->prefix);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S(":");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = sstr(prop->name);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = S(" />\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    // end
+    ucx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
+    
+    ucx_map_free(namespaces);
+    return buf;
+}
+
+UcxBuffer* create_basic_propfind_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);  
+    s = S(DAV_NS);
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("\" >\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // properties
+    s = S("<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<D:resourcetype />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-key />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-name />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("<i:crypto-hash />\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    s = S("</D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // end
+    s = S("</D:propfind>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+PropfindParser* create_propfind_parser(UcxBuffer *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;
+    UcxList *properties = NULL; // xmlNode list
+    char *crypto_name = NULL; // name set by crypto-name property
+    char *crypto_key = NULL;
+    
+    result->properties = NULL;
+    
+    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;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                // error
+                                return -1;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            if(!sstrcmp(status_str, S("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) {
+                            properties = ucx_list_append(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->properties = properties;
+    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) {
+        ucx_list_free(result->properties);
+    }
+}
+
+int hrefeq(DavSession *sn, char *href1, char *href2) {
+    sstr_t href_s = sstr(util_url_decode(sn, href1));
+    sstr_t href_r = sstr(util_url_decode(sn, href2));
+    int ret = 0;
+    if(!sstrcmp(href_s, 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(!sstrcmp(href_s, 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(!sstrcmp(href_s, href_r)) {
+                ret = 1;
+            }
+        }
+    } 
+
+    free(href_s.ptr);
+    free(href_r.ptr);
+    
+    return ret;
+}
+
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *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 {
+        sstr_t resname = sstr(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
+    UCX_FOREACH(elm, response->properties) {
+        xmlNode *prop = elm->data;
+        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) {
+            UcxMap *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;
+    char *href = NULL;
+    UcxList *properties = NULL; // 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((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;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                sn->error = DAV_ERROR;
+                                return 1;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            if(!sstrcmp(status_str, S("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) {
+                            properties = ucx_list_append(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;
+    }
+    
+    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 {
+            sstr_t resname = sstr(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);
+        }
+        
+        href = dav_session_strdup(sn, href);
+        res = dav_resource_new_full(sn, resource->path, name, href);
+        
+        dav_session_free(sn, name);
+    }
+    res->iscollection = iscollection;
+    
+    // add properties
+    int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+    xmlNode *crypto_prop = NULL;
+    
+    UCX_FOREACH(elm, properties) {
+        xmlNode *prop = elm->data;
+        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;
+            }
+        }
+    }
+    ucx_list_free(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) {
+            UcxMap *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,
+        UcxBuffer *request,
+        UcxBuffer *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 = ucx_sprintf("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, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+    
+    ucx_buffer_seek(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;
+}
+
+UcxBuffer* create_proppatch_request(DavResourceData *data) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    scstr_t s;
+    
+    UcxMap *namespaces = ucx_map_new(8);
+    char prefix[8];
+    int pfxnum = 0;
+    UCX_FOREACH(elm, data->set) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            snprintf(prefix, 8, "x%d", pfxnum++);
+            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
+        }
+    }
+    UCX_FOREACH(elm, data->remove) {
+        DavProperty *p = elm->data;
+        if(strcmp(p->ns->name, "DAV:")) {
+            snprintf(prefix, 8, "x%d", pfxnum++);
+            ucx_map_cstr_put(namespaces, p->ns->name, strdup(prefix));
+        }
+    }
+    
+    s = SC("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // write root element and namespaces
+    s = SC("<D:propertyupdate xmlns:D=\"DAV:\"");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    UcxMapIterator mapi = ucx_map_iterator(namespaces);
+    UcxKey key;
+    char *pfxval;
+    UCX_MAP_FOREACH(key, pfxval, mapi) {
+        s = SC(" xmlns:");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = scstr(pfxval);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = SC("=\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = scstrn(key.data, key.len);
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        s = SC("\"");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    s = SC(">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(data->set) {
+        s = SC("<D:set>\n<D:prop>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        UCX_FOREACH(elm, data->set) {
+            DavProperty *property = elm->data;
+            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
+            if(!prefix) {
+                prefix = "D";
+            }
+            
+            // begin tag
+            s = SC("<");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(">");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            
+            // content
+            DavXmlNode *content = property->value;
+            if(content->type == DAV_XML_TEXT && !content->next) {
+                ucx_buffer_write(content->content, 1, content->contentlength, buf);
+            } else {
+                dav_print_node(buf, (write_func)ucx_buffer_write, namespaces, content);
+            }
+            
+            // end tag
+            s = SC("</");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(">\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        s = SC("</D:prop>\n</D:set>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    if(data->remove) {
+        s = SC("<D:remove>\n<D:prop>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        UCX_FOREACH(elm, data->remove) {
+            DavProperty *property = elm->data;
+            char *prefix = ucx_map_cstr_get(namespaces, property->ns->name);
+            
+            s = SC("<");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(prefix);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(":");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = scstr(property->name);
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+            s = SC(" />\n");
+            ucx_buffer_write(s.ptr, 1, s.length, buf);
+        }
+        s = SC("</D:prop>\n</D:remove>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = SC("</D:propertyupdate>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    // cleanup namespace map
+    ucx_map_free_content(namespaces, free);
+    ucx_map_free(namespaces);
+    
+    return buf;
+}
+
+UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<D:set>\n<D:prop>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(DAV_ENCRYPT_NAME(sn)) {
+        s = S("<idav:crypto-name>");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        char *crname = aes_encrypt(name, strlen(name), key);
+        ucx_buffer_puts(buf, crname);
+        free(crname);
+        s = S("</idav:crypto-name>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = S("<idav:crypto-key>");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    ucx_buffer_puts(buf, key->name);
+    s = S("</idav:crypto-key>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    if(hash) {
+        s = S("<idav:crypto-hash>");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+        ucx_buffer_puts(buf, hash);
+        s = S("</idav:crypto-hash>\n");
+        ucx_buffer_write(s.ptr, 1, s.length, buf);
+    }
+    
+    s = S("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
+    ucx_buffer_write(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 = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+            free(url);
+        } else {
+            ltheader = ucx_sprintf("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);
+    
+    UcxBuffer *buf = NULL;
+    if(!read_func) {
+        buf = ucx_buffer_new(data, length, 0);
+        buf->size = length;
+        data = buf;
+        read_func = (dav_read_func)ucx_buffer_read;
+        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_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) {
+        ucx_buffer_free(buf);
+    }
+    
+    return ret;
+}
+
+CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *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 = ucx_sprintf("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, ucx_buffer_write);
+    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 = ucx_sprintf("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 = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr;
+        headers = curl_slist_append(headers, ltheader);
+        free(ltheader);
+    }
+    //sstr_t deststr = ucx_sprintf("Destination: %s", dest);
+    sstr_t deststr = sstrcat(2, S("Destination: "), sstr(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;
+}
+
+
+UcxBuffer* create_lock_request(void) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    sstr_t s;
+    
+    s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("<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");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    s = S("</D:lockinfo>\n");
+    ucx_buffer_write(s.ptr, 1, s.length, buf);
+    
+    return buf;
+}
+
+int parse_lock_response(DavSession *sn, UcxBuffer *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, UcxBuffer *request, UcxBuffer *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) {
+        sstr_t thdr;
+        if(timeout < 0) {
+            thdr = ucx_sprintf("%s", "Timeout: Infinite");
+        } else {
+            thdr = ucx_sprintf("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, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    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
+    sstr_t ltheader = ucx_sprintf("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
+    sstr_t ltheader;
+    struct curl_slist *headers = NULL;
+    if(locktoken) {
+        ltheader = ucx_sprintf("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, UcxBuffer *request, UcxBuffer *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, ucx_buffer_read);
+    curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek);
+    curl_easy_setopt(handle, CURLOPT_READDATA, request); 
+    curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+    
+    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write);
+    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..660e738
--- /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 <ucx/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 {
+    char    *href;
+    int     iscollection;
+    UcxList *properties;
+    char    *crypto_name;
+    char    *crypto_key;
+};
+
+struct LockDiscovery {
+    char    *timeout;
+    char    *locktoken;
+};
+
+int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin);
+
+CURLcode do_propfind_request(
+        DavSession *sn,
+        UcxBuffer *request,
+        UcxBuffer *response);
+
+CURLcode do_proppatch_request(
+        DavSession *sn,
+        char *lock,
+        UcxBuffer *request,
+        UcxBuffer *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);
+
+UcxBuffer* create_allprop_propfind_request(void);
+UcxBuffer* create_cryptoprop_propfind_request(void);
+UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt);
+UcxBuffer* create_basic_propfind_request(void);
+
+PropfindParser* create_propfind_parser(UcxBuffer *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, char *href1, 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, UcxBuffer *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);
+
+UcxBuffer* create_proppatch_request(DavResourceData *data);
+UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash);
+
+CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *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);
+
+UcxBuffer* create_lock_request(void);
+int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock);
+CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *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, UcxBuffer *request, UcxBuffer *response);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METHODS_H */
+
diff --git a/libidav/resource.c b/libidav/resource.c
new file mode 100644 (file)
index 0000000..8853e44
--- /dev/null
@@ -0,0 +1,1557 @@
+/*
+ * 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 "ucx/buffer.h"
+#include "ucx/utils.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, char *path) {
+    //char *href = util_url_path(url);
+    //DavResource *res = dav_resource_new_href(sn, href);
+    char *parent = util_parent_path(path);
+    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, 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, char *href) {  
+    DavResource *res = ucx_mempool_calloc(sn->mp, 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, char *parent_path, char *name, char *href) {
+    sstr_t n = sstr(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 = sstr(util_resource_name(href));
+                break;
+            }
+        }
+    }
+    // remove trailing '/'
+    if(n.length > 0 && n.ptr[n.length-1] == '/') {
+        n.length--;
+    }
+    
+    DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource));
+    res->session = sn;
+    
+    // set name, path and href
+    res->name = sstrdup_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, sstr(path), sstr(href));
+    }
+    free(path);
+    
+    return res;
+}
+
+void resource_free_properties(DavSession *sn, UcxMap *properties) {
+    if(!properties) return;
+    
+    UcxMapIterator i = ucx_map_iterator(properties);
+    DavProperty *property;
+    UCX_MAP_FOREACH(key, property, i) {
+        // TODO: free everything
+        dav_session_free(sn, property);
+    }
+    ucx_map_free(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);
+    
+    UCX_FOREACH(elm, data->set) {
+        DavProperty *p = elm->data;
+        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->value);
+        dav_session_free(sn, p);
+    }
+    
+    UCX_FOREACH(elm, data->remove) {
+        DavProperty *p = elm->data;
+        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, sstr_t href) {
+    res->href = sstrdup_a(res->session->mp->allocator, href).ptr;
+}
+
+void resource_set_info(DavResource *res, char *href_str) {
+    char *url_str = NULL;
+    curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
+    sstr_t name = sstr(util_resource_name(href_str));
+    sstr_t href = sstr(href_str);
+    
+    sstr_t base_href = sstr(util_url_path(res->session->base_url));
+    sstr_t path = sstrsubs(href, base_href.length - 1);
+    
+    UcxAllocator *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 = sstrdup_a(a, sstrn(uname, nlen)).ptr;
+    res->href = sstrdup_a(a, href).ptr;
+    res->path = sstrdup_a(a, sstrn(upath, plen)).ptr;
+    
+    curl_free(uname);
+    curl_free(upath);
+}
+
+DavResourceData* resource_data_new(DavSession *sn) {
+    DavResourceData *data = ucx_mempool_malloc(
+            sn->mp,
+            sizeof(DavResourceData));
+    if(!data) {
+        return NULL;
+    }
+    data->properties = ucx_map_new_a(sn->mp->allocator, 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;
+    
+    sstr_t key = dav_property_key(ns, name);
+    ucx_map_sstr_put(((DavResourceData*)res->data)->properties, key, prop);
+    free(key.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, UcxMap *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) {
+    sstr_t keystr = dav_property_key(ns, name);
+    UcxKey key = ucx_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) {
+    sstr_t keystr = dav_property_key(ns, name);
+    UcxKey key = ucx_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, UcxKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = ucx_map_get(data->properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key) {
+    DavResourceData *data = (DavResourceData*)res->data;
+    DavProperty *property = ucx_map_get(data->crypto_properties, key);
+    
+    return property ? property->value : NULL;
+}
+
+sstr_t dav_property_key(const char *ns, const char *name) {
+    return dav_property_key_a(ucx_default_allocator(), ns, name);
+}
+
+sstr_t dav_property_key_a(UcxAllocator *a, const char *ns, const char *name) {
+    scstr_t ns_str = scstr(ns);
+    scstr_t name_str = scstr(name);
+    
+    return sstrcat_a(a, 4, ns_str, S("\0"), name_str, S("\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, UcxList *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;
+            UCX_FOREACH(elm, ordercr) {
+                DavOrderCriterion *cr = elm->data;
+                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;
+    UcxList *remove_list = NULL;
+    UcxList *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) {
+        // if the property is in the remove list, we return NULL
+        UCX_FOREACH(elm, remove_list) {
+            DavProperty *p = elm->data;
+            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
+    UCX_FOREACH(elm, set_list) {
+        DavProperty *p = elm->data;
+        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;
+}
+
+void 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);
+    dav_set_string_property_ns(res, pns, pname, value);
+}
+
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    DavSession *sn = res->session;
+    UcxAllocator *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)) {
+        data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+    } else {
+        data->set = ucx_list_append_a(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;
+    UcxAllocator *a = sn->mp->allocator; 
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(sn, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+    } else {
+        data->set = ucx_list_append_a(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;
+    UcxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+        data->crypto_remove = ucx_list_append_a(a, data->crypto_remove, property);
+    } else {
+        data->remove = ucx_list_append_a(a, data->remove, property);
+    }
+}
+
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = value; // TODO: copy node?
+    
+    data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+}
+
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    property->value = dav_text_node(res->session, value);
+    
+    data->crypto_set = ucx_list_append_a(a, data->crypto_set, property);
+}
+
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
+    DavResourceData *data = res->data;
+    UcxAllocator *a = res->session->mp->allocator;
+    
+    DavProperty *property = createprop(res->session, ns, name);
+    
+    data->crypto_remove = ucx_list_append_a(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 = data->properties->count;
+    DavPropName *names = dav_session_calloc(
+            res->session,
+            *count,
+            sizeof(DavPropName));
+    
+    
+    UcxMapIterator i = ucx_map_iterator(data->properties);
+    DavProperty *value;
+    int j = 0;
+    UCX_MAP_FOREACH(key, value, i) {
+        DavPropName *name = &names[j];
+        
+        name->ns = value->ns->name;
+        name->name = value->name;
+        
+        j++;
+    }
+    
+    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) {
+    UcxBuffer *rqbuf = create_allprop_propfind_request();
+    int ret = dav_propfind(res->session, res, rqbuf);
+    ucx_buffer_free(rqbuf);
+    return ret;
+}
+
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
+    UcxMempool *mp = ucx_mempool_new(64);
+    
+    UcxList *proplist = NULL;
+    for(size_t i=0;i<numprop;i++) {
+        DavProperty *p = ucx_mempool_malloc(mp, sizeof(DavProperty));
+        p->name = properties[i].name;
+        p->ns = ucx_mempool_malloc(mp, sizeof(DavNamespace));
+        p->ns->name = properties[i].ns;
+        if(!strcmp(properties[i].ns, "DAV:")) {
+            p->ns->prefix = "D";
+        } else {
+            p->ns->prefix = ucx_asprintf(mp->allocator, "x%d", i).ptr;
+        }
+        p->value = NULL;
+        proplist = ucx_list_append_a(mp->allocator, proplist, p);
+    }
+    
+    UcxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
+    int ret = dav_propfind(res->session, res, rqbuf);
+    ucx_buffer_free(rqbuf);
+    ucx_mempool_destroy(mp);
+    return ret;
+}
+
+
+/*
+ * read wrapper with integrated hashing
+ */
+
+typedef struct {
+    DAV_SHA_CTX *sha;
+    void *stream;
+    dav_read_func read;
+    dav_seek_func seek;
+    int error;
+} HashStream;
+
+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) {
+        int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key;
+        CURLcode ret;
+        if(encryption) {
+            AESEncrypter *enc = NULL;
+            UcxBuffer *buf = NULL;
+            if(data->read) {
+                enc = aes_encrypter_new(
+                        sn->key,
+                        data->content,
+                        data->read,
+                        data->seek);
+            } else {
+                buf = ucx_buffer_new(data->content, data->length, 0);
+                buf->size = data->length;
+                enc = aes_encrypter_new(
+                        sn->key,
+                        buf,
+                        (dav_read_func)ucx_buffer_read,
+                        (dav_seek_func)dav_buffer_seek);
+            }
+              
+            // 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) {
+                ucx_buffer_free(buf);
+            }
+            
+            // add crypto properties
+            // TODO: store the properties later
+            if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
+                free(enc_hash);
+                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;
+            UcxBuffer *iobuf = NULL;
+            if(!data->read) {
+                iobuf = ucx_buffer_new(data->content, data->length, 0);
+                iobuf->size = data->length;
+                init_hash_stream(
+                        &hstr,
+                        iobuf,
+                        (dav_read_func)ucx_buffer_read,
+                        (dav_seek_func)ucx_buffer_seek);
+            } 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);
+        }
+        
+        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) {
+                ucx_mempool_free(sn->mp, 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) {
+            UcxBuffer *rqbuf = create_cryptoprop_propfind_request();
+            ret = dav_propfind(res->session, res, rqbuf);
+            ucx_buffer_free(rqbuf);
+        }
+        
+        if(!ret) {
+            DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
+            UcxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
+            if(!crypto_props) {
+                // resource hasn't encrypted properties yet
+                crypto_props = ucx_map_new(32); // create new map
+            }
+            
+            // remove all properties
+            UCX_FOREACH(elm, data->crypto_remove) {
+                if(crypto_props->count == 0) {
+                    break; // map already empty, can't remove any more
+                }
+                
+                DavProperty *property = elm->data;
+                sstr_t key = dav_property_key(property->ns->name, property->name);
+                DavProperty *existing_prop = ucx_map_sstr_remove(crypto_props, key);
+                if(existing_prop) {
+                    // TODO: free existing_prop
+                }                
+                free(key.ptr);
+            }
+            
+            // set properties
+            UCX_FOREACH(elm, data->crypto_set) {
+                DavProperty *property = elm->data;
+                sstr_t key = dav_property_key(property->ns->name, property->name);
+                DavProperty *existing_prop = ucx_map_sstr_remove(crypto_props, key);
+                ucx_map_sstr_put(crypto_props, key, property);
+                if(existing_prop) {
+                    // TODO: free existing_prop
+                }  
+                free(key.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;
+                data->set = ucx_list_prepend_a(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) {
+        UcxBuffer *request = create_proppatch_request(data);
+        UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+        //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;
+        }
+        
+        ucx_buffer_free(request);
+        ucx_buffer_free(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;
+    
+    UcxBuffer *response = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    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;
+    }
+    
+    ucx_buffer_free(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 = 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
+            UcxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0);
+            int ret = dav_propfind(sn, res, rqbuf);
+            ucx_buffer_free(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));
+    
+    UcxBuffer *request = create_lock_request();
+    UcxBuffer *response = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+    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);
+    
+    ucx_buffer_free(request);
+    
+    long status = 0;
+    curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+    if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+        LockDiscovery lock;
+        if(parse_lock_response(sn, response, &lock)) {
+            sn->error = DAV_ERROR;
+            ucx_buffer_free(response);
+            return -1;
+        }
+        ucx_buffer_free(response);
+        
+        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);
+        ucx_buffer_free(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;
+    }
+    
+    UcxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
+    UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    
+    util_set_url(sn, href);
+    // TODO: lock
+    CURLcode ret = do_proppatch_request(sn, NULL, request, response);
+    ucx_buffer_free(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;   
+        ucx_buffer_free(response);
+        return 0;
+    } else {
+        dav_session_set_error(sn, ret, status);
+        ucx_buffer_free(response);
+        return 1;
+    }
+}
+
+/* ----------------------------- crypto-prop  ----------------------------- */
+
+DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties) {
+    if(!sn->key) {
+        return NULL;
+    }
+    
+    UcxBuffer *content = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND);
+    
+    // create an xml document containing all properties
+    UcxMap *nsmap = ucx_map_new(8);
+    ucx_map_cstr_put(nsmap, "DAV:", strdup("D"));
+    
+    ucx_buffer_puts(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    ucx_buffer_puts(content, "<D:prop xmlns:D=\"DAV:\">\n");
+    
+    UcxMapIterator i = ucx_map_iterator(properties);
+    DavProperty *prop;
+    UCX_MAP_FOREACH(key, 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, (write_func)ucx_buffer_write, nsmap, &pnode);
+        ucx_buffer_putc(content, '\n');
+    }
+    
+    ucx_buffer_puts(content, "</D:prop>");
+    
+    ucx_map_free_content(nsmap, (ucx_destructor)free);
+    ucx_map_free(nsmap);
+    
+    // encrypt xml document
+    char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
+    ucx_buffer_free(content);
+    
+    DavXmlNode *ret = NULL;
+    if(crypto_prop_content) {
+        ret = dav_text_node(sn, crypto_prop_content);
+        free(crypto_prop_content);
+    }    
+    return ret;
+}
+
+UcxMap* 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);
+}
+
+UcxMap* 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
+    UcxMap *map = ucx_map_new(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;
+            
+            sstr_t key = dav_property_key(property->ns->name, property->name);
+            ucx_map_sstr_put(map, key, property);
+            free(key.ptr);
+        }
+        n = n->next;
+    }
+    
+    xmlFreeDoc(doc);
+    if(map->count == 0) {
+        ucx_map_free(map);
+        return NULL;
+    }
+    return map;
+}
diff --git a/libidav/resource.h b/libidav/resource.h
new file mode 100644 (file)
index 0000000..5cce0b7
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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 <ucx/string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavResourceData   DavResourceData;
+
+struct DavResourceData {
+    UcxMap  *properties;
+    UcxList *set;
+    UcxList *remove;
+    UcxList *crypto_set;
+    UcxList *crypto_remove;
+    
+    /*
+     * properties encapsulated in a crypto-prop property or NULL
+     */
+    UcxMap *crypto_properties;
+    
+    /*
+     * char* or stream
+     */
+    void      *content;
+    /*
+     * if NULL, content is a char*
+     */
+    read_func read;
+    /*
+     * curl seek func
+     */
+    dav_seek_func seek;
+    /*
+     * content length
+     */
+    size_t    length;
+    
+    /*
+     * sha256 content hash
+     */
+    char hash[32];
+};
+
+DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href);
+
+void resource_free_properties(DavSession *sn, UcxMap *properties);
+
+void resource_set_href(DavResource *res, sstr_t href);
+
+void resource_set_info(DavResource *res, 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, UcxMap *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, UcxKey key);
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key);
+void resource_add_child(DavResource *parent, DavResource *child);
+void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr);
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash);
+
+sstr_t dav_property_key_a(UcxAllocator *a, const char *ns, const char *name);
+
+DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties);
+UcxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node);
+UcxMap* 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..9f57fa4
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+ * 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 <ucx/buffer.h>
+#include <ucx/utils.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;
+    }
+    sstr_t url = sstr(base_url);
+    if(url.length == 0) {
+        return NULL;
+    }
+    DavSession *sn = malloc(sizeof(DavSession));
+    memset(sn, 0, sizeof(DavSession));
+    sn->mp = ucx_mempool_new(DAV_SESSION_MEMPOOL_SIZE);
+    sn->pathcache = ucx_map_new_a(sn->mp->allocator, 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);
+    
+    // create lock manager
+    DavLockManager *locks = ucx_mempool_malloc(sn->mp, sizeof(DavLockManager));
+    locks->resource_locks = ucx_map_new_a(sn->mp->allocator, 16);
+    locks->collection_locks = NULL;
+    sn->locks = locks;
+
+    // set proxy
+    DavProxy *proxy = sstrprefix(url, S("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
+    context->sessions = ucx_list_append(context->sessions, 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;
+}
+
+void dav_session_set_auth(DavSession *sn, char *user, char *password) {
+    if(user && password) {
+        size_t ulen = strlen(user);
+        size_t plen = strlen(password);
+        size_t upwdlen = ulen + plen + 2;
+        char *upwdbuf = malloc(upwdlen);
+        snprintf(upwdbuf, upwdlen, "%s:%s", user, password);
+        curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
+        free(upwdbuf);
+    }
+}
+
+void dav_session_set_baseurl(DavSession *sn, char *base_url) {
+    if(sn->base_url) {
+        ucx_mempool_free(sn->mp, sn->base_url);
+    }
+    
+    sstr_t url = sstr(base_url);
+    if(url.ptr[url.length - 1] == '/') {
+        sstr_t url = sstrdup_a(sn->mp->allocator, sstr(base_url));
+        sn->base_url = url.ptr;
+    } else {
+        char *url_str = ucx_mempool_malloc(sn->mp, 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, UcxBuffer *request, UcxBuffer *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 && http_status == 401 && sn->auth_prompt) {
+        if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
+            if(request) {
+                ucx_buffer_seek(request, 0, SEEK_SET);
+            }
+            if(response) {
+                ucx_buffer_seek(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
+    UcxList *sessions = sn->context->sessions;
+    ssize_t i = ucx_list_find(sessions, sn, ucx_cmp_ptr, NULL);
+    if(i >= 0)  {
+        UcxList *elm = ucx_list_get(sessions, i);
+        if(elm) {
+            sn->context->sessions = ucx_list_remove(sessions, elm);
+        }
+    }
+    
+    ucx_mempool_destroy(sn->mp);
+    curl_easy_cleanup(sn->handle);
+    free(sn);
+}
+
+
+void* dav_session_malloc(DavSession *sn, size_t size) {
+    return ucx_mempool_malloc(sn->mp, size);
+}
+
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
+    return ucx_mempool_calloc(sn->mp, nelm, size);
+}
+
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
+    return ucx_mempool_realloc(sn->mp, ptr, size);
+}
+
+void  dav_session_free(DavSession *sn, void *ptr) {
+    ucx_mempool_free(sn->mp, ptr);
+}
+
+char* dav_session_strdup(DavSession *sn, const char *str) {
+    return sstrdup_a(sn->mp->allocator, sstr((char*)str)).ptr;
+}
+
+
+char* dav_session_create_plain_href(DavSession *sn, 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, char *path) {
+    if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
+        sstr_t p = sstr(path);
+        UcxBuffer *href = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
+        UcxBuffer *pbuf = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
+        int start = 0;
+        int begin = 0;
+        
+        // check path cache
+        char *cp = strdup(path);
+        //printf("cp: %s\n", cp);
+        while(strlen(cp) > 1) {
+            char *cached = ucx_map_cstr_get(sn->pathcache, cp);
+            if(cached) {
+                start = strlen(cp);
+                begin = start;
+                ucx_buffer_puts(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
+            ucx_buffer_puts(href, util_url_path(sn->base_url));
+        }
+        
+        // create resource for name lookup
+        sstr_t rp = sstrdup(sstrn(path, start));
+        DavResource *root = dav_resource_new(sn, rp.ptr);
+        free(rp.ptr);
+        resource_set_href(root, sstrn(href->space, href->pos));
+        
+        // create request buffer for propfind requests
+        UcxBuffer *rqbuf = create_basic_propfind_request();
+        
+        sstr_t remaining = sstrsubs(p, start);
+        ssize_t nelm = 0;
+        sstr_t *elms = sstrsplit(remaining, S("/"), &nelm);
+        DavResource *res = root;
+        ucx_buffer_puts(pbuf, res->path);
+        // iterate over all remaining path elements
+        for(int i=0;i<nelm;i++) {
+            sstr_t elm = elms[i];
+            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] != '/') {
+                        ucx_buffer_putc(href, '/');
+                    }
+                    ucx_buffer_putc(pbuf, '/');
+                }
+                // add last path/href to the cache
+                sstr_t pp = sstrn(pbuf->space, pbuf->size);
+                sstr_t hh = sstrn(href->space, href->size);
+                dav_session_cache_path(sn, pp, hh);
+                
+                ucx_buffer_write(elm.ptr, 1, elm.length, pbuf);
+                if(child) {
+                    // href is already URL encoded, so don't encode again
+                    ucx_buffer_puts(href, util_resource_name(child->href));
+                    res = child;
+                } else if(DAV_ENCRYPT_NAME(sn)) {
+                    char *random_name = util_random_str();
+                    ucx_buffer_puts(href, random_name);
+                    free(random_name);
+                } else {
+                    // path is not URL encoded, so we have to do this here
+                    scstr_t resname = scstr(util_resource_name(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);
+                        ucx_buffer_write(esc, 1, strlen(esc), href);
+                        ucx_buffer_putc(href, '/');
+                        curl_free(esc);
+                    } else  {
+                        char *esc = curl_easy_escape(sn->handle,
+                                resname.ptr, resname.length);
+                        ucx_buffer_write(esc, 1, strlen(esc), href);
+                        curl_free(esc);
+                    }
+                }
+            }
+            
+            // cleanup
+            free(elm.ptr);
+        }
+        free(elms);
+        
+        // if necessary add a path separator
+        if(p.ptr[p.length-1] == '/') {
+            if(href->space[href->pos-1] != '/') {
+                ucx_buffer_putc(href, '/');
+            }
+            ucx_buffer_putc(pbuf, '/');
+        }
+        // add the final path to the cache
+        sstr_t pp = sstrn(pbuf->space, pbuf->size);
+        sstr_t hh = sstrn(href->space, href->size);
+        dav_session_cache_path(sn, pp, hh);
+        
+        sstr_t href_str = sstrdup_a(
+                sn->mp->allocator,
+                sstrn(href->space,
+                href->size));
+        
+        // cleanup
+        dav_resource_free_all(root);
+        ucx_buffer_free(rqbuf);
+        ucx_buffer_free(pbuf);
+        ucx_buffer_free(href);
+        
+        return href_str.ptr;
+    } else {
+        return dav_session_create_plain_href(sn, path);
+    }
+}
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, 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, sstr_t path, sstr_t href) {
+    char *elm = ucx_map_sstr_get(sn->pathcache, path);
+    if(!elm) {
+        href = sstrdup_a(sn->mp->allocator, href);
+        ucx_map_sstr_put(sn->pathcache, path, href.ptr);
+    }
+}
+
+
+DavLock* dav_create_lock(DavSession *sn, 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);
+}
+
+int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock) {
+    DavLockManager *locks = sn->locks;
+    if(ucx_map_cstr_get(locks->resource_locks, path)) {
+        return -1;
+    }
+    
+    ucx_map_cstr_put(locks->resource_locks, path, lock);
+    return 0;
+}
+
+static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) {
+    UcxList *next = elm->next;
+    if(next) {
+        next->prev = newelm;
+        newelm->next = next;
+    }
+    newelm->prev = elm;
+    elm->next = newelm;
+}
+
+int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock) {
+    DavLockManager *locks = sn->locks;
+    if(!locks->collection_locks) {
+        locks->collection_locks = ucx_list_append_a(
+                sn->mp->allocator,
+                NULL,
+                lock);
+        lock->path = dav_session_strdup(sn, path);
+        return 0;
+    }
+    
+    UcxList *elm = locks->collection_locks;
+    for(;;) {
+        DavLock *l = elm->data;
+        int cmp = strcmp(path, l->path);
+        if(cmp > 0) {
+            UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
+            lock->path = dav_session_strdup(sn, path);
+            insert_lock(sn, elm, newelm);
+        } else if(cmp == 0) {
+            return -1;
+        }
+        
+        if(elm->next) {
+            elm = elm->next;
+        } else {
+            UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
+            lock->path = dav_session_strdup(sn, path);
+            ucx_list_concat(elm, newelm);
+            break;
+        }
+    }
+    
+    return 0;
+}
+
+DavLock* dav_get_lock(DavSession *sn, char *path) {
+    DavLockManager *locks = sn->locks;
+    
+    DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path);
+    if(lock) {
+        return lock;
+    }
+    
+    sstr_t p = sstr(path);
+    UCX_FOREACH(elm, locks->collection_locks) {
+        DavLock *cl = elm->data;
+        int cmd = strcmp(path, cl->path);
+        if(cmd == 0) {
+            return cl;
+        } else if(sstrprefix(p, sstr(cl->path)))  {
+            return cl;
+        } else if(cmd > 0) {
+            break;
+        }
+    }
+    
+    return NULL;
+}
+
+void dav_remove_lock(DavSession *sn, char *path, DavLock *lock) {
+    DavLockManager *locks = sn->locks;
+    
+    if(ucx_map_cstr_remove(locks->resource_locks, path)) {
+        return;
+    }
+    
+    UcxList *rm = NULL;
+    UCX_FOREACH(elm, locks->collection_locks) {
+        DavLock *cl = elm->data;
+        if(cl == lock) {
+            rm = elm;
+            break;
+        }
+    }
+    
+    if(rm) {
+        locks->collection_locks = ucx_list_remove_a(
+                sn->mp->allocator,
+                locks->collection_locks,
+                rm);
+    }
+}
diff --git a/libidav/session.h b/libidav/session.h
new file mode 100644 (file)
index 0000000..2ea2b76
--- /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 <ucx/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 {
+    char *path;
+    char *token;
+    
+} DavLock;
+
+typedef struct DavLockManager {
+    UcxMap  *resource_locks;
+    UcxList *collection_locks;
+} DavLockManager;
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status);
+CURLcode dav_session_curl_perform_buf(DavSession *sn, UcxBuffer *request, UcxBuffer *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, char *path);
+
+char* dav_session_get_href(DavSession *sn, char *path);
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name);
+
+void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href);
+
+
+DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout);
+void dav_destroy_lock(DavSession *sn, DavLock *lock);
+
+int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock);
+int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock);
+
+DavLock* dav_get_lock(DavSession *sn, char *path);
+void dav_remove_lock(DavSession *sn, 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..6ca83bd
--- /dev/null
@@ -0,0 +1,1161 @@
+/*
+ * 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 <ucx/string.h>
+#include <ucx/buffer.h>
+#include <ucx/utils.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 <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(sstr_t 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
+    sstr_t date = sstrtrim(sstr(iso8601str));
+
+    sstr_t time = sstrchr(date, 'T');
+    if(time.length == 0) {
+        return 0;
+    }
+    date.length = time.ptr - date.ptr;
+    time.ptr++; time.length--;
+    
+    sstr_t tzinfo;
+    if((tzinfo = sstrchr(time, 'Z')).length > 0 ||
+        (tzinfo = sstrchr(time, '+')).length > 0 ||
+        (tzinfo = sstrchr(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
+    sstr_t frac;
+    if((frac = sstrchr(time, '.')).length > 0 ||
+        (frac = sstrchr(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(!sstrcmp(tzinfo, S("Z"))) {
+#ifdef __FreeBSD__
+        return timegm(&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);
+#ifdef __FreeBSD__
+            return timegm(&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) {
+    char *end;
+    errno = 0;
+    uint64_t val = strtoull(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_strtoint(const char *str, int64_t *value) {
+    char *end;
+    errno = 0;
+    int64_t val = strtoll(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_szstrtouint(const char *str, uint64_t *value) {
+    char *end;
+    errno = 0;
+    size_t len = strlen(str);
+    uint64_t val = strtoull(str, &end, 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;
+    }
+}
+
+char* util_url_base_s(sstr_t url) {
+    size_t i = 0;
+    if(url.length > 0) {
+        int slmax;
+        if(sstrprefix(url, SC("http://"))) {
+            slmax = 3;
+        } else if(sstrprefix(url, SC("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;
+                }
+            }
+        }
+    }
+    sstr_t server = sstrsubsl(url, 0, i);
+    return sstrdup(server).ptr;
+}
+
+char* util_url_base(char *url) {
+    return util_url_base_s(sstr(url));
+}
+
+char* util_url_path(char *url) {
+    char *path = NULL;
+    size_t len = strlen(url);
+    int slashcount = 0;
+    int slmax;
+    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+        slmax = 3;
+    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+        slmax = 3;
+    } else {
+        slmax = 1;
+    }
+    char c;
+    for(int i=0;i<len;i++) {
+        c = url[i];
+        if(c == '/') {
+            slashcount++;
+            if(slashcount == slmax) {
+                path = url + i;
+                break;
+            }
+        }
+    } 
+    if(!path) {
+        path = url + len; // empty string
+    }
+    return path;
+}
+
+char* util_url_decode(DavSession *sn, 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) {
+    
+    sstr_t sbuffer = sstrn(buffer, size*nitems);
+    
+    UcxMap *map = (UcxMap*) data;
+    
+    // if we get a status line, clear the map and exit
+    if(sstrprefix(sbuffer, S("HTTP/"))) {
+        ucx_map_free_content(map, free);
+        ucx_map_clear(map);
+        return size*nitems;
+    }
+    
+    // if we get the terminating CRLF, just exit
+    if(!sstrcmp(sbuffer, S("\r\n"))) {
+        return 2;
+    }
+    
+    sstr_t key = sbuffer;
+    sstr_t value = sstrchr(sbuffer, ':');
+    
+    if(value.length == 0) {
+        return 0; // invalid header line
+    }
+    
+    key.length = value.ptr - key.ptr;
+    value.ptr++; value.length--;
+    
+    key = sstrlower(sstrtrim(key));
+    value = sstrdup(sstrtrim(value));
+        
+    ucx_map_sstr_put(map, key, value.ptr);
+    
+    free(key.ptr);
+    
+    return sbuffer.length;
+}
+
+int util_path_isrelated(const char *path1, const char *path2) {
+    scstr_t p1 = scstr(path1);
+    scstr_t p2 = scstr(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(!sstrcmp(p1, p2)) {
+        return 1;
+    }
+    
+    if(sstrprefix(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);
+    UcxBuffer *buf = ucx_buffer_new(NULL, len+1, UCX_BUFFER_AUTOEXTEND);
+    
+    if(path[0] == '/') {
+        ucx_buffer_putc(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) {
+                scstr_t seg = scstrn(seg_ptr, seg_len);
+                if(!sstrcmp(seg, SC(".."))) {
+                    for(int j=buf->pos;j>=0;j--) {
+                        char t = buf->space[j];
+                        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(!sstrcmp(seg, SC("."))) {
+                    // ignore
+                } else {
+                    if(add_separator) {
+                        ucx_buffer_putc(buf, PATH_SEPARATOR);
+                    }
+                    ucx_buffer_write(seg_ptr, 1, seg_len, buf);
+                    add_separator = 1;
+                }
+            }
+            
+            seg_start = i;
+        }
+    }
+    
+    ucx_buffer_putc(buf, 0);
+    
+    
+    char *space = buf->space;
+    buf->flags = 0; // disable autofree
+    ucx_buffer_free(buf);
+    return 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 i;
+    size_t last_dir = 0;
+    for(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;
+    UcxBuffer *out = NULL;
+    if(last_dir+1 < base_len) {
+        // base is deeper than the link root, we have to go backwards
+        int dircount = 0;
+        for(int i=last_dir+1;i<base_len;i++) {
+            if(IS_PATH_SEPARATOR(base[i])) {
+                dircount++;
+            }
+        }
+        
+        out = ucx_buffer_new(NULL, dircount*3+path_len-last_dir, UCX_BUFFER_AUTOEXTEND);
+        
+        for(int i=0;i<dircount;i++) {
+            ucx_buffer_puts(out, "../");
+        }
+    } else {
+        out = ucx_buffer_new(NULL, 1024, path_len - last_dir);
+    }
+    
+    ucx_buffer_puts(out, abspath + last_dir + 1);
+    ucx_buffer_putc(out, 0);
+    out->flags = 0;
+    ret = out->space;
+    ucx_buffer_free(out);
+    
+    return ret;
+}
+
+#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, UcxMap* 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);
+    }
+}
+
+char* util_resource_name(char *url) {
+    sstr_t urlstr = sstr(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    sstr_t resname = sstrrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+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) {
+    sstr_t base = sstr((char*)url_base);
+    sstr_t path;
+    if(p) {
+        path = sstr((char*)p);
+    } else {
+        path = sstrn("", 0);
+    }
+    
+    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;
+        }
+    }
+    
+    sstr_t url;
+    if(add_separator) {
+        url = sstrcat(3, base, sstr("/"), path);
+    } else {
+        url = sstrcat(2, base, path);
+    }
+    
+    return url.ptr;
+}
+
+char* util_get_url(DavSession *sn, const char *href) {
+    scstr_t base = scstr(sn->base_url);
+    scstr_t href_str = scstr(href);
+    
+    char *base_path = util_url_path(sn->base_url);
+    base.length -= strlen(base_path);
+    
+    sstr_t url = sstrcat(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, char *path) {
+    char *space = malloc(256);
+    UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND);
+    
+    // add base url
+    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
+    // remove trailing slash
+    ucx_buffer_seek(url, -1, SEEK_CUR);
+    
+    sstr_t p = sstr(path);
+    ssize_t ntk = 0;
+    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
+    
+    for(int i=0;i<ntk;i++) {
+        sstr_t node = tks[i];
+        if(node.length > 0) {
+            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+            ucx_buffer_putc(url, '/');
+            ucx_buffer_write(esc, 1, strlen(esc), url);
+            curl_free(esc);
+        }
+        free(node.ptr);
+    }
+    free(tks);
+    if(path[p.length-1] == '/') {
+        ucx_buffer_putc(url, '/');
+    }
+    ucx_buffer_putc(url, 0);
+    
+    space = url->space;
+    ucx_buffer_free(url);
+    
+    return space;
+}
+
+char* util_parent_path(const char *path) {
+    char *name = util_resource_name((char*)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) {
+    char *str = malloc(16);
+    uint64_t size = contentlength;
+    
+    if(iscollection) {
+        str[0] = '\0'; // currently no information for collections
+    } else if(size < 0x400) {
+        snprintf(str, 16, "%" PRIu64 " bytes", size);
+    } else if(size < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.1f KiB", s);
+        } else {
+            snprintf(str, 16, "%.0f KiB", s);
+        }
+    } else if(size < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.1f MiB", s);
+        } else {
+            size /= 0x100000;
+            snprintf(str, 16, "%.0f MiB", s);
+        }
+    } else if(size < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.1f GiB", 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(size < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.1f TiB", 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, char *str, char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        sstr_t err = ucx_sprintf("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, 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, char *str, char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        sstr_t err = ucx_sprintf("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, 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';
+    
+    sstr_t t = S(
+            "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
+ */
+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;
+    }
+}
+
+sstr_t util_readline(FILE *stream) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    
+    int c;
+    while((c = fgetc(stream)) != EOF) {
+        if(c == '\n') {
+            break;
+        }
+        ucx_buffer_putc(buf, c);
+    }
+    
+    sstr_t str = sstrdup(sstrtrim(sstrn(buf->space, buf->size)));
+    ucx_buffer_free(buf);
+    return str;
+}
+
+char* util_password_input(char *prompt) {
+    fprintf(stderr, "%s", prompt);
+    fflush(stderr);
+    
+#ifndef _WIN32
+    // hide terminal input
+    struct termios oflags, nflags;
+    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
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    int c = 0;
+    while((c = getpasswordchar()) != EOF) {
+        if(c == '\n' || c == '\r') {
+            break;
+        }
+        ucx_buffer_putc(buf, c);
+    }
+    ucx_buffer_putc(buf, 0);
+    fflush(stdin);
+    
+#ifndef _WIN32
+    // restore terminal settings
+    if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
+        perror("tcsetattr");
+    }
+#endif
+    
+    char *str = buf->space;
+    free(buf); // only free the UcxBuffer struct
+    return str;
+}
+
+
+char* util_hexstr(const unsigned char *data, size_t len) {
+    size_t buflen = 2*len + 4;
+    UcxBuffer *buf = ucx_buffer_new(malloc(buflen), buflen + 1, 0);
+    for(int i=0;i<len;i++) {
+        ucx_bprintf(buf, "%02x", data[i]);
+    }
+    ucx_buffer_putc(buf, 0);
+    char *str = buf->space;
+    ucx_buffer_free(buf);
+    return str;
+}
+
+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..f29138f
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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 <ucx/string.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <curl/curl.h>
+#include "webdav.h"
+
+#ifndef S_IRWXG
+/* if one is not defined, the others are probably also not defined */
+#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(char *url);
+char* util_url_base_s(sstr_t url);
+char* util_url_path(char *url);
+char* util_url_decode(DavSession *sn, char *url);
+char* util_resource_name(char *url);
+char* util_concat_path(const char *url_base, const char *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, UcxMap* map);
+
+char* util_path_to_url(DavSession *sn, char *path);
+char* util_parent_path(const char *path);
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength);
+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, char *str, char *key);
+char* util_encrypt_str_k(DavSession *sn, char *str, DavKey *key);
+char* util_decrypt_str(DavSession *sn, char *str, char *key);
+char* util_decrypt_str_k(DavSession *sn, char *str, DavKey *key);
+
+char* util_random_str();
+
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub);
+
+sstr_t util_readline(FILE *stream);
+char* util_password_input(char *prompt);
+
+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..197f3b6
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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));
+    
+    UcxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, sstr(properties));
+    }
+    
+    // check if the list already contains a D:version-name property
+    int add_vname = 1;
+    UCX_FOREACH(elm, proplist) {
+        DavProperty *p = elm->data;
+        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 = malloc(sizeof(DavProperty));
+        p->ns = dav_get_namespace(sn->context, "D");
+        p->name = strdup("version-name");
+        p->value = NULL;
+        proplist = ucx_list_prepend(proplist, p);
+    }
+    
+    // create a version-tree request, which is almost the same as propfind
+    UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1);
+    UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    
+    // 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
+    while(proplist) {
+        DavProperty *p = proplist->data;
+        free(p->name);
+        free(p);
+        UcxList *next = proplist->next;
+        free(proplist);
+        proplist = next;
+    }
+    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..ed8ceb2
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * 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 "ucx/buffer.h"
+#include "ucx/utils.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 = NULL;
+    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 = ucx_map_new(16);
+    if(!context->namespaces) {
+        dav_context_destroy(context);
+        return NULL;
+    }
+    context->namespaceinfo = ucx_map_new(16);
+    if(!context->namespaceinfo) {
+        dav_context_destroy(context);
+    }
+    context->keys = ucx_map_new(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
+    UcxList *elm = ctx->sessions;
+    while(elm) {
+        DavSession *sn = elm->data;
+        elm = elm->next;
+        dav_session_destroy(sn);
+    }
+    if(ctx->http_proxy) {
+        free(ctx->http_proxy);
+    }
+    if(ctx->https_proxy) {
+        free(ctx->https_proxy);
+    }
+    
+    if(ctx->namespaces) {
+        UcxMapIterator i = ucx_map_iterator(ctx->namespaces);
+        UcxKey k;
+        DavNamespace *ns;
+        UCX_MAP_FOREACH(k, ns, i) {
+            if(!ns) continue;
+            if(ns->prefix) {
+                free(ns->prefix);
+            }
+            if(ns->name) {
+                free(ns->name);
+            }
+            free(ns);
+        }
+        ucx_map_free(ctx->namespaces);
+    }
+    if(ctx->namespaceinfo) {
+        // TODO: implement
+    }
+    if(ctx->keys) {
+        UcxMapIterator i = ucx_map_iterator(ctx->keys);
+        UcxKey k;
+        DavKey *key;
+        UCX_MAP_FOREACH(k, key, i) {
+            if(!key) continue;
+            if(key->name) {
+                free(key->name);
+            }
+            if(key->data) {
+                free(key->data);
+            }
+            free(key);
+        }
+        ucx_map_free(ctx->keys);
+    }    
+    
+    free(ctx);
+}
+
+void dav_context_add_key(DavContext *context, DavKey *key) {
+    ucx_map_cstr_put(context->keys, key->name, key);
+}
+
+DavKey* dav_context_get_key(DavContext *context, char *name) {
+    if(name) {
+        return ucx_map_cstr_get(context->keys, name);
+    }
+    return NULL;
+}
+
+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);
+    char *n = strdup(name);
+    
+    int err = 0;
+    if(p && n) {
+        namespace->prefix = p;
+        namespace->name = n;
+        err = ucx_map_cstr_put(context->namespaces, prefix, namespace);
+    }
+    
+    if(err) {
+        free(namespace);
+        if(p) free(p);
+        if(n) free(n);
+    }
+    
+    return err;
+}
+
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
+    return ucx_map_cstr_get(context->namespaces, prefix);
+}
+
+DavNamespace* dav_get_namespace_s(DavContext *context, sstr_t prefix) {
+    return ucx_map_sstr_get(context->namespaces, prefix);
+}
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
+    DavNSInfo *info = ucx_map_cstr_get(context->namespaceinfo, ns);
+    if(!info) {
+        info = calloc(1, sizeof(DavNSInfo));
+        info->encrypt = encrypt;
+        ucx_map_cstr_put(context->namespaceinfo, ns, info);
+    } else {
+        info->encrypt = encrypt;
+    }
+    return 0;
+}
+
+int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
+    DavNSInfo *info = ucx_map_cstr_get(context->namespaceinfo, ns);
+    if(info) {
+        return info->encrypt;
+    }
+    return 0;
+}
+
+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 *ns = dav_get_namespace_s(
+                ctx,
+                sstrn(prefixed_name, pname-prefixed_name));
+        if(ns) {
+            pns = ns->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,
+                sstrn(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, S("D"));
+    }
+}
+
+// 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) {
+        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, char *properties) {  
+    CURL *handle = sn->handle;
+    DavResource *resource = dav_resource_new(sn, path);
+    util_set_url(sn, dav_resource_get_href(resource));
+    
+    UcxList *proplist = NULL;
+    if(properties) {
+        proplist = parse_properties_string(sn->context, sstr(properties));
+    }
+    UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0);
+    UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    
+    //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;
+    }
+    
+    ucx_buffer_free(rqbuf);
+    ucx_buffer_free(rpbuf);
+    while(proplist) {
+        DavProperty *p = proplist->data;
+        free(p->name);
+        free(p);
+        UcxList *next = proplist->next;
+        free(proplist);
+        proplist = next;
+    }
+    
+    return resource;
+}
+
+
+int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf) {
+    // clean resource properties
+    DavResourceData *data = root->data;
+    ucx_map_clear(data->properties); // TODO: free existing content
+    
+    CURL *handle = sn->handle;
+    util_set_url(sn, dav_resource_get_href(root));
+     
+    UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
+    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;
+    }
+    ucx_buffer_free(rpbuf);
+    return error;
+}
+
+UcxList* parse_properties_string(DavContext *context, sstr_t str) {
+    UcxList *proplist = NULL;
+    ssize_t nprops = 0;
+    sstr_t *props = sstrsplit(str, S(","), &nprops);
+    for(int i=0;i<nprops;i++) {
+        sstr_t s = props[i];
+        sstr_t nsname = sstrchr(s, ':');
+        if(nsname.length > 0) {
+            sstr_t nspre = sstrsubsl(s, 0, nsname.ptr - s.ptr);
+            nsname.ptr++;
+            nsname.length--;
+            
+            DavProperty *dp = malloc(sizeof(DavProperty));
+            sstr_t pre = sstrtrim(nspre);
+            dp->ns = dav_get_namespace_s(context, pre);
+            dp->name = sstrdup(nsname).ptr;
+            if(dp->ns && dp->name) {
+                proplist = ucx_list_append(proplist, dp);
+            } else {
+                free(dp->name);
+                free(dp);
+            }
+        }
+        free(s.ptr);
+    }
+    free(props);
+    return proplist;
+}
+
+DavResource* dav_query(DavSession *sn, char *query, ...) {
+    DavQLStatement *stmt = dav_parse_statement(sstr(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);
+    return result.result;
+}
+
+
+
+
diff --git a/libidav/webdav.h b/libidav/webdav.h
new file mode 100644 (file)
index 0000000..eb9a891
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * 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 <ucx/map.h>
+#include <ucx/mempool.h>
+#include <ucx/list.h>
+#include <ucx/buffer.h>
+#include <curl/curl.h>
+#include <libxml/tree.h>
+
+#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 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 *);
+
+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;
+    UcxMempool    *mp;
+    UcxMap        *pathcache;
+    DavKey        *key;
+    void          *locks;
+    uint32_t      flags;
+    DavError      error;
+    char          *errorstr;
+    
+    int(*auth_prompt)(DavSession *sn, void *userdata);
+    void *authprompt_userdata;
+    
+    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 {
+    UcxMap   *namespaces;
+    UcxMap   *namespaceinfo;
+    UcxMap   *keys;
+    UcxList  *sessions;
+    DavProxy *http_proxy;
+    DavProxy *https_proxy;
+};
+
+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_add_key(DavContext *context, DavKey *key);
+DavKey* dav_context_get_key(DavContext *context, char *name);
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *ns);
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix);
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt);
+int dav_namespace_is_encrypted(DavContext *context, const char *ns);
+
+DavSession* dav_session_new(DavContext *context, char *base_url);
+DavSession* dav_session_new_auth(
+        DavContext *context,
+        char *base_url,
+        char *user,
+        char *password);
+void dav_session_set_auth(DavSession *sn, char *user, char *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_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, char *properties);
+
+UcxList* parse_properties_string(DavContext *context, sstr_t str);
+
+DavResource* dav_query(DavSession *sn, char *query, ...);
+
+sstr_t 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, char *path);
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *name);
+DavResource* dav_resource_new_href(DavSession *sn, 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);
+void 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);
+
+// private
+int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *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, char *text);
+
+DavXmlNode* dav_copy_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..082cfba
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * 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 <ucx/utils.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;
+    }
+    
+    UcxMempool *mp = sn->mp;
+    
+    ConvXmlElm *ce = malloc(sizeof(ConvXmlElm));
+    ce->node = node;
+    ce->parent = NULL;
+    UcxList *stack = ucx_list_prepend(NULL, ce);
+    
+    DavXmlNode *ret = NULL;
+    
+    while(stack) {
+        ConvXmlElm *c = stack->data;
+        stack = ucx_list_remove(stack, stack);
+        
+        xmlNode *n = c->node;
+        DavXmlNode *prev = NULL;
+        while(n) {
+            DavXmlNode *newxn = ucx_mempool_calloc(mp, 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 = ucx_mempool_calloc(mp, 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 = malloc(sizeof(ConvXmlElm));
+                    convc->node = n->children;
+                    convc->parent = newxn;
+                    stack = ucx_list_prepend(stack, convc);
+                }
+            } else if(newxn->type == DAV_XML_TEXT) {
+                sstr_t content = sstrdup_a(mp->allocator, sstr((char*)n->content));
+                newxn->content = content.ptr;
+                newxn->contentlength = content.length;
+            }
+            
+            prev = newxn;
+            n = n->next;
+        }
+        
+        free(c);
+    }
+    
+    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, write_func writef, UcxMap *nsmap, DavXmlNode *node) {
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            char *tagend = node->children ? ">" : " />";          
+            char *prefix = NULL;
+            if(node->namespace) {
+                prefix = ucx_map_cstr_get(nsmap, node->namespace);
+                if(!prefix) {
+                    sstr_t newpre = ucx_sprintf("x%d", (int)nsmap->count+1);
+                    // TODO: fix namespace declaration
+                    //ucx_map_cstr_put(nsmap, node->namespace, newpre.ptr);
+                    prefix = newpre.ptr;
+                    ucx_fprintf(
+                            stream,
+                            writef,
+                            "<%s:%s xmlns:%s=\"%s\"",
+                            prefix,
+                            node->name,
+                            prefix,
+                            node->namespace);
+                } else {
+                    ucx_fprintf(stream, writef, "<%s:%s", prefix, node->name);
+                }
+            } else {
+                ucx_fprintf(stream, writef, "<%s", node->name);
+            }
+            
+            DavXmlAttr *attr = node->attributes;
+            while(attr) {
+                ucx_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) {
+                    ucx_fprintf(stream, writef, "</%s:%s>", prefix, node->name);
+                } else {
+                    ucx_fprintf(stream, writef, "</%s>", node->name);
+                }
+            }
+        } 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, char *text) {
+    UcxMempool *mp = sn->mp; 
+    DavXmlNode *newxn = ucx_mempool_calloc(mp, 1, sizeof(DavXmlNode));
+    newxn->type = DAV_XML_TEXT;
+    sstr_t content = sstrdup_a(mp->allocator, sstr(text));
+    newxn->content = content.ptr;
+    newxn->contentlength = content.length;
+    return newxn;
+}
+
+
+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;
+    sstr_t content = sstrdup(sstr((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 *last;
+        DavXmlAttr *end = node->attributes;
+        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..a78449f
--- /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, write_func writef, UcxMap *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..11b8e73
--- /dev/null
@@ -0,0 +1,62 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2021 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (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/libidav
+BUILD_DIRS += build/mizunara
+BUILD_DIRS += build/mizucp
+BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
+
+all: $(BUILD_DIRS) ucx ui libidav mizucp mizunara
+       make/$(PACKAGE_SCRIPT)
+
+$(BUILD_DIRS):
+       mkdir -p $@
+
+ucx: $(BUILD_DIRS) FORCE
+       cd ucx; $(MAKE)
+
+ui: $(BUILD_DIRS) FORCE
+       cd ui; $(MAKE) all
+
+libidav: $(BUILD_DIRS) FORCE
+       cd libidav; $(MAKE) all
+
+mizucp: $(BUILD_DIRS) FORCE
+       cd mizucp; $(MAKE)
+
+mizunara: $(BUILD_DIRS) ui FORCE
+       cd mizunara; $(MAKE)
+
+FORCE:
+
diff --git a/make/clang.mk b/make/clang.mk
new file mode 100644 (file)
index 0000000..93e8096
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# clang toolchain config
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
diff --git a/make/configure.vm b/make/configure.vm
new file mode 100644 (file)
index 0000000..80068be
--- /dev/null
@@ -0,0 +1,615 @@
+#!/bin/sh
+
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.name}=`${var.value}`
+#else
+${var.name}=${var.value}
+#end
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=/usr
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$PREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=
+#end
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+       echo "Cannot create tmp dir"
+       echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.isDefault()} )
+${feature.getVarName()}=on
+#end
+#end
+
+# help text
+printhelp()
+{
+       echo "Usage: $0 [OPTIONS]..."
+       cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+
+#if( $options.size() > 0 )
+Options:
+#foreach( $opt in $options )
+  --${opt.getArgument()}=${opt.getValuesString()}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+#if( $feature.default )
+  --disable-${feature.arg}
+#else
+  --enable-${feature.arg}
+#end
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments 
+#
+#set( $D = '$' )
+for ARG in $@
+do
+    case "$ARG" in
+               "--prefix="*)         PREFIX=${D}{ARG#--prefix=} ;;
+               "--exec-prefix="*)    EPREFIX=${D}{ARG#--exec-prefix=} ;;
+               "--bindir="*)         BINDIR=${D}{ARG#----bindir=} ;;
+               "--sbindir="*)        SBINDIR=${D}{ARG#--sbindir=} ;;
+               "--libdir="*)         LIBDIR=${D}{ARG#--libdir=} ;;
+               "--libexecdir="*)     LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
+               "--datadir="*)        DATADIR=${D}{ARG#--datadir=} ;;
+               "--sysconfdir="*)     SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
+               "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
+               "--localstatedir="*)  LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
+               "--includedir="*)     INCLUDEDIR=${D}{ARG#--includedir=} ;;
+               "--infodir="*)        INFODIR=${D}{ARG#--infodir=} ;;
+               "--mandir"*)          MANDIR=${D}{ARG#--mandir} ;;
+               "--help"*) printhelp; exit 1 ;;
+       #foreach( $opt in $options )
+       "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
+    #end
+       #foreach( $feature in $features )
+               "--enable-${feature.arg}") ${feature.getVarName()}=on ;;
+               "--disable-${feature.arg}") unset ${feature.getVarName()} ;;
+       #end
+               "-"*) echo "unknown option: $ARG"; exit 1 ;;
+       esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+       BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+       SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+       LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+       LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+       DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+       SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+       SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+       LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+       INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+       INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+       MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+       PLATFORM_NAME=$p
+       break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+#foreach( $var in $vars )
+${var.name}=$${var.name}
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=$PREFIX
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$EPREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=$BINDIR
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=$SBINDIR
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=$LIBDIR
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=$LIBEXECDIR
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=$DATADIR
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=$SYSCONFDIR
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=$LOCALSTATEDIR
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=$INCLUDEDIR
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=$INFODIR
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=$MANDIR
+#end
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+#foreach( $dependency in $namedDependencies )
+dependency_${dependency.name}()
+{
+    printf "checking for ${dependency.name}... "
+    #foreach( $sub in $dependency.getSubdependencies() )
+    # dependency $sub.name $sub.getPlatformString()
+    while true
+    do
+       #if( $sub.platform )
+       if isnotplatform "${sub.platform}"; then
+            break
+        fi
+       #end
+               #foreach( $not in $sub.getNotList() )
+               if isplatform "${not}"; then
+            break
+        fi
+               #end
+        #if( $sub.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+        #end
+        #foreach( $pkg in $sub.pkgconfig )
+               $PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        #foreach( $flags in $sub.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -eq 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+        #foreach( $test in $sub.tests )
+        $test > /dev/null
+        if [ $? -ne 0 ]; then
+               break
+        fi
+        #end
+               #if ( $sub.make.length() > 0 )
+               cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name         
+$sub.make
+__EOF__
+        #end
+               echo yes
+        return 0
+    done
+       
+       #end
+       echo no
+       return 1
+}
+#end
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# general dependencies
+CFLAGS=
+LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+       #if( $dependency.platform )
+    if isnotplatform "${dependency.platform}"; then
+        break
+    fi
+    #end
+       #foreach( $not in $dependency.getNotList() )
+    if isplatform "${not}"; then
+        break
+    fi
+       #end
+    while true
+    do
+        #if( $dependency.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+            ERROR=1
+            break
+        fi
+        #end
+        #foreach( $pkg in $dependency.pkgconfig )
+        printf "checking for pkg-config package $pkg.getPkgConfigParam()... "
+               $PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ]; then
+            echo no
+            ERROR=1
+            break
+        fi
+        echo yes
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        
+        #foreach( $flags in $dependency.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -ne 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            ERROR=1
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+               #if ( $dependency.make.length() > 0 )
+               cat >> $TEMP_DIR/make.mk << __EOF__
+$dependency.make
+__EOF__
+        #end
+        
+        break
+    done
+    
+    break
+done
+#end
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+       VERR=0
+       #foreach( $dep in $val.dependencies )
+       dependency_$dep
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       #end
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       #foreach( $def in $val.defines )
+               CFLAGS="$CFLAGS ${def.toFlags()}"
+       #end
+       #if( $val.hasMake() )
+       cat >> $TEMP_DIR/make.mk << __EOF__
+$val.make
+__EOF__
+       #end
+       return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+#foreach( $target in $targets )
+#if ( $target.name )
+# Target: $target.name
+#else
+# Target
+#end
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+dependency_$dependency
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+       ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+       # check dependency
+       dependency_$dependency
+       if [ $? -ne 0 ]; then
+               # "auto" features can fail and are just disabled in this case
+               if [ $${feature.getVarName()} != "auto" ]; then
+                       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+                       ERROR=1
+               fi
+       fi
+#end
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z ${D}${opt.getVarName()} ]; then
+       SAVED_ERROR=$ERROR
+       SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+       ERROR=0
+       while true
+       do
+               #foreach( $optdef in $opt.defaults )
+               #if( $optdef.platform )
+               if isplatform "$optdef.platform"; then
+               #end
+               $optdef.func
+               if [ $? -eq 0 ]; then
+                       echo "  ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               #if( $optdef.platform )
+               fi
+               #end
+               #end
+               break
+       done
+       if [ $ERROR -ne 0 ]; then
+               SAVED_ERROR=1
+       fi
+       ERROR=$SAVED_ERROR
+       DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+       if false; then
+               false
+       #foreach( $optval in $opt.values )
+       elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then
+               echo "  ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options
+               $optval.func
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       #end
+       fi
+fi
+#end
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "${target.getCFlags()}  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#end
+if [ $ERROR -ne 0 ]; then
+       echo
+       echo "Error: Unresolved dependencies"
+       echo $DEPENDENCIES_FAILED
+       rm -Rf $TEMP_DIR
+       exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+#if ( $options.size() > 0 )
+echo "Options:"
+cat $TEMP_DIR/options
+#end
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
diff --git a/make/gcc.mk b/make/gcc.mk
new file mode 100644 (file)
index 0000000..624bdf1
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# gcc toolchain config
+#
+
+CFLAGS = 
+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_unix.sh b/make/package_unix.sh
new file mode 100755 (executable)
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..cc20939
--- /dev/null
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+       <!--
+       <dependency name="gtk4">
+               <pkgconfig>gtk+-4.0</pkgconfig>
+               <cflags>-DUI_GTK3</cflags>
+               <ldflags>-lpthread</ldflags>
+       </dependency>
+       -->
+       <dependency name="gtk3">
+               <pkgconfig>gtk+-3.0</pkgconfig>
+               <cflags>-DUI_GTK3</cflags>
+               <ldflags>-lpthread</ldflags>
+       </dependency>
+       
+       <dependency name="motif">
+               <cflags>-DUI_MOTIF</cflags>
+               <ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+       </dependency>
+       
+       <dependency name="curl" platform="windows">
+               <cflags>-I/mingw/include</cflags>
+               <ldflags>-lcurl</ldflags>
+       </dependency>
+       <dependency name="curl" platform="macos">
+               <cflags type="exec">curl-config --cflags</cflags>
+               <ldflags type="exec">curl-config --ldflags</ldflags>
+       </dependency>
+       <dependency name="curl">
+               <pkgconfig>libcurl</pkgconfig>
+       </dependency>
+       <dependency name="curl">
+               <test>which curl-config</test>
+               <cflags type="exec">curl-config --cflags</cflags>
+               <ldflags type="exec">curl-config --ldflags</ldflags>
+       </dependency>
+       
+       <dependency name="libxml2" platform="windows">
+               <cflags type="exec">xml2-config --cflags</cflags>
+               <ldflags type="exec">xml2-config --libs</ldflags>
+       </dependency>
+       <dependency name="libxml2" platform="macos">
+               <cflags type="exec">xml2-config --cflags</cflags>
+               <ldflags type="exec">xml2-config --libs</ldflags>
+       </dependency>
+       <dependency name="libxml2">
+               <pkgconfig>libxml-2.0</pkgconfig>
+       </dependency>
+       <dependency name="libxml2">
+               <cflags type="exec">xml2-config --cflags</cflags>
+               <ldflags type="exec">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 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>
+               <ldflags>-lpthread</ldflags>
+       </dependency>
+       
+       <dependency platform="bsd" not="macos">
+               <cflags>-I/usr/local/include</cflags>
+               <ldflags>-L/usr/local/lib</ldflags>
+       </dependency>
+       
+       <target name="tk">
+               <option arg="toolkit">
+                       <!--
+                       <value str="gtk4">
+                               <dependencies>gtk4</dependencies>
+                               <make>TOOLKIT = gtk</make>
+                               <make>GTKOBJ = draw_cairo.o</make>
+                       </value>
+                       -->
+                       <value str="gtk3">
+                               <dependencies>gtk3</dependencies>
+                               <make>TOOLKIT = gtk</make>
+                               <make>GTKOBJ = draw_cairo.o</make>
+                       </value>
+                       <!--
+                       <value str="gtk2">
+                               <dependencies>gtk2</dependencies>
+                               <make>TOOLKIT = gtk</make>
+                               <make>GTKOBJ = draw_cairo.o</make>
+                       </value>
+                       <value str="gtk2legacy">
+                               <dependencies>gtk2legacy</dependencies>
+                               <make>TOOLKIT = gtk</make>
+                               <make>GTKOBJ = draw_gdk.o</make>
+                       </value>
+                       <value str="qt5">
+                               <dependencies>qt5</dependencies>
+                               <make>TOOLKIT = qt</make>
+                               <make>LD = $(CXX)</make>
+                       </value>
+                       <value str="qt4">
+                               <dependencies>qt4</dependencies>
+                               <make>TOOLKIT = qt</make>
+                               <make>LD = $(CXX)</make>
+                       </value>
+                       -->
+                       <value str="motif">
+                               <dependencies>motif</dependencies>
+                               <make>TOOLKIT = motif</make>
+                       </value>
+                       <!--
+                       <default value="wpf" platform="windows" />
+                       <default value="cocoa" platform="macos" />
+                       -->
+                       <default value="motif" />
+                       <default value="gtk3" />
+                       <!--
+                       <default value="qt5" />
+                       <default value="gtk2" />
+                       <default value="qt4" />
+                       -->
+               </option>
+       </target>
+       
+       <target name="dav">
+               <dependencies>curl,libxml2,openssl</dependencies>
+       </target>
+</project>
+
diff --git a/make/suncc.mk b/make/suncc.mk
new file mode 100644 (file)
index 0000000..a97fe3c
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# suncc toolchain
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
diff --git a/make/toolchain.sh b/make/toolchain.sh
new file mode 100644 (file)
index 0000000..8e9a529
--- /dev/null
@@ -0,0 +1,181 @@
+#!/bin/sh
+#
+# toolchain detection
+#
+
+C_COMPILERS="cc gcc clang suncc"
+CPP_COMPILERS="CC g++ clang++ sunCC"
+unset CC_ARG_CHECKED
+unset TOOLCHAIN_DETECTION_ERROR
+unset TOOLCHAIN_NAME
+
+check_c_compiler()
+{
+       cat > $TEMP_DIR/test.c << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+       printf("clang\n");
+#elif defined(__GNUC__)
+       printf("gcc\n");
+#elif defined(__sun)
+       printf("suncc\n");
+#else
+       printf("unknown\n");
+#endif
+       return 0;
+}
+__EOF__
+       rm -f $TEMP_DIR/checkcc
+       $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
+       
+       if [ $? -ne 0 ]; then
+               return 1
+       fi
+       return 0
+}
+
+check_cpp_compiler()
+{
+       cat > $TEMP_DIR/test.cpp << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+       std::cout << "clang" << std::endl;
+#elif defined(__GNUC__)
+       std::cout << "gcc" << std::endl;
+#elif defined(__sun)
+       std::cout << "suncc" << std::endl;
+#else
+       std::cout << "unknown" << std::endl;
+#endif
+       return 0;
+}
+__EOF__
+       rm -f $TEMP_DIR/checkcc
+       $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
+       
+       if [ $? -ne 0 ]; then
+               return 1
+       fi
+       return 0
+}
+
+printf "detect C compiler... "
+
+for COMP in $C_COMPILERS
+do
+       check_c_compiler $COMP
+       if [ $? -ne 0 ]; then
+               if [ ! -z "$CC" ]; then
+                       if [ $COMP = $CC ]; then
+                               echo "$CC is not a working C Compiler"
+                               TOOLCHAIN_DETECTION_ERROR="error"
+                               break
+                       fi
+               fi
+       else
+               TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+               USE_TOOLCHAIN=$TOOLCHAIN_NAME
+               if [ $COMP = "cc" ]; then
+                       # we have found a working compiler, but in case
+                       # the compiler is gcc or clang, we try to use
+                       # these commands and not 'cc'
+                       TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+                       if [ $TOOLCHAIN_NAME = "gcc" ]; then
+                               check_c_compiler "gcc"
+                               if [ $? -eq 0 ]; then
+                                       COMP=gcc
+                                       USE_TOOLCHAIN="gcc"
+                               fi
+                       fi
+                       if [ $TOOLCHAIN_NAME = "clang" ]; then
+                               check_c_compiler "clang"
+                               if [ $? -eq 0 ]; then
+                                       COMP=clang
+                                       USE_TOOLCHAIN="clang"
+                               fi
+                       fi
+               fi
+               
+               TOOLCHAIN_NAME=$USE_TOOLCHAIN
+               TOOLCHAIN_CC=$COMP
+               echo $COMP
+               break
+       fi
+done
+if [ -z $TOOLCHAIN_CC ]; then
+       echo "not found"
+fi
+
+printf "detect C++ compiler... "
+
+for COMP in $CPP_COMPILERS
+do
+       check_cpp_compiler $COMP
+       if [ $? -ne 0 ]; then
+               if [ ! -z "$CXX" ]; then
+                       if [ $COMP = $CXX ]; then
+                               echo "$CC is not a working C++ Compiler"
+                               TOOLCHAIN_DETECTION_ERROR="error"
+                               break
+                       fi
+               fi
+       else
+               if [ $COMP = "CC" ]; then
+                       # we have found a working compiler, but in case
+                       # the compiler is gcc or clang, we try to use
+                       # these commands and not 'cc'
+                       TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+                       USE_TOOLCHAIN=$TOOLCHAIN_NAME
+                       if [ $TOOLCHAIN_NAME = "gcc" ]; then
+                               check_cpp_compiler "g++"
+                               if [ $? -eq 0 ]; then
+                                  COMP=g++
+                                  USE_TOOLCHAIN="gcc"
+                               fi
+                       fi
+                       if [ $TOOLCHAIN_NAME = "clang" ]; then
+                               check_cpp_compiler "clang++"
+                               if [ $? -eq 0 ]; then
+                                  COMP=clang++
+                                  USE_TOOLCHAIN="clang"
+                               fi
+                       fi
+               fi
+               
+               TOOLCHAIN_NAME=$USE_TOOLCHAIN
+               TOOLCHAIN_CXX=$COMP
+               echo $COMP
+               break
+       fi
+done
+if [ -z $TOOLCHAIN_CXX ]; then
+       echo "not found"
+fi
+
+TOOLCHAIN_LD=$TOOLCHAIN_CC
+
+if [ -z "$TOOLCHAIN_NAME" ]; then
+       TOOLCHAIN_DETECTION_ERROR="error"
+else
+       cat >> $TEMP_DIR/config.mk << __EOF__
+# toolchain
+__EOF__
+       echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
+       if [ ! -z "$TOOLCHAIN_CXX" ]; then
+               echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
+       fi
+       echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
+       echo >> $TEMP_DIR/config.mk
+       
+       cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
+       if [ $? -eq 0 ]; then 
+               echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
+       else
+               echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
+               echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
+       fi
+fi
diff --git a/make/windows.mk b/make/windows.mk
new file mode 100644 (file)
index 0000000..2f3bc72
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS  = -std=gnu99
+LDFLAGS = 
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = obj
+LIB_EXT = lib
+APP_EXT = .exe
+
diff --git a/mizucp/Makefile b/mizucp/Makefile
new file mode 100644 (file)
index 0000000..a8a1898
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ..
+include $(BUILD_ROOT)/config.mk
+
+CFLAGS += -I../ucx -I..
+
+SRC = main.c
+
+OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/mizucp/%.$(OBJ_EXT))
+
+all: $(BUILD_ROOT)/build/bin/mizucp
+
+$(BUILD_ROOT)/build/bin/mizucp: $(OBJ) $(BUILD_ROOT)/build/lib/libidav.a
+       $(LD) -o $(BUILD_ROOT)/build/bin/mizucp$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -lidav -lucx $(LDFLAGS) $(DAV_LDFLAGS)
+
+$(BUILD_ROOT)/build/mizucp/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
+
diff --git a/mizucp/main.c b/mizucp/main.c
new file mode 100644 (file)
index 0000000..9dc305a
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 "main.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <pthread.h>
+#include <poll.h>
+
+#include <libidav/utils.h>
+
+#include <ucx/utils.h>
+
+#define OPTSTR "hlpsuv"
+
+#define TIMEOUT_IDLE -1
+#define TIMEOUT_CLIENT 1000
+#define CLIENT_UPDATE_INTERVALL 1
+
+static char *cfgdir;
+static char *socket_path;
+
+static int srvctrl;
+
+static int eventp[2];
+
+int main(int argc, char** argv) { 
+    int ret = 1;
+    
+    extern char *optarg;
+    extern int optind, opterr, optopt;
+    
+    CPSettings settings;
+    memset(&settings, 0, sizeof(CPSettings));
+    
+    int help = 0;
+    int version = 0;
+    int list = 0;    // list copying processes
+    
+    int c;
+    while((c = getopt(argc, argv, OPTSTR)) != -1) {
+        switch(c) {
+            case 'l': list = 1; break;
+            case 'p': settings.pause = 1; break;
+            case 's': settings.printsocket = 1; break;
+            case 'u': settings.url = 1; break;
+            case 'v': version = 1; break;
+        }
+    }
+    
+    int ac = argc - optind;
+    
+    if(list) {
+        // list command
+    } else if(help) {
+        // print help
+    } else if(version) {
+        // print version
+    } else if(ac == 2) {
+        // copy
+        settings.from = argv[optind];
+        settings.to   = argv[optind+1];
+        ret = uwcp_copy(&settings);
+    } else {
+        
+        // print usage
+    }
+    
+    return ret;
+}
+
+static int check_configdir(void) {
+    char *home = getenv(UWCP_ENV_HOME);
+    
+    cfgdir = util_concat_path(home, UWCP_CFG_DIR);
+    
+    struct stat s;
+    if(stat(cfgdir, &s)) {
+        if(errno == ENOENT) {
+            if(mkdir(cfgdir, S_IRWXU)) {
+                fprintf(stderr, "Cannot create %s: %s", cfgdir, strerror(errno));
+                return 1;
+            }
+        } else {
+            fprintf(stderr, "Cannot access %s: %s", cfgdir, strerror(errno));
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+static int create_control_socket(void) {
+    char *copydir = util_concat_path(cfgdir, UWCP_COPY_DIR);
+    
+    struct stat s;
+    if(stat(copydir, &s)) {
+        if(errno == ENOENT) {
+            if(mkdir(copydir, S_IRWXU)) {
+                fprintf(stderr, "Cannot create %s: %s", copydir, strerror(errno));
+                return 1;
+            }
+        } else {
+            fprintf(stderr, "Cannot access %s: %s", copydir, strerror(errno));
+            return 1;
+        }
+    }
+    
+    // create unix domain socket
+    char *random_str = util_random_str();
+    sstr_t socketp = ucx_sprintf("%s/%.*s", copydir, 8, random_str);
+    free(random_str);
+    socket_path = socketp.ptr;
+    
+    struct sockaddr_un addr;
+    if(socketp.length > sizeof(addr.sun_path)-1) {
+        fprintf(stderr,
+                "path '%s' too long for unix domain socket",
+                socketp.ptr);
+        return 1;
+    }
+    
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    memcpy(addr.sun_path, socketp.ptr, socketp.length);
+    
+    srvctrl = socket(AF_UNIX, SOCK_STREAM, 0);
+    if(srvctrl == -1) {
+        fprintf(stderr,
+                "Cannot create server control socket: %s",
+                strerror(errno));
+        return 1;
+    }
+    if(bind(srvctrl, (struct sockaddr*)&addr, sizeof(addr))) {
+        fprintf(stderr,
+                "srvctrl socket bind failed: %s",
+                strerror(errno));
+        return 1;
+    }
+    
+    listen(srvctrl, 4);
+    
+    return 0;
+}
+
+int uwcp_copy(CPSettings *settings) {
+    int ret  = 0;
+    
+    if(check_configdir()) {
+        return 2;
+    }
+    
+    
+    if(create_control_socket()) {
+        return 3;
+    }
+    
+    if(settings->printsocket) {
+        printf("%s\n", socket_path);
+    } else {
+        printf("copy %s to %s\n", settings->from, settings->to);
+        if(settings->pause) {
+            printf("pause\n");
+        }
+    }
+    
+    //pid_t p = fork();
+    pid_t p = 0;
+    if(p == 0) {
+        //close(0);
+        //close(1);
+        //close(2);
+        
+        ret =  uwcp_srvctrl(settings);
+    }
+    
+    return ret;
+}
+
+int uwcp_srvctrl(CPSettings *settings) {
+    if(pipe(eventp)) {
+        perror("Cannot create event pipe");
+        return 1;
+    }
+    
+    size_t allocfds = 8;
+    size_t numfds = 1;
+    
+    struct pollfd *fds = calloc(allocfds, sizeof(struct pollfd));
+    CtrlClient **clients = calloc(allocfds, sizeof(void*));
+    
+    int timeout = TIMEOUT_IDLE;
+    
+    fds[0].fd = srvctrl;
+    fds[0].events = POLLIN;
+    
+    int abort = 0;
+    
+    time_t tbegin = time(NULL);
+    
+    while(poll(fds, numfds, 1000) >= 0) {
+        time_t tend = time(NULL);
+        time_t diff = tend - tbegin;
+        tbegin = tend;
+        
+        if((fds[0].revents & POLLIN) == POLLIN) {
+            printf("accept\n");
+            int fd = accept(srvctrl, NULL, 0);
+            if(fd < 0) {
+                break;
+            }
+            
+            //int flags = fcntl(fd, F_GETFL, 0);
+            //flags = flags & ~O_NONBLOCK;
+            //fcntl(fd, F_SETFL, flags);
+            
+            CtrlClient *client = malloc(sizeof(CtrlClient));
+            memset(client, 0, sizeof(CtrlClient));
+            client->fd = fd;
+            
+            printf("add client: %d\n", client->fd);
+            
+            fds[numfds].fd = client->fd;
+            fds[numfds].events = POLLIN;
+            fds[numfds].revents = 0;
+            clients[numfds] = client;
+            numfds++;
+        }
+        
+        // check clients
+        int remove = 0;
+        for(int i=1;i<numfds;i++) {
+            if((fds[i].revents & POLLIN) == POLLIN) {
+                CtrlClient *client = clients[i];
+                ssize_t r = read(fds[i].fd, client->buf + client->pos, CLIENT_MSG_BUFSIZE - client->pos);
+                if(r <= 0) {
+                    printf("remove client: %d\n", fds[i].fd);
+                    fds[i].events = 0;
+                    remove = 1;
+                } else {
+                    client->pos += r;
+                    
+                    int msgret = handle_messages(client);
+                    if(msgret == 1) {
+                        fds[i].events = 0;
+                        remove = 1;
+                    } else if(msgret == -1) {
+                        abort = 1;
+                    }
+                }
+            }
+        }
+        
+        if(remove) {
+            int j = 1;
+            for(int i=1;i<numfds;i++) {
+                if(fds[i].events != 0) {
+                    fds[j] = fds[i];
+                    clients[j] = clients[j];
+                    j++;
+                } else {
+                    client_free(clients[i]);
+                    close(fds[i].fd);
+                }
+            }
+            numfds = j;
+        }
+        
+        if(diff >= CLIENT_UPDATE_INTERVALL) {
+            for(int i=1;i<numfds;i++) {
+                client_send_status(clients[i]);
+            }
+        }
+        
+        if(abort) break;
+        
+        timeout = numfds > 1 ? TIMEOUT_CLIENT : TIMEOUT_IDLE;
+    }
+    
+    unlink(socket_path);
+    
+    return 0;
+}
+
+
+void client_free(CtrlClient *client) {
+    free(client);
+}
+
+int handle_messages(CtrlClient *client) {
+    if(client->pos == CLIENT_MSG_BUFSIZE) {
+        return 1;
+    }
+    
+    int msgstart = 0;
+    for(int i=0;i<client->pos;i++) {
+        if(client->buf[i] == '\n') {
+            sstr_t msg;
+            msg.ptr = &client->buf[msgstart];
+            msg.length = i - msgstart;
+            msgstart = i+1;
+            
+            int msgret = handle_client_msg(client, msg);
+            if(msgret) return msgret;
+        }
+    }
+    
+    if(msgstart < client->pos) {
+        // incomplete message
+        memmove(client->buf, client->buf + msgstart, client->pos - msgstart);
+        client->pos -= msgstart;
+    } else {
+        client->pos = 0;
+    }
+    
+    return 0;
+}
+
+int handle_client_msg(CtrlClient *client, sstr_t msg) {
+    printf("msg: %.*s\n", (int)msg.length, msg.ptr);
+    
+    if(!sstrcmp(msg, S("abort"))) {
+        return -1;
+    }
+    
+    return 0;
+}
+
+void client_send_status(CtrlClient *client) {
+    char *msg = "s 0\n";
+    write(client->fd, msg, strlen(msg));
+}
diff --git a/mizucp/main.h b/mizucp/main.h
new file mode 100644 (file)
index 0000000..9192835
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 MAIN_H
+#define MAIN_H
+
+#include <stdlib.h>
+
+#include <ucx/string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UWCP_ENV_HOME "HOME"
+#define UWCP_CFG_DIR  ".uwfile"
+#define UWCP_COPY_DIR "copy"
+    
+#define CLIENT_MSG_BUFSIZE 512
+    
+typedef char CPBool;
+
+typedef struct {
+    char   *from;
+    char   *to;
+    CPBool url;
+    CPBool pause;
+    CPBool printsocket;
+} CPSettings;
+
+typedef struct {
+    int fd;
+    char buf[CLIENT_MSG_BUFSIZE];
+    size_t pos;
+} CtrlClient;
+
+int uwcp_copy(CPSettings *settings);
+
+int uwcp_srvctrl(CPSettings *settings);
+
+void client_free(CtrlClient *client);
+
+int handle_messages(CtrlClient *client);
+int handle_client_msg(CtrlClient *client, sstr_t msg);
+
+void client_send_status(CtrlClient *client);
+    
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAIN_H */
+
diff --git a/mizunara/Makefile b/mizunara/Makefile
new file mode 100644 (file)
index 0000000..5656808
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2021 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (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 $(BUILD_ROOT)/config.mk
+
+CFLAGS += -I../ui/ -I../ucx -I..
+
+SRC = main.c
+
+OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/mizunara/%.$(OBJ_EXT))
+
+all: $(BUILD_ROOT)/build/bin/mizunara
+
+$(BUILD_ROOT)/build/bin/mizunara: $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
+       $(LD) -o $(BUILD_ROOT)/build/bin/mizunara$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS)
+
+$(BUILD_ROOT)/build/mizunara/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
+
diff --git a/mizunara/main.c b/mizunara/main.c
new file mode 100644 (file)
index 0000000..ba20e28
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <ui/ui.h>
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+
+void action_menu(UiEvent *event, void *userdata) {
+    
+}
+
+
+void application_startup(UiEvent *event, void *data) {
+    
+    UiObject *obj = ui_window("Test", NULL);
+    
+    
+    ui_show(obj);
+}
+
+int main(int argc, char** argv) { 
+    ui_init("app1", argc, argv);
+    ui_onstartup(application_startup, NULL);
+    
+    // menu
+    ui_menu("File");
+    ui_menuitem("Hello", action_menu, NULL);
+    ui_submenu("Submenu1");
+    ui_submenu("Submenu2");
+    ui_menuitem("item2", action_menu, NULL);
+    ui_submenu_end();
+    ui_menuitem("item3", action_menu, NULL);
+    ui_submenu_end();
+    ui_menuitem("item4", action_menu, NULL);
+
+    
+    ui_main();
+    
+    return (EXIT_SUCCESS);
+}
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..2369197
--- /dev/null
@@ -0,0 +1,62 @@
+#
+# 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  = utils.c
+SRC += list.c
+SRC += map.c
+SRC += avl.c
+SRC += properties.c
+SRC += mempool.c
+SRC += string.c
+SRC += test.c
+SRC += allocator.c
+SRC += logging.c
+SRC += buffer.c
+SRC += stack.c
+SRC += ucx.c
+SRC += array.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/README b/ucx/README
new file mode 100644 (file)
index 0000000..d0890d0
--- /dev/null
@@ -0,0 +1,4 @@
+UCX is a library for common data structures, algorithms and string functions.
+
+More informations at: https://develop.uap-core.de/ucx/
+
diff --git a/ucx/allocator.c b/ucx/allocator.c
new file mode 100644 (file)
index 0000000..22a5cd5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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/allocator.h"
+
+#include <stdlib.h>
+
+static UcxAllocator default_allocator = {
+    NULL,
+    ucx_default_malloc,
+    ucx_default_calloc,
+    ucx_default_realloc,
+    ucx_default_free
+};
+
+UcxAllocator *ucx_default_allocator() {
+    UcxAllocator *allocator = &default_allocator;
+    return allocator;
+}
+
+void *ucx_default_malloc(void *ignore, size_t n) {
+    return malloc(n);
+}
+
+void *ucx_default_calloc(void *ignore, size_t n, size_t size) {
+    return calloc(n, size);
+}
+
+void *ucx_default_realloc(void *ignore, void *data, size_t n) {
+    return realloc(data, n);
+}
+
+void ucx_default_free(void *ignore, void *data) {
+    free(data);
+}
diff --git a/ucx/array.c b/ucx/array.c
new file mode 100644 (file)
index 0000000..0592fc6
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 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.
+ */
+
+#define _GNU_SOURCE /* we want to use qsort_r(), if available */
+#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */
+
+
+#include "ucx/array.h"
+#include "ucx/utils.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef UCX_ARRAY_DISABLE_QSORT
+#ifdef __GLIBC__
+#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
+#define ucx_array_sort_impl qsort_r
+#endif /* glibc version >= 2.8 */
+#elif /* not  __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__)
+#define ucx_array_sort_impl ucx_qsort_r
+#define USE_UCX_QSORT_R
+#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun)
+#if __STDC_VERSION__ >= 201112L
+#define ucx_array_sort_impl qsort_s
+#endif
+#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */
+#endif /* UCX_ARRAY_DISABLE_QSORT */
+
+#ifndef ucx_array_sort_impl
+#define ucx_array_sort_impl ucx_mergesort
+#endif
+
+static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) {
+    size_t required_capacity = array->capacity;
+    while (reqcap > required_capacity) {
+        if (required_capacity * 2 < required_capacity)
+            return 1;
+        required_capacity <<= 1;
+    }
+    if (ucx_array_reserve(array, required_capacity)) {
+        return 1;
+    }
+    return 0;
+}
+
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t index, void* data) {
+    
+    if(!alloc || !capacity || !array) {
+        errno = EINVAL;
+        return 1;
+    }
+    
+    size_t newcapacity = *capacity;
+    while(index >= newcapacity) {
+        if(ucx_szmul(newcapacity, 2, &newcapacity)) {
+            errno = EOVERFLOW;
+            return 1;
+        }        
+    }
+
+    size_t memlen, offset;
+    if(ucx_szmul(newcapacity, elmsize, &memlen)) {
+        errno = EOVERFLOW;
+        return 1;
+    }
+    /* we don't need to check index*elmsize - it is smaller than memlen */
+    
+    
+    void* newptr = alrealloc(alloc, *array, memlen);
+    if(newptr == NULL) {
+        errno = ENOMEM; /* we cannot assume that every allocator sets this */
+        return 1;
+    }
+    *array = newptr;
+    *capacity = newcapacity;
+    
+    
+    char* dest = *array;
+    dest += elmsize*index;
+    memcpy(dest, data, elmsize);
+    
+    return 0;
+}
+
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t index, void* data) {
+    
+    return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*),
+            index, &data);
+}
+
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize) {
+    return ucx_array_new_a(capacity, elemsize, ucx_default_allocator());
+}
+
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    UcxArray* array = almalloc(allocator, sizeof(UcxArray));
+    if(array) {
+        ucx_array_init_a(array, capacity, elemsize, allocator);
+    }
+    return array;
+}
+
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) {
+    ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator());
+}
+
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    
+    array->allocator = allocator;
+    array->elemsize = elemsize;
+    array->size = 0;
+    array->data = alcalloc(allocator, capacity, elemsize);
+    
+    if (array->data) {
+        array->capacity = capacity;
+    } else {
+        array->capacity = 0;
+    }
+}
+
+int ucx_array_clone(UcxArray* dest, UcxArray const* src) {
+    if (ucx_array_ensurecap(dest, src->capacity)) {
+        return 1;
+    }
+    
+    dest->elemsize = src->elemsize;
+    dest->size = src->size;
+    
+    if (dest->data) {
+        memcpy(dest->data, src->data, src->size*src->elemsize);
+    }
+    
+    return 0;
+}
+
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data) {
+    
+    if (array1->size != array2->size || array1->elemsize != array2->elemsize) {
+        return 0;
+    } else {
+        if (array1->size == 0)
+            return 1;
+        
+        size_t elemsize;
+        if (cmpfnc == NULL) {
+            cmpfnc = ucx_cmp_mem;
+            elemsize = array1->elemsize;
+            data = &elemsize;
+        }
+        
+        for (size_t i = 0 ; i < array1->size ; i++) {
+            int r = cmpfnc(
+                    ucx_array_at(array1, i),
+                    ucx_array_at(array2, i),
+                    data);
+            if (r != 0)
+                return 0;
+        }
+        return 1;
+    }
+}
+
+void ucx_array_destroy(UcxArray *array) {
+    if(array->data)
+        alfree(array->allocator, array->data);
+    array->data = NULL;
+    array->capacity = array->size = 0;
+}
+
+void ucx_array_free(UcxArray *array) {
+    ucx_array_destroy(array);
+    alfree(array->allocator, array);
+}
+
+int ucx_array_append_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    void* dest = ucx_array_at(array, array->size);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    array->size += count;
+    
+    return 0;
+}
+
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    if (array->size > 0) {
+        void *dest = ucx_array_at(array, count);
+        memmove(dest, array->data, array->elemsize*array->size);
+    }
+    
+    if (data) {
+        memcpy(array->data, data, array->elemsize*count);
+    } else {
+        memset(array->data, 0, array->elemsize*count);
+    }
+    array->size += count;
+        
+    return 0;
+}
+
+int ucx_array_set_from(UcxArray *array, size_t index,
+        void *data, size_t count) {
+    if (ucx_array_ensurecap(array, index + count))
+        return 1;
+    
+    if (index+count > array->size) {
+        array->size = index+count;
+    }
+    
+    void *dest = ucx_array_at(array, index);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    
+    return 0;
+}
+
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2) {
+    
+    if (array1->elemsize != array2->elemsize)
+        return 1;
+    
+    size_t capacity = array1->capacity+array2->capacity;
+        
+    if (array1->capacity < capacity) {
+        if (ucx_array_reserve(array1, capacity)) {
+            return 1;
+        }
+    }
+    
+    void* dest = ucx_array_at(array1, array1->size);
+    memcpy(dest, array2->data, array2->size*array2->elemsize);
+    
+    array1->size += array2->size;
+    
+    return 0;
+}
+
+void *ucx_array_at(UcxArray const *array, size_t index) {
+    char* memory = array->data;
+    char* loc = memory + index*array->elemsize;
+    return loc;
+}
+
+size_t ucx_array_find(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    
+    size_t elemsize;
+    if (cmpfnc == NULL) {
+        cmpfnc = ucx_cmp_mem;
+        elemsize = array->elemsize;
+        data = &elemsize;
+    }
+
+    if (array->size > 0) {
+        for (size_t i = 0 ; i < array->size ; i++) {
+            void* ptr = ucx_array_at(array, i);
+            if (cmpfnc(ptr, elem, data) == 0) {
+                return i;
+            }
+        }
+        return array->size;
+    } else {
+        return 0;
+    }
+}
+
+int ucx_array_contains(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    return ucx_array_find(array, elem, cmpfnc, data) != array->size;
+}
+
+static void ucx_mergesort_merge(void *arrdata,size_t elemsize,
+        cmp_func cmpfnc, void *data,
+        size_t start, size_t mid, size_t end) { 
+    
+    char* array = arrdata;
+    
+    size_t rightstart = mid + 1; 
+  
+    if (cmpfnc(array + mid*elemsize,
+            array + rightstart*elemsize, data) <= 0) {
+        /* already sorted */
+        return;
+    }
+  
+    /* we need memory for one element */
+    void *value = malloc(elemsize);
+    
+    while (start <= mid && rightstart <= end) { 
+        if (cmpfnc(array + start*elemsize,
+                array + rightstart*elemsize, data) <= 0) { 
+            start++; 
+        } else {
+            /* save the value from the right */
+            memcpy(value, array + rightstart*elemsize, elemsize);
+                        
+            /* shift all left elements one element to the right */
+            size_t shiftcount = rightstart-start;
+            void *startptr = array + start*elemsize;
+            void *dest = array + (start+1)*elemsize;
+            memmove(dest, startptr, shiftcount*elemsize);
+            
+            /* bring the first value from the right to the left */
+            memcpy(startptr, value, elemsize);
+  
+            start++; 
+            mid++; 
+            rightstart++; 
+        }
+    }
+    
+    /* free the temporary memory */
+    free(value);
+} 
+  
+static void ucx_mergesort_impl(void *arrdata, size_t elemsize,
+        cmp_func cmpfnc, void *data, size_t l, size_t r) { 
+    if (l < r) {
+        size_t m = l + (r - l) / 2; 
+  
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); 
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); 
+        ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r);
+    } 
+}
+
+static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize,
+        cmp_func cmpfnc, void *data) {
+    
+    ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1);
+}
+
+#ifdef USE_UCX_QSORT_R
+struct cmpfnc_swapargs_info {
+    cmp_func func;
+    void *data;
+};
+
+static int cmp_func_swap_args(void *data, const void *x, const void *y) {
+    struct cmpfnc_swapargs_info* info = data;
+    return info->func(x, y, info->data);
+}
+
+static void ucx_qsort_r(void *array, size_t count, size_t elemsize,
+                    cmp_func cmpfnc, void *data) {
+    struct cmpfnc_swapargs_info info;
+    info.func = cmpfnc;
+    info.data = data;
+    qsort_r(array, count, elemsize, &info, cmp_func_swap_args);
+}
+#endif /* USE_UCX_QSORT_R */
+
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) {
+    ucx_array_sort_impl(array->data, array->size, array->elemsize,
+            cmpfnc, data);
+}
+
+void ucx_array_remove(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, index+1);
+        memmove(dest, src, (array->size - index)*array->elemsize);
+    }
+}
+
+void ucx_array_remove_fast(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {       
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, array->size);
+        memcpy(dest, src, array->elemsize);
+    }
+}
+
+int ucx_array_shrink(UcxArray* array) {
+    void* newptr = alrealloc(array->allocator, array->data,
+                array->size*array->elemsize);
+    if (newptr) {
+        array->data = newptr;
+        array->capacity = array->size;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+int ucx_array_resize(UcxArray* array, size_t capacity) {
+    if (array->capacity >= capacity) {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            if (array->size > array->capacity) {
+                array->size = array->capacity;
+            }
+            return 0;
+        } else {
+            return 1;
+        }
+    } else {
+        return ucx_array_reserve(array, capacity);
+    }
+}
+
+int ucx_array_reserve(UcxArray* array, size_t capacity) {
+    if (array->capacity > capacity) {
+        return 0;
+    } else {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+
+int ucx_array_grow(UcxArray* array, size_t count) {
+    return ucx_array_reserve(array, array->size+count);
+}
diff --git a/ucx/avl.c b/ucx/avl.c
new file mode 100644 (file)
index 0000000..7639b56
--- /dev/null
+++ b/ucx/avl.c
@@ -0,0 +1,373 @@
+/*
+ * 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/avl.h"
+
+#include <limits.h>
+
+#define ptrcast(ptr) ((void*)(ptr))
+#define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree))
+#define alloc_node(al) (UcxAVLNode*) almalloc((al), sizeof(UcxAVLNode))
+
+static void ucx_avl_connect(UcxAVLTree *tree,
+        UcxAVLNode *node, UcxAVLNode *child, intptr_t nullkey) {
+    if (child) {
+        child->parent = node;
+    }
+    // if child is NULL, nullkey decides if left or right pointer is cleared
+    if (tree->cmpfunc(
+        ptrcast(child ? child->key : nullkey),
+        ptrcast(node->key), tree->userdata) > 0) {
+      node->right = child;
+    } else {
+      node->left = child;
+    }
+    size_t lh = node->left ? node->left->height : 0;
+    size_t rh = node->right ? node->right->height : 0;
+    node->height = 1 + (lh > rh ? lh : rh);
+}
+
+#define avlheight(node) ((node) ? (node)->height : 0)
+
+static UcxAVLNode* avl_rotright(UcxAVLTree *tree, UcxAVLNode *l0) {
+    UcxAVLNode *p = l0->parent;
+    UcxAVLNode *l1 = l0->left;
+    if (p) {
+        ucx_avl_connect(tree, p, l1, 0);
+    } else {
+        l1->parent = NULL;
+    }
+    ucx_avl_connect(tree, l0, l1->right, l1->key);
+    ucx_avl_connect(tree, l1, l0, 0);
+    return l1;
+}
+
+static UcxAVLNode* avl_rotleft(UcxAVLTree *tree, UcxAVLNode *l0) {
+    UcxAVLNode *p = l0->parent;
+    UcxAVLNode *l1 = l0->right;
+    if (p) {
+        ucx_avl_connect(tree, p, l1, 0);
+    } else {
+        l1->parent = NULL;
+    }
+    ucx_avl_connect(tree, l0, l1->left, l1->key);
+    ucx_avl_connect(tree, l1, l0, 0);
+    return l1;
+}
+
+static void ucx_avl_balance(UcxAVLTree *tree, UcxAVLNode *n) {
+    int lh = avlheight(n->left);
+    int rh = avlheight(n->right);
+    n->height = 1 + (lh > rh ? lh : rh);
+    
+    if (lh - rh == 2) {
+      UcxAVLNode *c = n->left;
+      if (avlheight(c->right) - avlheight(c->left) == 1) {
+        avl_rotleft(tree, c);
+      }
+      n = avl_rotright(tree, n);
+    } else if (rh - lh == 2) {  
+      UcxAVLNode *c = n->right;
+      if (avlheight(c->left) - avlheight(c->right) == 1) {
+        avl_rotright(tree, c);
+      }
+      n = avl_rotleft(tree, n);
+    }
+
+    if (n->parent) {
+      ucx_avl_balance(tree, n->parent);
+    } else {
+      tree->root = n;
+    }
+}
+
+UcxAVLTree *ucx_avl_new(cmp_func cmpfunc) {
+    return ucx_avl_new_a(cmpfunc, ucx_default_allocator());
+}
+
+UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator) {
+    UcxAVLTree* tree = alloc_tree(allocator);
+    if (tree) {
+        tree->allocator = allocator;
+        tree->cmpfunc = cmpfunc;
+        tree->root = NULL;
+        tree->userdata = NULL;
+    }
+    
+    return tree;
+}
+
+static void ucx_avl_free_node(UcxAllocator *al, UcxAVLNode *node) {
+    if (node) {
+        ucx_avl_free_node(al, node->left);
+        ucx_avl_free_node(al, node->right);
+        alfree(al, node);
+    }
+}
+
+void ucx_avl_free(UcxAVLTree *tree) {
+    UcxAllocator *al = tree->allocator;
+    ucx_avl_free_node(al, tree->root);
+    alfree(al, tree);
+}
+
+static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node,
+        ucx_destructor destr) {
+    if (node) {
+        ucx_avl_free_content_node(al, node->left, destr);
+        ucx_avl_free_content_node(al, node->right, destr);
+        if (destr) {
+            destr(node->value);
+        } else {
+            alfree(al, node->value);
+        }
+    }
+}
+
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) {
+    ucx_avl_free_content_node(tree->allocator, tree->root, destr);
+}
+
+UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) {
+    UcxAVLNode *n = tree->root;
+    int cmpresult;
+    while (n && (cmpresult = tree->cmpfunc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    return n;
+}
+
+void *ucx_avl_get(UcxAVLTree *tree, intptr_t key) {
+    UcxAVLNode *n = ucx_avl_get_node(tree, key);
+    return n ? n->value : NULL;
+}
+
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = tree->root;
+    UcxAVLNode *closest = NULL;
+
+    intmax_t cmpresult;
+    intmax_t closest_dist;
+    closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX;
+    
+    while (n && (cmpresult = dfnc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        if (mode == UCX_AVL_FIND_CLOSEST) {
+            intmax_t dist = cmpresult;
+            if (dist < 0) dist *= -1;
+            if (dist < closest_dist) {
+                closest_dist = dist;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) {
+            if (cmpresult > closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) {
+            if (cmpresult < closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        }
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    return n ? n : closest;
+}
+
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode);
+    return n ? n->value : NULL;
+}
+
+int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) {
+    return ucx_avl_put_s(tree, key, value, NULL);
+}
+
+int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value,
+        void **oldvalue) {
+    if (tree->root) {
+        UcxAVLNode *n = tree->root;
+        int cmpresult;
+        while ((cmpresult = tree->cmpfunc(
+                ptrcast(key), ptrcast(n->key), tree->userdata))) {
+            UcxAVLNode *m = cmpresult > 0 ? n->right : n->left;
+            if (m) {
+                n = m;
+            } else {
+                break;
+            }
+        }
+
+        if (cmpresult) {
+            UcxAVLNode* e = alloc_node(tree->allocator);
+            if (e) {
+                e->key = key; e->value = value; e->height = 1;
+                e->parent = e->left = e->right = NULL;
+                ucx_avl_connect(tree, n, e, 0);
+                ucx_avl_balance(tree, n);
+                return 0;
+            } else {
+                return 1;
+            }
+        } else {
+            if (oldvalue) {
+                *oldvalue = n->value;
+            }
+            n->value = value;
+            return 0;
+        }
+    } else {
+        tree->root = alloc_node(tree->allocator);
+        if (tree->root) {
+            tree->root->key = key; tree->root->value = value;
+            tree->root->height = 1;
+            tree->root->parent = tree->root->left = tree->root->right = NULL;
+            
+            if (oldvalue) {
+                *oldvalue = NULL;
+            }
+            
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+
+int ucx_avl_remove(UcxAVLTree *tree, intptr_t key) {
+    return ucx_avl_remove_s(tree, key, NULL, NULL);
+}
+    
+int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node) {
+    return ucx_avl_remove_s(tree, node->key, NULL, NULL);
+}
+
+int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
+        intptr_t *oldkey, void **oldvalue) {
+    
+    UcxAVLNode *n = tree->root;
+    int cmpresult;
+    while (n && (cmpresult = tree->cmpfunc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    if (n) {
+        if (oldkey) {
+            *oldkey = n->key;
+        }
+        if (oldvalue) {
+            *oldvalue = n->value;
+        }
+        
+        UcxAVLNode *p = n->parent;
+        if (n->left && n->right) {
+            UcxAVLNode *s = n->right;
+            while (s->left) {
+                s = s->left;
+            }
+            ucx_avl_connect(tree, s->parent, s->right, s->key);
+            n->key = s->key; n->value = s->value;
+            p = s->parent;
+            alfree(tree->allocator, s);
+        } else {
+            if (p) {
+                ucx_avl_connect(tree, p, n->right ? n->right:n->left, n->key);
+            } else {
+                tree->root = n->right ? n->right : n->left;
+                if (tree->root) {
+                    tree->root->parent = NULL;
+                }
+            }
+            alfree(tree->allocator, n);
+        }
+
+        if (p) {
+            ucx_avl_balance(tree, p);
+        }
+        
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static size_t ucx_avl_countn(UcxAVLNode *node) {
+    if (node) {
+        return 1 + ucx_avl_countn(node->left) + ucx_avl_countn(node->right);
+    } else {
+        return 0;
+    }
+}
+
+size_t ucx_avl_count(UcxAVLTree *tree) {
+    return ucx_avl_countn(tree->root);
+}
+
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) {
+    if (node->left) {
+        UcxAVLNode* n = node->left;
+        while (n->right) {
+            n = n->right;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->right == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
+
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) {
+    if (node->right) {
+        UcxAVLNode* n = node->right;
+        while (n->left) {
+            n = n->left;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->left == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
diff --git a/ucx/buffer.c b/ucx/buffer.c
new file mode 100644 (file)
index 0000000..a6a8085
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * 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/buffer.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags) {
+    UcxBuffer *buffer = (UcxBuffer*) malloc(sizeof(UcxBuffer));
+    if (buffer) {
+        buffer->flags = flags;
+        if (!space) {
+            buffer->space = (char*)malloc(capacity);
+            if (!buffer->space) {
+                free(buffer);
+                return NULL;
+            }
+            memset(buffer->space, 0, capacity);
+            buffer->flags |= UCX_BUFFER_AUTOFREE;
+        } else {
+            buffer->space = (char*)space;
+        }
+        buffer->capacity = capacity;
+        buffer->size = 0;
+
+        buffer->pos = 0;
+    }
+
+    return buffer;
+}
+
+void ucx_buffer_free(UcxBuffer *buffer) {
+    if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) {
+        free(buffer->space);
+    }
+    free(buffer);
+}
+
+UcxBuffer* ucx_buffer_extract(
+        UcxBuffer *src, size_t start, size_t length, int flags) {
+    if (src->size == 0 || length == 0 ||
+        ((size_t)-1) - start < length || start+length > src->capacity)
+    {
+        return NULL;
+    }
+
+    UcxBuffer *dst = (UcxBuffer*) malloc(sizeof(UcxBuffer));
+    if (dst) {
+        dst->space = (char*)malloc(length);
+        if (!dst->space) {
+            free(dst);
+            return NULL;
+        }
+        dst->capacity = length;
+        dst->size = length;
+        dst->flags = flags | UCX_BUFFER_AUTOFREE;
+        dst->pos = 0;
+        memcpy(dst->space, src->space+start, length);
+    }
+    return dst;
+}
+
+int ucx_buffer_seek(UcxBuffer *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;
+    }
+
+}
+
+int ucx_buffer_eof(UcxBuffer *buffer) {
+    return buffer->pos >= buffer->size;
+}
+
+int ucx_buffer_extend(UcxBuffer *buffer, size_t len) {
+    size_t newcap = buffer->capacity;
+    
+    if (buffer->capacity + len < buffer->capacity) {
+        return -1;
+    }
+    
+    while (buffer->capacity + len > newcap) {
+        newcap <<= 1;
+        if (newcap < buffer->capacity) {
+            return -1;
+        }
+    }
+    
+    char *newspace = (char*)realloc(buffer->space, newcap);
+    if (newspace) {
+        memset(newspace+buffer->size, 0, newcap-buffer->size);
+        buffer->space = newspace;
+        buffer->capacity = newcap;
+    } else {
+        return -1;
+    }
+    
+    return 0;
+}
+
+size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer) {
+    size_t len;
+    if(ucx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    size_t required = buffer->pos + len;
+    if (buffer->pos > required) {
+        return 0;
+    }
+    
+    if (required > buffer->capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, required - buffer->capacity)) {
+                return 0;
+            }
+        } else {
+            len = buffer->capacity - buffer->pos;
+            if (size > 1) {
+                len -= len%size;
+            }
+        }
+    }
+    
+    if (len == 0) {
+        return len;
+    }
+    
+    memcpy(buffer->space + buffer->pos, ptr, len);
+    buffer->pos += len;
+    if(buffer->pos > buffer->size) {
+        buffer->size = buffer->pos;
+    }
+    
+    return len / size;
+}
+
+size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer) {
+    size_t len;
+    if(ucx_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->space + buffer->pos, len);
+    buffer->pos += len;
+    
+    return len / size;
+}
+
+int ucx_buffer_putc(UcxBuffer *buffer, int c) {
+    if(buffer->pos >= buffer->capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if(ucx_buffer_extend(buffer, 1)) {
+                return EOF;
+            }
+        } else {
+            return EOF;
+        }
+    }
+    
+    c &= 0xFF;
+    buffer->space[buffer->pos] = (char) c;
+    buffer->pos++;
+    if(buffer->pos > buffer->size) {
+        buffer->size = buffer->pos;
+    }
+    return c;
+}
+
+int ucx_buffer_getc(UcxBuffer *buffer) {
+    if (ucx_buffer_eof(buffer)) {
+        return EOF;
+    } else {
+        int c = ((unsigned char*)buffer->space)[buffer->pos];
+        buffer->pos++;
+        return c;
+    }
+}
+
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) {
+    return ucx_buffer_write((const void*)str, 1, strlen(str), buffer);
+}
+
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->space, buffer->space + shift, buffer->size - shift);
+        buffer->size -= shift;
+        
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int ucx_buffer_shift_right(UcxBuffer* 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 & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+    
+    memmove(buffer->space + shift, buffer->space, movebytes);
+    buffer->size = shift+movebytes;
+    
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+    
+    return 0;
+}
+
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) {
+    if (shift < 0) {
+        return ucx_buffer_shift_left(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return ucx_buffer_shift_right(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
diff --git a/ucx/list.c b/ucx/list.c
new file mode 100644 (file)
index 0000000..293592c
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * 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/list.h"
+
+UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) {
+    return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data);
+}
+
+UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l,
+        copy_func fnc, void *data) {
+    UcxList *ret = NULL;
+    while (l) {
+        if (fnc) {
+            ret = ucx_list_append_a(alloc, ret, fnc(l->data, data));
+        } else {
+            ret = ucx_list_append_a(alloc, ret, l->data);
+        }
+        l = l->next;
+    }
+    return ret;
+}
+
+int ucx_list_equals(const UcxList *l1, const UcxList *l2,
+        cmp_func fnc, void* data) {
+    if (l1 == l2) return 1;
+    
+    while (l1 != NULL && l2 != NULL) {
+        if (fnc == NULL) {
+            if (l1->data != l2->data) return 0;
+        } else {
+            if (fnc(l1->data, l2->data, data) != 0) return 0;
+        }
+        l1 = l1->next;
+        l2 = l2->next;
+    }
+    
+    return (l1 == NULL && l2 == NULL);
+}
+
+void ucx_list_free(UcxList *l) {
+    ucx_list_free_a(ucx_default_allocator(), l);
+}
+
+void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) {
+    UcxList *e = l, *f;
+    while (e != NULL) {
+        f = e;
+        e = e->next;
+        alfree(alloc, f);
+    }
+}
+
+void ucx_list_free_content(UcxList* list, ucx_destructor destr) {
+    if (!destr) destr = free;
+    while (list != NULL) {
+        destr(list->data);
+        list = list->next;
+    }
+}
+
+UcxList *ucx_list_append(UcxList *l, void *data)  {
+    return ucx_list_append_a(ucx_default_allocator(), l, data);
+}
+
+UcxList *ucx_list_append_a(UcxAllocator *alloc, UcxList *l, void *data)  {
+    UcxList *nl = (UcxList*) almalloc(alloc, sizeof(UcxList));
+    if (!nl) {
+        return NULL;
+    }
+    
+    nl->data = data;
+    nl->next = NULL;
+    if (l) {
+        UcxList *t = ucx_list_last(l);
+        t->next = nl;
+        nl->prev = t;
+        return l;
+    } else {
+        nl->prev = NULL;
+        return nl;
+    }
+}
+
+UcxList *ucx_list_prepend(UcxList *l, void *data) {
+    return ucx_list_prepend_a(ucx_default_allocator(), l, data);
+}
+
+UcxList *ucx_list_prepend_a(UcxAllocator *alloc, UcxList *l, void *data) {
+    UcxList *nl = ucx_list_append_a(alloc, NULL, data);
+    if (!nl) {
+        return NULL;
+    }
+    l = ucx_list_first(l);
+    
+    if (l) {
+        nl->next = l;
+        l->prev = nl;
+    }
+    return nl;
+}
+
+UcxList *ucx_list_concat(UcxList *l1, UcxList *l2) {
+    if (l1) {
+        UcxList *last = ucx_list_last(l1);
+        last->next = l2;
+        if (l2) {
+            l2->prev = last;
+        }
+        return l1;
+    } else {
+        return l2;
+    }
+}
+
+UcxList *ucx_list_last(const UcxList *l) {
+    if (l == NULL) return NULL;
+    
+    const UcxList *e = l;
+    while (e->next != NULL) {
+        e = e->next;
+    }
+    return (UcxList*)e;
+}
+
+ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem) {
+    ssize_t index = 0;
+    while (list) {
+        if (list == elem) {
+            return index;
+        }
+        list = list->next;
+        index++;
+    }
+    return -1;
+}
+
+UcxList *ucx_list_get(const UcxList *l, size_t index) {
+    if (l == NULL) return NULL;
+
+    const UcxList *e = l;
+    while (e->next && index > 0) {
+        e = e->next;
+        index--;
+    }
+    
+    return (UcxList*)(index == 0 ? e : NULL);
+}
+
+ssize_t ucx_list_find(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
+    ssize_t index = 0;
+    UCX_FOREACH(e, l) {
+        if (fnc) {
+            if (fnc(elem, e->data, cmpdata) == 0) {
+                return index;
+            }
+        } else {
+            if (elem == e->data) {
+                return index;
+            }
+        }
+        index++;
+    }
+    return -1;
+}
+
+int ucx_list_contains(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
+    return ucx_list_find(l, elem, fnc, cmpdata) > -1;
+}
+
+size_t ucx_list_size(const UcxList *l) {
+    if (l == NULL) return 0;
+    
+    const UcxList *e = l;
+    size_t s = 1;
+    while (e->next != NULL) {
+        e = e->next;
+        s++;
+    }
+
+    return s;
+}
+
+static UcxList *ucx_list_sort_merge(size_t length,
+        UcxList* ls, UcxList* le, UcxList* re,
+        cmp_func fnc, void* data) {
+
+    UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length);
+    UcxList *rc, *lc;
+
+    lc = ls; rc = le;
+    size_t n = 0;
+    while (lc && lc != le && rc != re) {
+        if (fnc(lc->data, rc->data, data) <= 0) {
+            sorted[n] = lc;
+            lc = lc->next;
+        } else {
+            sorted[n] = rc;
+            rc = rc->next;
+        }
+        n++;
+    }
+    while (lc && lc != le) {
+        sorted[n] = lc;
+        lc = lc->next;
+        n++;
+    }
+    while (rc && rc != re) {
+        sorted[n] = rc;
+        rc = rc->next;
+        n++;
+    }
+
+    // Update pointer
+    sorted[0]->prev = NULL;
+    for (int i = 0 ; i < length-1 ; i++) {
+        sorted[i]->next = sorted[i+1];
+        sorted[i+1]->prev = sorted[i];
+    }
+    sorted[length-1]->next = NULL;
+
+    UcxList *ret = sorted[0];
+    free(sorted);
+    return ret;
+}
+
+UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) {
+    if (l == NULL) {
+        return NULL;
+    }
+
+    UcxList *lc;
+    size_t ln = 1;
+
+    UcxList *ls = l, *le, *re;
+    
+    // check how many elements are already sorted
+    lc = ls;
+    while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) {
+        lc = lc->next;
+        ln++;
+    }
+    le = lc->next;
+
+    if (le == NULL) {
+        return l; // this list is already sorted :)
+    } else {
+        UcxList *rc;
+        size_t rn = 1;
+        rc = le;
+        // skip already sorted elements
+        while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) {
+            rc = rc->next;
+            rn++;
+        }
+        re = rc->next;
+
+        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+        UcxList *sorted = ucx_list_sort_merge(ln+rn,
+                ls, le, re,
+                fnc, data);
+        
+        // Something left? Sort it!
+        size_t remainder_length = ucx_list_size(re);
+        if (remainder_length > 0) {
+            UcxList *remainder = ucx_list_sort(re, fnc, data);
+
+            // merge sorted list with (also sorted) remainder
+            l = ucx_list_sort_merge(ln+rn+remainder_length,
+                    sorted, remainder, NULL, fnc, data);
+        } else {
+            // no remainder - we've got our sorted list
+            l = sorted;
+        }
+
+        return l;
+    }
+}
+
+UcxList *ucx_list_first(const UcxList *l) {
+    if (!l) {
+        return NULL;
+    }
+    
+    const UcxList *e = l;
+    while (e->prev) {
+        e = e->prev;
+    }
+    return (UcxList *)e;
+}
+
+UcxList *ucx_list_remove(UcxList *l, UcxList *e) {
+    return ucx_list_remove_a(ucx_default_allocator(), l, e);
+}
+    
+UcxList *ucx_list_remove_a(UcxAllocator *alloc, UcxList *l, UcxList *e) {
+    if (l == e) {
+        l = e->next;
+    }
+    
+    if (e->next) {
+        e->next->prev = e->prev;
+    }
+    
+    if (e->prev) {
+        e->prev->next = e->next;
+    }
+    
+    alfree(alloc, e);
+    return l;
+}
+
+
+static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata,
+        int op) {
+    
+    UcxList *res = NULL;
+    UcxList *cur = NULL;
+    const UcxList *src = left;
+    
+    do {
+        UCX_FOREACH(node, src) {
+            void* elem = node->data;
+            if (
+                (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) ||
+                (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) ||
+                (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) {
+                UcxList *nl = almalloc(allocator, sizeof(UcxList));
+                nl->prev = cur;
+                nl->next = NULL;
+                if (cpfnc) {
+                    nl->data = cpfnc(elem, cpdata);
+                } else {
+                    nl->data = elem;
+                }
+                if (cur != NULL)
+                    cur->next = nl;
+                cur = nl;
+                if (res == NULL)
+                    res = cur;
+            }
+        }
+        if (op == 0 && src == left)
+            src = right;
+        else
+            src = NULL;
+    } while (src != NULL);
+    
+    return res;
+}
+
+UcxList* ucx_list_union(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_union_a(ucx_default_allocator(),
+            left, right, cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 0);
+}
+
+UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_intersection_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 1);
+}
+
+UcxList* ucx_list_difference(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_difference_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 2);
+}
diff --git a/ucx/logging.c b/ucx/logging.c
new file mode 100644 (file)
index 0000000..d6fdce0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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/logging.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+
+UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask) {
+    UcxLogger *logger = (UcxLogger*) malloc(sizeof(UcxLogger));
+    if (logger != NULL) {
+        logger->stream = stream;
+        logger->writer = (write_func)fwrite;
+        logger->dateformat = (char*) "%F %T %z ";
+        logger->level = level;
+        logger->mask = mask;
+        logger->levels = ucx_map_new(8);
+        
+        unsigned int l;
+        l = UCX_LOGGER_ERROR;
+        ucx_map_int_put(logger->levels, l, (void*) "[ERROR]");
+        l = UCX_LOGGER_WARN;
+        ucx_map_int_put(logger->levels, l, (void*) "[WARNING]");
+        l = UCX_LOGGER_INFO;
+        ucx_map_int_put(logger->levels, l, (void*) "[INFO]");
+        l = UCX_LOGGER_DEBUG;
+        ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]");
+        l = UCX_LOGGER_TRACE;
+        ucx_map_int_put(logger->levels, l, (void*) "[TRACE]");
+    }
+
+    return logger;
+}
+
+void ucx_logger_free(UcxLogger *logger) {
+    ucx_map_free(logger->levels);
+    free(logger);
+}
+
+// estimated max. message length (documented)
+#define UCX_LOGGER_MSGMAX 4096
+
+void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
+        const unsigned int line, const char *format, ...) {
+    if (level <= logger->level) {
+        char msg[UCX_LOGGER_MSGMAX];
+        const char *text;
+        size_t k = 0;
+        size_t n;
+        
+        if ((logger->mask & UCX_LOGGER_LEVEL) > 0) {
+            text = (const char*) ucx_map_int_get(logger->levels, level);
+            if (!text) {
+                text = "[UNKNOWN]";
+            }
+            n = strlen(text);
+            n = n > 256 ? 256 : n;
+            memcpy(msg+k, text, n);
+            k += n;
+            msg[k++] = ' ';
+        }
+        if ((logger->mask & UCX_LOGGER_TIMESTAMP) > 0) {
+            time_t now = time(NULL);
+            k += strftime(msg+k, 128, logger->dateformat, localtime(&now));
+        }
+        if ((logger->mask & UCX_LOGGER_SOURCE) > 0) {
+            char *fpart = strrchr(file, '/');
+            if (fpart) file = fpart+1;
+            fpart = strrchr(file, '\\');
+            if (fpart) file = fpart+1;
+            n = strlen(file);
+            memcpy(msg+k, file, n);
+            k += n;
+            k += sprintf(msg+k, ":%u ", line);
+        }
+        
+        if (k > 0) {
+            msg[k++] = '-'; msg[k++] = ' ';
+        }
+        
+        va_list args;
+        va_start (args, format);
+        k += vsnprintf(msg+k, UCX_LOGGER_MSGMAX-k-1, format, args);
+        va_end (args);        
+        
+        msg[k++] = '\n';
+        
+        logger->writer(msg, 1, k, logger->stream);
+    }
+}
diff --git a/ucx/map.c b/ucx/map.c
new file mode 100644 (file)
index 0000000..ba7961d
--- /dev/null
+++ b/ucx/map.c
@@ -0,0 +1,402 @@
+/*
+ * 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/map.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+UcxMap *ucx_map_new(size_t size) {
+    return ucx_map_new_a(NULL, size);
+}
+
+UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size) {
+    if(size == 0) {
+        size = 16;
+    }
+       
+    if(!allocator) {
+        allocator = ucx_default_allocator();
+    }
+    
+    UcxMap *map = (UcxMap*)almalloc(allocator, sizeof(UcxMap));
+    if (!map) {
+        return NULL;
+    }
+    
+    map->allocator = allocator;
+    map->map = (UcxMapElement**)alcalloc(
+            allocator, size, sizeof(UcxMapElement*));
+    if(map->map == NULL) {
+        alfree(allocator, map);
+        return NULL;
+    }
+    map->size = size;
+    map->count = 0;
+
+    return map;
+}
+
+static void ucx_map_free_elmlist_contents(UcxMap *map) {
+    for (size_t n = 0 ; n < map->size ; n++) {
+        UcxMapElement *elem = map->map[n];
+        if (elem != NULL) {
+            do {
+                UcxMapElement *next = elem->next;
+                alfree(map->allocator, elem->key.data);
+                alfree(map->allocator, elem);
+                elem = next;
+            } while (elem != NULL);
+        }
+    }
+}
+
+void ucx_map_free(UcxMap *map) {
+    ucx_map_free_elmlist_contents(map);
+    alfree(map->allocator, map->map);
+    alfree(map->allocator, map);
+}
+
+void ucx_map_free_content(UcxMap *map, ucx_destructor destr) {
+    UcxMapIterator iter = ucx_map_iterator(map);
+    void *val;
+    UCX_MAP_FOREACH(key, val, iter) {
+        if (destr) {
+            destr(val);
+        } else {
+            alfree(map->allocator, val);
+        }
+    }
+}
+
+void ucx_map_clear(UcxMap *map) {
+    if (map->count == 0) {
+        return; // nothing to do
+    }
+    ucx_map_free_elmlist_contents(map);
+    memset(map->map, 0, map->size*sizeof(UcxMapElement*));
+    map->count = 0;
+}
+
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) {
+    UcxMapIterator i = ucx_map_iterator(from);
+    void *value;
+    UCX_MAP_FOREACH(key, value, i) {
+        if (ucx_map_put(to, key, fnc ? fnc(value, data) : value)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) {
+    return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data);
+}
+
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+        UcxMap const *map, copy_func fnc, void *data) {
+    size_t bs = (map->count * 5) >> 1;
+    UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size);
+    if (!newmap) {
+        return NULL;
+    }
+    ucx_map_copy(map, newmap, fnc, data);
+    return newmap;
+}
+
+int ucx_map_rehash(UcxMap *map) {
+    size_t load = (map->size * 3) >> 2;
+    if (map->count > load) {
+        UcxMap oldmap;
+        oldmap.map = map->map;
+        oldmap.size = map->size;
+        oldmap.count = map->count;
+        oldmap.allocator = map->allocator;
+        
+        map->size = (map->count * 5) >> 1;
+        map->map = (UcxMapElement**)alcalloc(
+                map->allocator, map->size, sizeof(UcxMapElement*));
+        if (!map->map) {
+            *map = oldmap;
+            return 1;
+        }
+        map->count = 0;
+        ucx_map_copy(&oldmap, map, NULL, NULL);
+        
+        /* free the UcxMapElement list of oldmap */
+        ucx_map_free_elmlist_contents(&oldmap);
+        alfree(map->allocator, oldmap.map);
+    }
+    return 0;
+}
+
+int ucx_map_put(UcxMap *map, UcxKey key, void *data) {
+    UcxAllocator *allocator = map->allocator;
+    
+    if (key.hash == 0) {
+        key.hash = ucx_hash((const char*)key.data, key.len);
+    }
+    
+    struct UcxMapKey mapkey;
+    mapkey.hash = key.hash;
+
+    size_t slot = mapkey.hash%map->size;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *prev = NULL;
+
+    while (elm && elm->key.hash < mapkey.hash) {
+        prev = elm;
+        elm = elm->next;
+    }
+    
+    if (!elm || elm->key.hash != mapkey.hash) {
+        UcxMapElement *e = (UcxMapElement*)almalloc(
+                allocator, sizeof(UcxMapElement));
+        if (!e) {
+            return -1;
+        }
+        e->key.data = NULL;
+        if (prev) {
+            prev->next = e;
+        } else {
+            map->map[slot] = e;
+        }
+        e->next = elm;
+        elm = e;
+    }
+    
+    if (!elm->key.data) {
+        void *kd = almalloc(allocator, key.len);
+        if (!kd) {
+            return -1;
+        }
+        memcpy(kd, key.data, key.len);
+        mapkey.data = kd;
+        mapkey.len = key.len;
+        elm->key = mapkey;
+        map->count++;
+    }
+    elm->data = data;
+
+    return 0;
+}
+
+static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) {
+    if(key.hash == 0) {
+        key.hash = ucx_hash((const char*)key.data, key.len);
+    }
+    
+    size_t slot = key.hash%map->size;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *pelm = NULL;
+    while (elm && elm->key.hash <= key.hash) {
+        if(elm->key.hash == key.hash) {
+            int n = (key.len > elm->key.len) ? elm->key.len : key.len;
+            if (memcmp(elm->key.data, key.data, n) == 0) {
+                void *data = elm->data;
+                if (remove) {
+                    if (pelm) {
+                        pelm->next = elm->next;
+                    } else {
+                        map->map[slot] = elm->next;
+                    }
+                    alfree(map->allocator, elm->key.data);
+                    alfree(map->allocator, elm);
+                    map->count--;
+                }
+
+                return data;
+            }
+        }
+        pelm = elm;
+        elm = pelm->next;
+    }
+
+    return NULL;
+}
+
+void *ucx_map_get(UcxMap const *map, UcxKey key) {
+    return ucx_map_get_and_remove((UcxMap *)map, key, 0);
+}
+
+void *ucx_map_remove(UcxMap *map, UcxKey key) {
+    return ucx_map_get_and_remove(map, key, 1);
+}
+
+UcxKey ucx_key(const void *data, size_t len) {
+    UcxKey key;
+    key.data = data;
+    key.len = len;
+    key.hash = ucx_hash((const char*)data, len);
+    return key;
+}
+
+
+int ucx_hash(const char *data, size_t len) {
+    /* murmur hash 2 */
+
+    int m = 0x5bd1e995;
+    int r = 24;
+
+    int h = 25 ^ len;
+
+    int i = 0;
+    while (len >= 4) {
+        int 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;
+        /* no break */
+        case 2: h ^= (data[i + 1] & 0xFF) << 8;
+        /* no break */
+        case 1: h ^= (data[i + 0] & 0xFF); h *= m;
+        /* no break */
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    return h;
+}
+
+UcxMapIterator ucx_map_iterator(UcxMap const *map) {
+    UcxMapIterator i;
+    i.map = map;
+    i.cur = NULL;
+    i.index = 0;
+    return i;
+}
+
+int ucx_map_iter_next(UcxMapIterator *i, UcxKey *key, void **elm) {
+    UcxMapElement *e = i->cur;
+    
+    if (e) {
+        e = e->next;
+    } else {
+        e = i->map->map[0];
+    }
+    
+    while (i->index < i->map->size) {
+        if (e) {
+            if (e->data) {
+                i->cur = e;
+                *elm = e->data;
+                key->data = e->key.data;
+                key->hash = e->key.hash;
+                key->len = e->key.len;
+                return 1;
+            }
+
+            e = e->next;
+        } else {
+            i->index++;
+            
+            if (i->index < i->map->size) {
+                e = i->map->map[i->index];
+            }
+        }
+    }
+    
+    return 0;
+}
+
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata) {
+    return ucx_map_union_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata) {
+    UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata);
+    ucx_map_copy(second, result, cpfnc, cpdata);
+    return result;
+}
+
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+    return ucx_map_intersection_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata) {
+    UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ?
+            first->size : second->size);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    return result;
+}
+
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata) {
+    return ucx_map_difference_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+
+    UcxMap *result = ucx_map_new_a(allocator, first->size - second->count);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (!ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    ucx_map_rehash(result);
+    return result;
+}
\ No newline at end of file
diff --git a/ucx/mempool.c b/ucx/mempool.c
new file mode 100644 (file)
index 0000000..beedc31
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * 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/mempool.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#ifdef __cplusplus
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+
+/** Capsule for destructible memory chunks. */
+typedef struct {
+    /** The destructor for the memory chunk. */
+    ucx_destructor destructor;
+    /**
+     * First byte of the memory chunk.
+     * Note, that the address <code>&amp;c</code> is also the address
+     * of the whole memory chunk.
+     */
+    char c;
+} ucx_memchunk;
+
+/** Capsule for data and its destructor. */
+typedef struct {
+    /** The destructor for the data. */
+    ucx_destructor destructor;
+    /** A pointer to the data. */
+    void           *ptr;
+} ucx_regdestr;
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void ucx_mempool_shared_destr(void* ptr) {
+    ucx_regdestr *rd = (ucx_regdestr*)ptr;
+    rd->destructor(rd->ptr);
+}
+
+UcxMempool *ucx_mempool_new(size_t n) {
+    size_t poolsz;
+    if(ucx_szmul(n, sizeof(void*), &poolsz)) {
+        return NULL;
+    }
+    
+    UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool));
+    if (!pool) {
+        return NULL;
+    }
+    
+    pool->data = (void**) malloc(poolsz);
+    if (pool->data == NULL) {
+        free(pool);
+        return NULL;
+    }
+    
+    pool->ndata = 0;
+    pool->size = n;
+    
+    UcxAllocator *allocator = (UcxAllocator*)malloc(sizeof(UcxAllocator));
+    if(!allocator) {
+        free(pool->data);
+        free(pool);
+        return NULL;
+    }
+    allocator->malloc = (ucx_allocator_malloc)ucx_mempool_malloc;
+    allocator->calloc = (ucx_allocator_calloc)ucx_mempool_calloc;
+    allocator->realloc = (ucx_allocator_realloc)ucx_mempool_realloc;
+    allocator->free = (ucx_allocator_free)ucx_mempool_free;
+    allocator->pool = pool;
+    pool->allocator = allocator;
+    
+    return pool;
+}
+
+int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) {
+    if (newcap < pool->ndata) {
+        return 1;
+    }
+    
+    size_t newcapsz;
+    if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) {
+        return 1;
+    }
+    
+    void **data = (void**) realloc(pool->data, newcapsz);
+    if (data) {
+        pool->data = data; 
+        pool->size = newcap;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+void *ucx_mempool_malloc(UcxMempool *pool, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
+    if (pool->ndata >= pool->size) {
+        size_t newcap = pool->size*2;
+        if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) {
+            return NULL;
+        }
+    }
+
+    void *p = malloc(sizeof(ucx_destructor) + n);
+    ucx_memchunk *mem = (ucx_memchunk*)p;
+    if (!mem) {
+        return NULL;
+    }
+
+    mem->destructor = NULL;
+    pool->data[pool->ndata] = mem;
+    pool->ndata++;
+
+    return &(mem->c);
+}
+
+void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) {
+    size_t msz;
+    if(ucx_szmul(nelem, elsize, &msz)) {
+        return NULL;
+    }
+    
+    void *ptr = ucx_mempool_malloc(pool, msz);
+    if (!ptr) {
+        return NULL;
+    }
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
+    char *mem = ((char*)ptr) - sizeof(ucx_destructor);
+    char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor));
+    if (!newm) {
+        return NULL;
+    }
+    if (mem != newm) {
+        for(size_t i=0 ; i < pool->ndata ; i++) {
+            if(pool->data[i] == mem) {
+                pool->data[i] = newm;
+                return newm + sizeof(ucx_destructor);
+            }
+        }
+        fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
+          (intptr_t)ptr, (intptr_t)pool);
+        abort();
+    } else {
+        return newm + sizeof(ucx_destructor);
+    }
+}
+
+void ucx_mempool_free(UcxMempool *pool, void *ptr) {
+    ucx_memchunk *chunk = (ucx_memchunk*)((char*)ptr-sizeof(ucx_destructor));
+    for(size_t i=0 ; i<pool->ndata ; i++) {
+        if(chunk == pool->data[i]) {
+            if(chunk->destructor != NULL) {
+                chunk->destructor(&(chunk->c));
+            }
+            free(chunk);
+            size_t last_index = pool->ndata - 1;
+            if(i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->ndata--;
+            return;
+        }
+    }
+    fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
+            (intptr_t)ptr, (intptr_t)pool);
+    abort();
+}
+
+void ucx_mempool_destroy(UcxMempool *pool) {
+    ucx_memchunk *chunk;
+    for(size_t i=0 ; i<pool->ndata ; i++) {
+        chunk = (ucx_memchunk*) pool->data[i];
+        if(chunk) {
+            if(chunk->destructor) {
+                chunk->destructor(&(chunk->c));
+            }
+            free(chunk);
+        }
+    }
+    free(pool->data);
+    free(pool->allocator);
+    free(pool);
+}
+
+void ucx_mempool_set_destr(void *ptr, ucx_destructor func) {
+    *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func;
+}
+
+void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) {
+    ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc(
+            pool,
+            sizeof(ucx_regdestr));
+    rd->destructor = destr;
+    rd->ptr = ptr;
+    ucx_mempool_set_destr(rd, ucx_mempool_shared_destr);
+}
+
diff --git a/ucx/properties.c b/ucx/properties.c
new file mode 100644 (file)
index 0000000..1cb4de0
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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, sstr_t *name, sstr_t *value)  {   
+    if(parser->tmplen > 0) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        sstr_t str = sstrn(buf, len);
+        sstr_t nl = sstrchr(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;
+        }
+        
+        sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i);
+        // check line
+        if(delimiter_index == 0) {
+            line = sstrtrim(line);
+            if(line.length != 0) {
+                parser->error = 1;
+            }
+        } else {
+            sstr_t n = sstrn(buf, delimiter_index);
+            sstr_t v = sstrn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1); 
+            n = sstrtrim(n);
+            v = sstrtrim(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, UcxMap *map) {
+    sstr_t name;
+    sstr_t value;
+    while(ucx_properties_next(parser, &name, &value)) {
+        value = sstrdup_a(map->allocator, value);
+        if(!value.ptr) {
+            return 1;
+        }
+        if(ucx_map_sstr_put(map, name, value.ptr)) {
+            alfree(map->allocator, value.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(UcxMap *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(UcxMap *map, FILE *file) {
+    UcxMapIterator iter = ucx_map_iterator(map);
+    void *v;
+    sstr_t value;
+    size_t written;
+
+    UCX_MAP_FOREACH(k, v, iter) {
+        value = sstr((char*)v);
+
+        written = 0;
+        written += fwrite(k.data, 1, k.len, file);
+        written += fwrite(" = ", 1, 3, file);
+        written += fwrite(value.ptr, 1, value.length, file);
+        written += fwrite("\n", 1, 1, file);
+
+        if (written != k.len + value.length + 4) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
diff --git a/ucx/stack.c b/ucx/stack.c
new file mode 100644 (file)
index 0000000..467233e
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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/stack.h"
+
+#include <string.h>
+
+static size_t ucx_stack_align(size_t n) {
+    int align = n % sizeof(void*);
+    if (align) {
+        n += sizeof(void*) - align;
+    }
+    return n;
+}
+
+void ucx_stack_init(UcxStack *stack, char* space, size_t size) {
+    stack->size = size - size % sizeof(void*);
+    stack->space = space;
+    stack->top = NULL;
+    
+    stack->allocator.pool = stack;
+    stack->allocator.malloc = (ucx_allocator_malloc) ucx_stack_malloc;
+    stack->allocator.calloc = (ucx_allocator_calloc) ucx_stack_calloc;
+    stack->allocator.realloc = (ucx_allocator_realloc) ucx_stack_realloc;
+    stack->allocator.free = (ucx_allocator_free) ucx_stack_free;
+}
+
+void *ucx_stack_malloc(UcxStack *stack, size_t n) {
+
+    if (ucx_stack_avail(stack) < ucx_stack_align(n)) {
+        return NULL;
+    } else {
+        char *prev = stack->top;
+        if (stack->top) {
+            stack->top += ucx_stack_align(ucx_stack_topsize(stack));
+        } else {
+            stack->top = stack->space;
+        }
+        
+        ((struct ucx_stack_metadata*)stack->top)->prev = prev;
+        ((struct ucx_stack_metadata*)stack->top)->size = n;
+        stack->top += sizeof(struct ucx_stack_metadata);
+        
+        return stack->top;
+    }
+}
+
+void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize) {
+    void *mem = ucx_stack_malloc(stack, nelem*elsize);
+    memset(mem, 0, nelem*elsize);
+    return mem;
+}
+
+void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n) {
+    if (ptr == stack->top) {
+        if (stack->size - (stack->top - stack->space) < ucx_stack_align(n)) {
+            return NULL;
+        } else {
+            ((struct ucx_stack_metadata*)stack->top - 1)->size = n;
+            return ptr;
+        }
+    } else {
+        if (ucx_stack_align(((struct ucx_stack_metadata*)ptr - 1)->size) <
+                ucx_stack_align(n)) {
+            void *nptr = ucx_stack_malloc(stack, n);
+            if (nptr) {
+                memcpy(nptr, ptr, n);
+                ucx_stack_free(stack, ptr);
+                
+                return nptr;
+            } else {
+                return NULL;
+            }
+        } else {
+            ((struct ucx_stack_metadata*)ptr - 1)->size = n;
+            return ptr;
+        }
+    }
+}
+
+void ucx_stack_free(UcxStack *stack, void *ptr) {
+    if (ptr == stack->top) {
+        stack->top = ((struct ucx_stack_metadata*) stack->top - 1)->prev;
+    } else {
+        struct ucx_stack_metadata *next = (struct ucx_stack_metadata*)(
+            (char*)ptr +
+            ucx_stack_align(((struct ucx_stack_metadata*) ptr - 1)->size)
+        );
+        next->prev = ((struct ucx_stack_metadata*) ptr - 1)->prev;
+    }
+}
+
+void ucx_stack_popn(UcxStack *stack, void *dest, size_t n) {
+    if (ucx_stack_empty(stack)) {
+        return;
+    }
+    
+    if (dest) {
+        size_t len = ucx_stack_topsize(stack);
+        if (len > n) {
+            len = n;
+        }
+
+        memcpy(dest, stack->top, len);
+    }
+    
+    ucx_stack_free(stack, stack->top);
+}
+
+size_t ucx_stack_avail(UcxStack *stack) {
+    size_t avail = ((stack->top ? (stack->size
+                    - (stack->top - stack->space)
+                    - ucx_stack_align(ucx_stack_topsize(stack)))
+                    : stack->size));
+    
+    if (avail > sizeof(struct ucx_stack_metadata)) {
+        return avail - sizeof(struct ucx_stack_metadata);
+    } else {
+        return 0;
+    }
+}
+
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) {
+    void *space = ucx_stack_malloc(stack, n);
+    if (space) {
+        memcpy(space, data, n);
+    }
+    return space;
+}
+
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data) {
+    
+    // skip the memset by using malloc
+    void *space = ucx_stack_malloc(stack, nelem*elsize);
+    if (space) {
+        memcpy(space, data, nelem*elsize);
+    }
+    return space;
+}
diff --git a/ucx/string.c b/ucx/string.c
new file mode 100644 (file)
index 0000000..5ea54f3
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ * 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/string.h"
+
+#include "ucx/allocator.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+#include <strings.h> /* for strncasecmp() */
+#endif /* _WIN32 */
+
+sstr_t sstr(char *cstring) {
+    sstr_t string;
+    string.ptr = cstring;
+    string.length = strlen(cstring);
+    return string;
+}
+
+sstr_t sstrn(char *cstring, size_t length) {
+    sstr_t string;
+    string.ptr = cstring;
+    string.length = length;
+    return string;
+}
+
+scstr_t scstr(const char *cstring) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = strlen(cstring);
+    return string;
+}
+
+scstr_t scstrn(const char *cstring, size_t length) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = length;
+    return string;
+}
+
+
+size_t scstrnlen(size_t n, ...) {
+    if (n == 0) return 0;
+    
+    va_list ap;
+    va_start(ap, n);
+    
+    size_t size = 0;
+
+    for (size_t i = 0 ; i < n ; i++) {
+        scstr_t str = va_arg(ap, scstr_t);
+        if(SIZE_MAX - str.length < size) {
+            size = SIZE_MAX;
+            break;
+        }
+        size += str.length;
+    }
+    va_end(ap);
+
+    return size;
+}
+
+static sstr_t sstrvcat_a(
+        UcxAllocator *a,
+        size_t count,
+        scstr_t s1,
+        va_list ap) {
+    sstr_t str;
+    str.ptr = NULL;
+    str.length = 0;
+    if(count < 2) {
+        return str;
+    }
+    
+    scstr_t s2 = va_arg (ap, scstr_t);
+    
+    if(((size_t)-1) - s1.length < s2.length) {
+        return str;
+    }
+    
+    scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t));
+    if(!strings) {
+        return str;
+    }
+    
+    // get all args and overall length
+    strings[0] = s1;
+    strings[1] = s2;
+    size_t slen = s1.length + s2.length;
+    int error = 0;
+    for (size_t i=2;i<count;i++) {
+        scstr_t s = va_arg (ap, scstr_t);
+        strings[i] = s;
+        if(((size_t)-1) - s.length < slen) {
+            error = 1;
+            break;
+        }
+        slen += s.length;
+    }
+    if(error) {
+        free(strings);
+        return str;
+    }
+    
+    // create new string
+    str.ptr = (char*) almalloc(a, slen + 1);
+    str.length = slen;
+    if(!str.ptr) {
+        free(strings);
+        str.length = 0;
+        return str;
+    }
+    
+    // concatenate strings
+    size_t pos = 0;
+    for (size_t i=0;i<count;i++) {
+        scstr_t s = strings[i];
+        memcpy(str.ptr + pos, s.ptr, s.length);
+        pos += s.length;
+    }
+    
+    str.ptr[str.length] = '\0';
+    
+    free(strings);
+    
+    return str;
+}
+
+sstr_t scstrcat(size_t count, scstr_t s1, ...) {
+    va_list ap;
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(ucx_default_allocator(), count, s1, ap);
+    va_end(ap);
+    return s;
+}
+
+sstr_t scstrcat_a(UcxAllocator *a, size_t count, scstr_t s1, ...) {
+    va_list ap;
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(a, count, s1, ap);
+    va_end(ap);
+    return s;
+}
+
+static int ucx_substring(
+        size_t str_length,
+        size_t start,
+        size_t length,
+        size_t *newlen,
+        size_t *newpos)
+{
+    *newlen = 0;
+    *newpos = 0;
+    
+    if(start > str_length) {
+        return 0;
+    }
+    
+    if(length > str_length - start) {
+        length = str_length - start;
+    }
+    *newlen = length;
+    *newpos = start;
+    return 1;
+}
+
+sstr_t sstrsubs(sstr_t s, size_t start) {
+    return sstrsubsl (s, start, s.length-start);
+}
+
+sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) {
+    size_t pos;
+    sstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+scstr_t scstrsubs(scstr_t string, size_t start) {
+    return scstrsubsl(string, start, string.length-start);
+}
+
+scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) {
+    size_t pos;
+    scstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+
+static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) {
+    for(size_t i=0;i<length;i++) {
+        if(str[i] == chr) {
+            *pos = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int ucx_strrchr(const char *str, size_t length, int chr, size_t *pos) {
+    if(length > 0) {
+        for(size_t i=length ; i>0 ; i--) {
+            if(str[i-1] == chr) {
+                *pos = i-1;
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+sstr_t sstrchr(sstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
+    }
+    return sstrn(NULL, 0);
+}
+
+sstr_t sstrrchr(sstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
+    }
+    return sstrn(NULL, 0);
+}
+
+scstr_t scstrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
+}
+
+scstr_t scstrrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
+}
+
+#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \
+    ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index])
+
+#define ptable_w(useheap, ptable, index, src) do {\
+    if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\
+    else ((size_t*)ptable)[index] = src;\
+    } while (0);
+
+
+static const char* ucx_strstr(
+        const char *str,
+        size_t length,
+        const char *match,
+        size_t matchlen,
+        size_t *newlen)
+{
+    *newlen = length;
+    if (matchlen == 0) {
+        return str;
+    }
+    
+    const char *result = NULL;
+    size_t resultlen = 0;
+    
+    /*
+     * 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
+     */
+    
+    /* static prefix table */
+    static uint8_t s_prefix_table[256];
+    
+    /* check pattern length and use appropriate prefix table */
+    /* if the pattern exceeds static prefix table, allocate on the heap */
+    register int useheap = matchlen > 255;
+    register void* ptable = useheap ?
+        calloc(matchlen+1, sizeof(size_t)): s_prefix_table;
+    
+    /* keep counter in registers */
+    register size_t i, j;
+    
+    /* fill prefix table */
+    i = 0; j = 0;
+    ptable_w(useheap, ptable, i, j);
+    while (i < matchlen) {
+        while (j >= 1 && match[j-1] != match[i]) {
+            ptable_r(j, useheap, ptable, j-1);
+        }
+        i++; j++;
+        ptable_w(useheap, ptable, i, j);
+    }
+
+    /* search */
+    i = 0; j = 1;
+    while (i < length) {
+        while (j >= 1 && str[i] != match[j-1]) {
+            ptable_r(j, useheap, ptable, j-1);
+        }
+        i++; j++;
+        if (j-1 == matchlen) {
+            size_t start = i - matchlen;
+            result = str + start;
+            resultlen = length - start;
+            break;
+        }
+    }
+
+    /* if prefix table was allocated on the heap, free it */
+    if (ptable != s_prefix_table) {
+        free(ptable);
+    }
+    
+    *newlen = resultlen;
+    return result;
+}
+
+sstr_t scstrsstr(sstr_t string, scstr_t match) {
+    sstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
+}
+
+scstr_t scstrscstr(scstr_t string, scstr_t match) {
+    scstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
+}
+
+#undef ptable_r
+#undef ptable_w
+
+sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) {
+    return scstrsplit_a(ucx_default_allocator(), s, d, n);
+}
+
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) {
+    if (s.length == 0 || d.length == 0) {
+        *n = -1;
+        return NULL;
+    }
+    
+    /* special cases: delimiter is at least as large as the string */
+    if (d.length >= s.length) {
+        /* exact match */
+        if (sstrcmp(s, d) == 0) {
+            *n = 0;
+            return NULL;
+        } else /* no match possible */ {
+            *n = 1;
+            sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t));
+            if(result) {
+                *result = sstrdup_a(allocator, s);
+            } else {
+                *n = -2;
+            }
+            return result;
+        }
+    }
+    
+    ssize_t nmax = *n;
+    size_t arrlen = 16;
+    sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t));
+
+    if (result) {
+        scstr_t curpos = s;
+        ssize_t j = 1;
+        while (1) {
+            scstr_t match;
+            /* optimize for one byte delimiters */
+            if (d.length == 1) {
+                match = curpos;
+                for (size_t i = 0 ; i < curpos.length ; i++) {
+                    if (curpos.ptr[i] == *(d.ptr)) {
+                        match.ptr = curpos.ptr + i;
+                        break;
+                    }
+                    match.length--;
+                }
+            } else {
+                match = scstrscstr(curpos, d);
+            }
+            if (match.length > 0) {
+                /* is this our last try? */
+                if (nmax == 0 || j < nmax) {
+                    /* copy the current string to the array */
+                    scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr);
+                    result[j-1] = sstrdup_a(allocator, item);
+                    size_t processed = item.length + d.length;
+                    curpos.ptr += processed;
+                    curpos.length -= processed;
+
+                    /* allocate memory for the next string */
+                    j++;
+                    if (j > arrlen) {
+                        arrlen *= 2;
+                        size_t reallocsz;
+                        sstr_t* reallocated = NULL;
+                        if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) {
+                            reallocated = (sstr_t*) alrealloc(
+                                    allocator, result, reallocsz);
+                        }
+                        if (reallocated) {
+                            result = reallocated;
+                        } else {
+                            for (ssize_t i = 0 ; i < j-1 ; i++) {
+                                alfree(allocator, result[i].ptr);
+                            }
+                            alfree(allocator, result);
+                            *n = -2;
+                            return NULL;
+                        }
+                    }
+                } else {
+                    /* nmax reached, copy the _full_ remaining string */
+                    result[j-1] = sstrdup_a(allocator, curpos);
+                    break;
+                }
+            } else {
+                /* no more matches, copy last string */
+                result[j-1] = sstrdup_a(allocator, curpos);
+                break;
+            }
+        }
+        *n = j;
+    } else {
+        *n = -2;
+    }
+
+    return result;
+}
+
+int scstrcmp(scstr_t s1, scstr_t 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 scstrcasecmp(scstr_t s1, scstr_t 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;
+    }
+}
+
+sstr_t scstrdup(scstr_t s) {
+    return sstrdup_a(ucx_default_allocator(), s);
+}
+
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) {
+    sstr_t newstring;
+    newstring.ptr = (char*)almalloc(allocator, s.length + 1);
+    if (newstring.ptr) {
+        newstring.length = s.length;
+        newstring.ptr[newstring.length] = 0;
+        
+        memcpy(newstring.ptr, s.ptr, s.length);
+    } else {
+        newstring.length = 0;
+    }
+    
+    return newstring;
+}
+
+
+static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) {
+    const char *newptr = s;
+    size_t length = len;
+    
+    while(length > 0 && isspace(*newptr)) {
+        newptr++;
+        length--;
+    }
+    while(length > 0 && isspace(newptr[length-1])) {
+        length--;
+    }
+    
+    *newlen = length;
+    return newptr - s;
+}
+
+sstr_t sstrtrim(sstr_t string) {
+    sstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
+    return newstr;
+}
+
+scstr_t scstrtrim(scstr_t string) {
+    scstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
+    return newstr;
+}
+
+int scstrprefix(scstr_t string, scstr_t prefix) {
+    if (string.length == 0) {
+        return prefix.length == 0;
+    }
+    if (prefix.length == 0) {
+        return 1;
+    }
+    
+    if (prefix.length > string.length) {
+        return 0;
+    } else {
+        return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+    }
+}
+
+int scstrsuffix(scstr_t string, scstr_t suffix) {
+    if (string.length == 0) {
+        return suffix.length == 0;
+    }
+    if (suffix.length == 0) {
+        return 1;
+    }
+    
+    if (suffix.length > string.length) {
+        return 0;
+    } else {
+        return memcmp(string.ptr+string.length-suffix.length,
+            suffix.ptr, suffix.length) == 0;
+    }
+}
+
+int scstrcaseprefix(scstr_t string, scstr_t prefix) {
+    if (string.length == 0) {
+        return prefix.length == 0;
+    }
+    if (prefix.length == 0) {
+        return 1;
+    }
+    
+    if (prefix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubsl(string, 0, prefix.length);
+        return scstrcasecmp(subs, prefix) == 0;
+    }
+}
+
+int scstrcasesuffix(scstr_t string, scstr_t suffix) {
+    if (string.length == 0) {
+        return suffix.length == 0;
+    }
+    if (suffix.length == 0) {
+        return 1;
+    }
+    
+    if (suffix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubs(string, string.length-suffix.length);
+        return scstrcasecmp(subs, suffix) == 0;
+    }
+}
+
+sstr_t scstrlower(scstr_t string) {
+    sstr_t ret = sstrdup(string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = tolower(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) {
+    sstr_t ret = sstrdup_a(allocator, string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = tolower(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrupper(scstr_t string) {
+    sstr_t ret = sstrdup(string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = toupper(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) {
+    sstr_t ret = sstrdup_a(allocator, string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = toupper(ret.ptr[i]);
+    }
+    return ret;
+}
+
+#define REPLACE_INDEX_BUFFER_MAX 100
+
+struct scstrreplace_ibuf {
+    size_t* buf;
+    unsigned int len; /* small indices */
+    struct scstrreplace_ibuf* next;
+};
+
+static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) {
+    while (buf) {
+        struct scstrreplace_ibuf *next = buf->next;
+        free(buf->buf);
+        free(buf);
+        buf = next;
+    }
+}
+
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+                     scstr_t pattern, scstr_t replacement, size_t replmax) {
+
+    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+        return sstrdup(str);
+
+    /* Compute expected buffer length */
+    size_t ibufmax = str.length / pattern.length;
+    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+    if (ibuflen > REPLACE_INDEX_BUFFER_MAX) {
+        ibuflen = REPLACE_INDEX_BUFFER_MAX;
+    }
+
+    /* Allocate first index buffer */
+    struct scstrreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf));
+    if (!firstbuf) return sstrn(NULL, 0);
+    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+    if (!firstbuf->buf) {
+        free(firstbuf);
+        return sstrn(NULL, 0);
+    }
+
+    /* Search occurrences */
+    scstr_t searchstr = str;
+    size_t found = 0;
+    do {
+        scstr_t match = scstrscstr(searchstr, pattern);
+        if (match.length > 0) {
+            /* Allocate next buffer in chain, if required */
+            if (curbuf->len == ibuflen) {
+                struct scstrreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct scstrreplace_ibuf));
+                if (!nextbuf) {
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(NULL, 0);
+                }
+                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+                if (!nextbuf->buf) {
+                    free(nextbuf);
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(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 */
+    sstr_t 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 = almalloc(allocator, result.length);
+        if (!result.ptr) {
+            scstrrepl_free_ibuf(firstbuf);
+            return sstrn(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);
+
+    /* Free index buffer */
+    scstrrepl_free_ibuf(firstbuf);
+
+    return result;
+}
+
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t replacement, size_t replmax) {
+    return scstrreplacen_a(ucx_default_allocator(),
+            str, pattern, replacement, replmax);
+}
+
+
+// type adjustment functions
+scstr_t ucx_sc2sc(scstr_t str) {
+    return str;
+}
+scstr_t ucx_ss2sc(sstr_t str) {
+    scstr_t cs;
+    cs.ptr = str.ptr;
+    cs.length = str.length;
+    return cs;
+}
+scstr_t ucx_ss2c_s(scstr_t c) {
+    return c;
+}
diff --git a/ucx/test.c b/ucx/test.c
new file mode 100644 (file)
index 0000000..20b80b4
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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/test.h"
+
+UcxTestSuite* ucx_test_suite_new() {
+    UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite));
+    if (suite != NULL) {
+        suite->success = 0;
+        suite->failure = 0;
+        suite->tests = NULL;
+    }
+
+    return suite;
+}
+
+void ucx_test_suite_free(UcxTestSuite* suite) {
+    UcxTestList *l = suite->tests;
+    while (l != NULL) {
+        UcxTestList *e = l;
+        l = l->next;
+        free(e);
+    }
+    free(suite);
+}
+
+int ucx_test_register(UcxTestSuite* suite, UcxTest test) {
+    if (suite->tests) {
+        UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList));
+        if (newelem) {
+            newelem->test = test;
+            newelem->next = NULL;
+            
+            UcxTestList *last = suite->tests;
+            while (last->next) {
+                last = last->next;
+            }
+            last->next = newelem;
+            
+            return EXIT_SUCCESS;
+        } else {
+            return EXIT_FAILURE;
+        }
+    } else {
+        suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList));
+        if (suite->tests) {
+            suite->tests->test = test;
+            suite->tests->next = NULL;
+            
+            return EXIT_SUCCESS;
+        } else {
+            return EXIT_FAILURE;
+        }
+    }
+}
+
+void ucx_test_run(UcxTestSuite* suite, FILE* output) {
+    suite->success = 0;
+    suite->failure = 0;
+    for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) {
+        elem->test(suite, output);
+    }
+    fwrite("\nAll test completed.\n", 1, 21, output);
+    fprintf(output, "  Total:   %u\n  Success: %u\n  Failure: %u\n",
+            suite->success+suite->failure, suite->success, suite->failure);
+}
diff --git a/ucx/ucx.c b/ucx/ucx.c
new file mode 100644 (file)
index 0000000..923330d
--- /dev/null
+++ b/ucx/ucx.c
@@ -0,0 +1,62 @@
+/**
+ * @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 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/ucx.h"
+
+int ucx_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/ucx/allocator.h b/ucx/ucx/allocator.h
new file mode 100644 (file)
index 0000000..2159272
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+/**
+ * Allocator for custom memory management.
+ * 
+ * A UCX allocator consists of a pointer to the memory area / pool and four
+ * function pointers to memory management functions operating on this memory
+ * area / pool. These functions shall behave equivalent to the standard libc
+ * functions <code>malloc(), calloc(), realloc()</code> and <code>free()</code>.
+ * 
+ * The signature of the memory management functions is based on the signature
+ * of the respective libc function but each of them takes the pointer to the
+ * memory area / pool as first argument.
+ * 
+ * As the pointer to the memory area / pool can be arbitrarily chosen, any data
+ * can be provided to the memory management functions. A UcxMempool is just
+ * one example.
+ * 
+ * @see mempool.h
+ * @see UcxMap
+ * 
+ * @file   allocator.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define        UCX_ALLOCATOR_H
+
+#include "ucx.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A function pointer to the allocators <code>malloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_malloc)(void *pool, size_t n);
+
+/**
+ * A function pointer to the allocators <code>calloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size);
+
+/**
+ * A function pointer to the allocators <code>realloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n);
+
+/**
+ * A function pointer to the allocators <code>free()</code> function.
+ * @see UcxAllocator
+ */
+typedef void(*ucx_allocator_free)(void *pool, void *data);
+
+/**
+ * UCX allocator data structure containing memory management functions.
+ */
+typedef struct {
+    /** Pointer to an area of memory or a complex memory pool.
+     * This pointer will be passed to any memory management function as first
+     * argument.
+     */
+    void *pool;
+    /**
+     * The <code>malloc()</code> function for this allocator.
+     */
+    ucx_allocator_malloc  malloc;
+    /**
+     * The <code>calloc()</code> function for this allocator.
+     */
+    ucx_allocator_calloc  calloc;
+    /**
+     * The <code>realloc()</code> function for this allocator.
+     */
+    ucx_allocator_realloc realloc;
+    /**
+     * The <code>free()</code> function for this allocator.
+     */
+    ucx_allocator_free    free;
+} UcxAllocator;
+
+/**
+ * Returns a pointer to the default allocator.
+ * 
+ * The default allocator contains wrappers to the standard libc memory
+ * management functions. Use this function to get a pointer to a globally
+ * available allocator. You may also define an own UcxAllocator by assigning
+ * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable
+ * to any function that takes a UcxAllocator as argument. Note that using
+ * this function is the recommended way of passing a default allocator, thus
+ * it never runs out of scope.
+ * 
+ * @return a pointer to the default allocator
+ * 
+ * @see UCX_ALLOCATOR_DEFAULT
+ */
+UcxAllocator *ucx_default_allocator();
+
+/**
+ * A wrapper for the standard libc <code>malloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>malloc()</code>
+ * @return return value of <code>malloc()</code>
+ */
+void *ucx_default_malloc(void *ignore, size_t n);
+/**
+ * A wrapper for the standard libc <code>calloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>calloc()</code>
+ * @param size  argument passed to <code>calloc()</code>
+ * @return return value of <code>calloc()</code>
+ */
+void *ucx_default_calloc(void *ignore, size_t n, size_t size);
+/**
+ * A wrapper for the standard libc <code>realloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argumend passed to <code>realloc()</code>
+ * @param n argument passed to <code>realloc()</code>
+ * @return return value of <code>realloc()</code>
+ */
+void *ucx_default_realloc(void *ignore, void *data, size_t n);
+/**
+ * A wrapper for the standard libc <code>free()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argument passed to <code>free()</code>
+ */
+void ucx_default_free(void *ignore, void *data);
+
+/**
+ * Shorthand for calling an allocators malloc function.
+ * @param allocator the allocator to use
+ * @param n size of space to allocate
+ * @return a pointer to the allocated memory area
+ */
+#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n))
+
+/**
+ * Shorthand for calling an allocators calloc function.
+ * @param allocator the allocator to use
+ * @param n the count of elements the space should be allocated for
+ * @param size the size of each element
+ * @return a pointer to the allocated memory area
+ */
+#define alcalloc(allocator, n, size) \
+        ((allocator)->calloc((allocator)->pool, n, size))
+
+/**
+ * Shorthand for calling an allocators realloc function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be reallocated
+ * @param n the new size of the allocated memory area
+ * @return a pointer to the reallocated memory area
+ */
+#define alrealloc(allocator, ptr, n) \
+        ((allocator)->realloc((allocator)->pool, ptr, n))
+
+/**
+ * Shorthand for calling an allocators free function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be freed
+ */
+#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr))
+
+/**
+ * Convenient macro for a default allocator <code>struct</code> definition.
+ */
+#define UCX_ALLOCATOR_DEFAULT {NULL, \
+        ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \
+        ucx_default_free }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_ALLOCATOR_H */
+
diff --git a/ucx/ucx/array.h b/ucx/ucx/array.h
new file mode 100644 (file)
index 0000000..5b02ebf
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 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.
+ */
+/**
+ * Dynamically allocated array implementation.
+ * 
+ * @file   array.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ARRAY_H
+#define        UCX_ARRAY_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX array type.
+ */
+typedef struct {
+    /**
+     * The current capacity of the array.
+     */
+    size_t capacity;
+    /**
+     * The actual number of elements in the array.
+     */
+    size_t size;
+    /**
+     * The size of an individual element in bytes.
+     */
+    size_t elemsize;
+    /**
+     * A pointer to the data.
+     */
+    void* data;
+    /**
+     * The allocator used for the data.
+     */
+    UcxAllocator* allocator;
+} UcxArray;
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_set(array, capacity, elmsize, idx, data) \
+    ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \
+                         elmsize, idx, data)
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t idx, void* data);
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_setptr(array, capacity, idx, ptr) \
+    ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \
+                            capacity, idx, ptr)
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t idx, void* ptr);
+
+
+/**
+ * Creates a new UCX array with the given capacity and element size.
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @return a pointer to a new UCX array structure
+ */
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize);
+
+/**
+ * Creates a new UCX array using the specified allocator.
+ * 
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ * @return a pointer to new UCX array structure
+ */
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Initializes a UCX array structure with the given capacity and element size.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ */
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize);
+
+/**
+ * Initializes a UCX array structure using the specified allocator.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ */
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Creates an shallow copy of an array.
+ * 
+ * This function clones the specified array by using memcpy().
+ * If the destination capacity is insufficient, an automatic reallocation is
+ * attempted.
+ * 
+ * Note: if the destination array is uninitialized, the behavior is undefined.
+ * 
+ * @param dest the array to copy to
+ * @param src the array to copy from
+ * @return zero on success, non-zero on reallocation failure.
+ */
+int ucx_array_clone(UcxArray* dest, UcxArray const* src);
+
+
+/**
+ * Compares two UCX arrays element-wise by using a compare function.
+ *
+ * Elements of the two specified arrays are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * This function always returns zero, if the element sizes of the arrays do
+ * not match and performs no comparisons in this case.
+ * 
+ * @param array1 the first array
+ * @param array2 the second array
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two arrays equal element-wise, 0 otherwise
+ */
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the array.
+ * 
+ * The data is freed and both capacity and count are reset to zero.
+ * If the array structure itself has been dynamically allocated, it has to be
+ * freed separately.
+ * 
+ * @param array the array to destroy
+ */
+void ucx_array_destroy(UcxArray *array);
+
+/**
+ * Destroys and frees the array.
+ * 
+ * @param array the array to free
+ */
+void ucx_array_free(UcxArray *array);
+
+/**
+ * Inserts elements at the end of the array.
+ * 
+ * This is an O(1) operation.
+ * The array will automatically grow, if the capacity is exceeded.
+ * If a pointer to data is provided, the data is copied into the array with
+ * memcpy(). Otherwise the new elements are completely zeroed.
+ * 
+ * @param array a pointer the array where to append the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are appended)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_set_from()
+ * @see ucx_array_append()
+ */
+int ucx_array_append_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Inserts elements at the beginning of the array.
+ * 
+ * This is an expensive operation, because the contents must be moved.
+ * If there is no particular reason to prepend data, you should use
+ * ucx_array_append_from() instead.
+ * 
+ * @param array a pointer the array where to prepend the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are inserted)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set_from()
+ * @see ucx_array_prepend()
+ */
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Sets elements starting at the specified index.
+ * 
+ * If the any index is out of bounds, the array automatically grows.
+ * The pointer to the data may be NULL, in which case the elements are zeroed. 
+ * 
+ * @param array a pointer the array where to set the data
+ * @param index the index of the element to set
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, the memory in the array is zeroed)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set()
+ */
+int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count);
+
+/**
+ * Concatenates two arrays.
+ * 
+ * The contents of the second array are appended to the first array in one
+ * single operation. The second array is otherwise left untouched.
+ * 
+ * The first array may grow automatically. If this fails, both arrays remain
+ * unmodified.
+ * 
+ * @param array1 first array
+ * @param array2 second array
+ * @return zero on success, non-zero if reallocation was necessary but failed 
+ * or the element size does not match
+ */
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2);
+
+/**
+ * Returns a pointer to the array element at the specified index.
+ * 
+ * @param array the array to retrieve the element from
+ * @param index index of the element to return
+ * @return a pointer to the element at the specified index or <code>NULL</code>,
+ * if the index is greater than the array size
+ */
+void *ucx_array_at(UcxArray const* array, size_t index);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, memcmp() is used.
+ * 
+ * If the array contains the data more than once, the index of the first
+ * occurrence is returned.
+ * If the array does not contain the data, the size of array is returned.
+ *  
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or the size of
+ * the array, if the data is not found in this array
+ */
+size_t ucx_array_find(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if an array contains a specific element.
+ * 
+ * An element is found, if ucx_array_find() returns a value less than the size.
+ * 
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the array contains the specified element data
+ * @see ucx_array_find()
+ */
+int ucx_array_contains(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxArray with the best available sort algorithm.
+ * 
+ * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS).
+ * The order of arguments is automatically adjusted for the FreeBSD and MacOS
+ * version of qsort_r().
+ * 
+ * If qsort_r() is not available, a merge sort algorithm is used, which is
+ * guaranteed to use no more additional memory than for exactly one element.
+ * 
+ * @param array the array to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func() or <code>NULL</code>
+ */
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is in general an expensive operation, because several elements may
+ * be moved. If the order of the elements is not relevant, use
+ * ucx_array_remove_fast() instead.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove(UcxArray *array, size_t index);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is an O(1) operation, but does not maintain the order of the elements.
+ * The last element in the array is moved to the location of the removed
+ * element.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove_fast(UcxArray *array, size_t index);
+
+/**
+ * Shrinks the memory to exactly fit the contents.
+ * 
+ * After this operation, the capacity equals the size.
+ * 
+ * @param array a pointer to the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_shrink(UcxArray* array);
+
+/**
+ * Sets the capacity of the array.
+ * 
+ * If the new capacity is smaller than the size of the array, the elements
+ * are removed and the size is adjusted accordingly.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the new capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_resize(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the array only, if the capacity is insufficient.
+ * 
+ * If the requested capacity is smaller than the current capacity, this
+ * function does nothing.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the guaranteed capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_reserve(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the capacity, if the specified number of elements would not fit.
+ * 
+ * A call to ucx_array_grow(array, count) is effectively the same as
+ * ucx_array_reserve(array, array->size+count).
+ * 
+ * @param array a pointer to the array
+ * @param count the number of elements that should additionally fit
+ * into the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_grow(UcxArray* array, size_t count);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_ARRAY_H */
+
diff --git a/ucx/ucx/avl.h b/ucx/ucx/avl.h
new file mode 100644 (file)
index 0000000..8b251a5
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * 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 avl.h
+ * 
+ * AVL tree implementation.
+ * 
+ * This binary search tree implementation allows average O(1) insertion and
+ * removal of elements (excluding binary search time).
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_AVL_H
+#define UCX_AVL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX AVL Node type.
+ * 
+ * @see UcxAVLNode
+ */
+typedef struct UcxAVLNode UcxAVLNode;
+
+/**
+ * UCX AVL Node.
+ */
+struct UcxAVLNode {
+    /**
+     * The key for this node.
+     */
+    intptr_t key;
+    /**
+     * Data contained by this node.
+     */
+    void *value;
+    /**
+     * The height of this (sub)-tree.
+     */
+    size_t height;
+    /**
+     * Parent node.
+     */
+    UcxAVLNode *parent;
+    /**
+     * Root node of left subtree.
+     */
+    UcxAVLNode *left;
+    /**
+     * Root node of right subtree.
+     */
+    UcxAVLNode *right;
+};
+
+/**
+ * UCX AVL Tree.
+ */
+typedef struct {
+    /**
+     * The UcxAllocator that shall be used to manage the memory for node data.
+     */
+    UcxAllocator *allocator;
+    /**
+     * Root node of the tree.
+     */
+    UcxAVLNode *root;
+    /**
+     * Compare function that shall be used to compare the UcxAVLNode keys.
+     * @see UcxAVLNode.key
+     */
+    cmp_func cmpfunc;
+    /**
+     * Custom user data.
+     * This data will also be provided to the cmpfunc.
+     */
+    void *userdata;
+} UcxAVLTree;
+
+/**
+ * Initializes a new UcxAVLTree with a default allocator.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @return a new UcxAVLTree object
+ * @see ucx_avl_new_a()
+ */
+UcxAVLTree *ucx_avl_new(cmp_func cmpfunc);
+
+/**
+ * Initializes a new UcxAVLTree with the specified allocator.
+ * 
+ * The cmpfunc should be capable of comparing two keys within this AVL tree.
+ * So if you want to use null terminated strings as keys, you could use the
+ * ucx_cmp_str() function here.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @param allocator the UcxAllocator that shall be used
+ * @return a new UcxAVLTree object
+ */
+UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator);
+
+/**
+ * Destroys a UcxAVLTree.
+ * 
+ * Note, that the contents are not automatically freed.
+ * Use may use #ucx_avl_free_content() before calling this function.
+ * 
+ * @param tree the tree to destroy
+ * @see ucx_avl_free_content()
+ */
+void ucx_avl_free(UcxAVLTree *tree);
+
+/**
+ * Frees the contents of a UcxAVLTree.
+ * 
+ * This is a convenience function that iterates over the tree and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the tree's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free the entire tree afterwards, as the contents will be invalid.
+ * 
+ * @param tree for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_avl_free()
+ */
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr);
+
+/**
+ * Macro for initializing a new UcxAVLTree with the default allocator and a
+ * ucx_cmp_ptr() compare function.
+ * 
+ * @return a new default UcxAVLTree object
+ */
+#define ucx_avl_default_new() \
+    ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator())
+
+/**
+ * Gets the node from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the node (or <code>NULL</code>, if the key is not present)
+ */
+UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Gets the value from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the value (or <code>NULL</code>, if the key is not present)
+ */
+void *ucx_avl_get(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * A mode for #ucx_avl_find_node() with the same behavior as
+ * #ucx_avl_get_node().
+ */
+#define UCX_AVL_FIND_EXACT         0
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at least
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_LOWER_BOUNDED 1
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at most
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_UPPER_BOUNDED 2
+/**
+ * A mode for #ucx_avl_find_node() finding the node with a key that is as close
+ * to the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.
+ */
+#define UCX_AVL_FIND_CLOSEST       3
+
+/**
+ * Finds a node within the tree. The following modes are supported:
+ * <ul>
+ * <li>#UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()</li>
+ * <li>#UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to
+ * the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.</li> 
+ * </ul>
+ * 
+ * The distance function provided MUST agree with the compare function of
+ * the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the node (or <code>NULL</code>, if no node can be found)
+ */
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Finds a value within the tree.
+ * See #ucx_avl_find_node() for details.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the value (or <code>NULL</code>, if no value can be found)
+ */
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * Attention: use this function only, if a possible old value does not need
+ * to be preserved.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * This is a secure function which saves the old value to the variable pointed
+ * at by oldvalue.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @param oldvalue optional: a pointer to the location where a possible old
+ * value shall be stored
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue);
+
+/**
+ * Removes a node from the AVL tree.
+ * 
+ * Note: the specified node is logically removed. The tree implementation
+ * decides which memory area is freed. In most cases the here provided node
+ * is freed, so its further use is generally undefined.
+ * 
+ * @param tree the UcxAVLTree
+ * @param node the node to remove
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * This is a secure function which saves the old key and value data from node
+ * to the variables at the location of oldkey and oldvalue (if specified), so
+ * they can be freed afterwards (if necessary).
+ * 
+ * Note: the returned key in oldkey is possibly not the same as the provided
+ * key for the lookup (in terms of memory location).
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key of the element to remove
+ * @param oldkey optional: a pointer to the location where the old key shall be
+ * stored
+ * @param oldvalue optional: a pointer to the location where the old value
+ * shall be stored
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
+        intptr_t *oldkey, void **oldvalue);
+
+/**
+ * Counts the nodes in the specified UcxAVLTree.
+ * @param tree the AVL tree
+ * @return the node count
+ */
+size_t ucx_avl_count(UcxAVLTree *tree);
+
+/**
+ * Finds the in-order predecessor of the given node.
+ * @param node an AVL node
+ * @return the in-order predecessor of the given node, or <code>NULL</code> if
+ * the given node is the in-order minimum
+ */
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node);
+
+/**
+ * Finds the in-order successor of the given node.
+ * @param node an AVL node
+ * @return the in-order successor of the given node, or <code>NULL</code> if
+ * the given node is the in-order maximum
+ */
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_AVL_H */
+
diff --git a/ucx/ucx/buffer.h b/ucx/ucx/buffer.h
new file mode 100644 (file)
index 0000000..25f659f
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * 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 buffer.h
+ * 
+ * Advanced buffer implementation.
+ * 
+ * Instances of UcxBuffer can be used to read from or to write to like one
+ * would do with a stream. This allows the use of ucx_stream_copy() to copy
+ * contents from one buffer to another.
+ * 
+ * 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
+ */
+
+#ifndef UCX_BUFFER_H
+#define        UCX_BUFFER_H
+
+#include "ucx.h"
+#include <sys/types.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define UCX_BUFFER_DEFAULT      0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents.
+ */
+#define UCX_BUFFER_AUTOFREE     0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define UCX_BUFFER_AUTOEXTEND   0x02
+
+/** UCX Buffer. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    char *space;
+    /** 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;
+    /**
+     * Flag register for buffer features.
+     * @see #UCX_BUFFER_DEFAULT
+     * @see #UCX_BUFFER_AUTOFREE
+     * @see #UCX_BUFFER_AUTOEXTEND
+     */
+    int flags;
+} UcxBuffer;
+
+/**
+ * Creates a new buffer.
+ * 
+ * <b>Note:</b> you may provide <code>NULL</code> as argument for
+ * <code>space</code>. Then this function will allocate the space and enforce
+ * the #UCX_BUFFER_AUTOFREE flag.
+ * 
+ * @param space pointer to the memory area, or <code>NULL</code> to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param flags buffer features (see UcxBuffer.flags)
+ * @return the new buffer
+ */
+UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags);
+
+/**
+ * Destroys a buffer.
+ * 
+ * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer
+ * are also freed.
+ * 
+ * @param buffer the buffer to destroy
+ */
+void ucx_buffer_free(UcxBuffer* buffer);
+
+/**
+ * Creates a new buffer and fills it with extracted content from another buffer.
+ * 
+ * <b>Note:</b> the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer.
+ * 
+ * @param src the source buffer
+ * @param start the start position of extraction
+ * @param length the count of bytes to extract (must not be zero)
+ * @param flags feature mask for the new buffer
+ * @return a new buffer containing the extraction
+ */
+UcxBuffer* ucx_buffer_extract(UcxBuffer *src,
+        size_t start, size_t length, int flags);
+
+/**
+ * A shorthand macro for the full extraction of the buffer.
+ * 
+ * @param src the source buffer
+ * @param flags feature mask for the new buffer
+ * @return a new buffer with the extracted content
+ */
+#define ucx_buffer_clone(src,flags) \
+    ucx_buffer_extract(src, 0, (src)->capacity, flags)
+
+
+/**
+ * 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 <code>shift</code> 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.
+ * 
+ * <b>Security note:</b> the shifting operation does <em>not</em> erase the
+ * previously occupied memory cells. You can easily do that manually, e.g. by
+ * calling <code>memset(buffer->space, 0, shift)</code> for a right shift or
+ * <code>memset(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
+ */
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift);
+
+/**
+ * Shifts the buffer to the right.
+ * See ucx_buffer_shift() for details.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift);
+
+/**
+ * Shifts the buffer to the left.
+ * 
+ * See ucx_buffer_shift() for details. Note, however, that this method expects
+ * a positive shift offset.
+ * 
+ * Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return always zero
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift);
+
+
+/**
+ * Moves the position of the buffer.
+ * 
+ * The new position is relative to the <code>whence</code> argument.
+ *
+ * SEEK_SET marks the start of the buffer.
+ * SEEK_CUR marks the current position.
+ * SEEK_END marks the end of the buffer.
+ * 
+ * With an offset of zero, this function sets the buffer position to zero
+ * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position
+ * unchanged (SEEK_CUR).
+ * 
+ * @param buffer
+ * @param offset position offset relative to <code>whence</code>
+ * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ * 
+ * The data is deleted by a zeroing it with call to <code>memset()</code>.
+ * 
+ * @param buffer the buffer to be cleared
+ */
+#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \
+        (buffer)->size = 0; (buffer)->pos = 0;
+
+/**
+ * Tests, if the buffer position has exceeded the buffer capacity.
+ * 
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * available byte of the buffer.
+ */
+int ucx_buffer_eof(UcxBuffer *buffer);
+
+
+/**
+ * Extends the capacity of the buffer.
+ * 
+ * <b>Note:</b> The buffer capacity increased by a power of two. I.e.
+ * the buffer capacity is doubled, as long as it would not hold the current
+ * content plus the additional required bytes.
+ * 
+ * <b>Attention:</b> the argument provided is the number of <i>additional</i>
+ * bytes the buffer shall hold. It is <b>NOT</b> the total number of bytes the
+ * buffer shall hold.
+ * 
+ * @param buffer the buffer to extend
+ * @param additional_bytes the number of additional bytes the buffer shall
+ * <i>at least</i> hold
+ * @return 0 on success or a non-zero value on failure
+ */
+int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes);
+
+/**
+ * Writes data to a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes written.
+ * 
+ * @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 UcxBuffer to write to
+ * @return the total count of bytes written
+ */
+size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer);
+
+/**
+ * Reads data from a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes read.
+ * 
+ * @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 UcxBuffer to read from
+ * @return the total number of elements read
+ */
+size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
+        UcxBuffer *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 #UCX_BUFFER_AUTOEXTEND feature is enabled,
+ * the buffer capacity is extended by ucx_buffer_extend(). If the feature is
+ * disabled or buffer extension fails, <code>EOF</code> 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 as <code>int</code> value
+ * @return the byte that has bean written as <code>int</code> value or
+ * <code>EOF</code> when the end of the stream is reached and automatic
+ * extension is not enabled or not possible
+ */
+int ucx_buffer_putc(UcxBuffer *buffer, int c);
+
+/**
+ * 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 as <code>int</code> value or <code>EOF</code>, if the
+ * end of the buffer is reached
+ */
+int ucx_buffer_getc(UcxBuffer *buffer);
+
+/**
+ * Writes a string to a buffer.
+ * 
+ * @param buffer the buffer
+ * @param str the string
+ * @return the number of bytes written
+ */
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str);
+
+/**
+ * Returns the complete buffer content as sstr_t.
+ * @param buffer the buffer
+ * @return the result of <code>sstrn()</code> with the buffer space and size
+ * as arguments
+ */
+#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_BUFFER_H */
+
diff --git a/ucx/ucx/list.h b/ucx/ucx/list.h
new file mode 100644 (file)
index 0000000..2bda6b8
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ * 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.
+ */
+/**
+ * Doubly linked list implementation.
+ * 
+ * @file   list.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_LIST_H
+#define        UCX_LIST_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX lists.
+ * 
+ * The first argument is the name of the iteration variable. The scope of
+ * this variable is limited to the <code>UCX_FOREACH</code> statement.
+ * 
+ * The second argument is a pointer to the list. In most cases this will be the
+ * pointer to the first element of the list, but it may also be an arbitrary
+ * element of the list. The iteration will then start with that element.
+ * 
+ * @param list The first element of the list
+ * @param elem The variable name of the element
+ */
+#define UCX_FOREACH(elem,list) \
+        for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next)
+
+/**
+ * UCX list type.
+ * @see UcxList
+ */
+typedef struct UcxList UcxList;
+
+/**
+ * UCX list structure.
+ */
+struct UcxList {
+    /**
+     * List element payload.
+     */
+    void    *data;
+    /**
+     * Pointer to the next list element or <code>NULL</code>, if this is the
+     * last element.
+     */
+    UcxList *next;
+    /**
+     * Pointer to the previous list element or <code>NULL</code>, if this is
+     * the first element.
+     */
+    UcxList *prev;
+};
+
+/**
+ * Creates an element-wise copy of a list.
+ * 
+ * This function clones the specified list by creating new list elements and
+ * copying the data with the specified copy_func(). If no copy_func() is
+ * specified, a shallow copy is created and the new list will reference the
+ * same data as the source list.
+ * 
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ */
+UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data);
+
+/**
+ * Creates an element-wise copy of a list using a UcxAllocator.
+ * 
+ * See ucx_list_clone() for details.
+ * 
+ * You might want to pass the allocator via the <code>data</code> parameter,
+ * to access it within the copy function for making deep copies.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ * @see ucx_list_clone()
+ */
+UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list,
+        copy_func cpyfnc, void* data);
+
+/**
+ * Compares two UCX lists element-wise by using a compare function.
+ * 
+ * Each element of the two specified lists are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * If the list pointers denote elements within a list, the lists are compared
+ * starting with the denoted elements. Thus any previous elements are not taken
+ * into account. This might be useful to check, if certain list tails match
+ * each other.
+ * 
+ * @param list1 the first list
+ * @param list2 the second list
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two lists equal element-wise, 0 otherwise
+ */
+int ucx_list_equals(const UcxList *list1, const UcxList *list2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the entire list.
+ * 
+ * The members of the list are not automatically freed, so ensure they are
+ * otherwise referenced or destroyed by ucx_list_free_contents().
+ * Otherwise, a memory leak is likely to occur.
+ * 
+ * <b>Caution:</b> the argument <b>MUST</b> denote an entire list (i.e. a call
+ * to ucx_list_first() on the argument must return the argument itself)
+ * 
+ * @param list the list to free
+ * @see ucx_list_free_contents()
+ */
+void ucx_list_free(UcxList *list);
+
+/**
+ * Destroys the entire list using a UcxAllocator.
+ * 
+ * See ucx_list_free() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to free
+ * @see ucx_list_free()
+ */
+void ucx_list_free_a(UcxAllocator *allocator, UcxList *list);
+
+/**
+ * Destroys the contents of the specified list by calling the specified
+ * destructor on each of them.
+ * 
+ * Note, that the contents are not usable afterwards and the list should be
+ * destroyed with ucx_list_free().
+ *
+ * If no destructor is specified (<code>NULL</code>), stdlib's free() is used.
+ * 
+ * @param list the list for which the contents shall be freed
+ * @param destr optional destructor function
+ * @see ucx_list_free()
+ */
+void ucx_list_free_content(UcxList* list, ucx_destructor destr);
+
+
+/**
+ * Inserts an element at the end of the list.
+ * 
+ * This is generally an O(n) operation, as the end of the list is retrieved with
+ * ucx_list_last().
+ * 
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ */
+UcxList *ucx_list_append(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the end of the list using a UcxAllocator.
+ * 
+ * See ucx_list_append() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ * @see ucx_list_append()
+ */
+UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+
+/**
+ * Inserts an element at the beginning of the list.
+ * 
+ * You <i>should</i> overwrite the old list pointer by calling
+ * <code>mylist = ucx_list_prepend(mylist, mydata);</code>. However, you may
+ * also perform successive calls of ucx_list_prepend() on the same list pointer,
+ * as this function always searchs for the head of the list with
+ * ucx_list_first().
+ * 
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ */
+UcxList *ucx_list_prepend(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the beginning of the list using a UcxAllocator.
+ * 
+ * See ucx_list_prepend() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ * @see ucx_list_prepend()
+ */
+UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+/**
+ * Concatenates two lists.
+ * 
+ * Either of the two arguments may be <code>NULL</code>.
+ * 
+ * This function modifies the references to the next/previous element of
+ * the last/first element of <code>list1</code>/<code>
+ * list2</code>.
+ * 
+ * @param list1 first list
+ * @param list2 second list
+ * @return if <code>list1</code> is <code>NULL</code>, <code>list2</code> is
+ * returned, otherwise <code>list1</code> is returned
+ */
+UcxList *ucx_list_concat(UcxList *list1, UcxList *list2);
+
+/**
+ * Returns the first element of a list.
+ * 
+ * If the argument is the list pointer, it is directly returned. Otherwise
+ * this function traverses to the first element of the list and returns the
+ * list pointer.
+ * 
+ * @param elem one element of the list
+ * @return the first element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_first(const UcxList *elem);
+
+/**
+ * Returns the last element of a list.
+ * 
+ * If the argument has no successor, it is the last element and therefore
+ * directly returned. Otherwise this function traverses to the last element of
+ * the list and returns it.
+ * 
+ * @param elem one element of the list
+ * @return the last element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_last(const UcxList *elem);
+
+/**
+ * Returns the list element at the specified index.
+ * 
+ * @param list the list to retrieve the element from
+ * @param index index of the element to return
+ * @return the element at the specified index or <code>NULL</code>, if the
+ * index is greater than the list size
+ */
+UcxList *ucx_list_get(const UcxList *list, size_t index);
+
+/**
+ * Returns the index of an element.
+ * 
+ * @param list the list where to search for the element
+ * @param elem the element to find
+ * @return the index of the element or -1 if the list does not contain the
+ * element
+ */
+ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem);
+
+/**
+ * Returns the element count of the list.
+ * 
+ * @param list the list whose elements are counted
+ * @return the element count
+ */
+size_t ucx_list_size(const UcxList *list);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, the pointers are
+ * compared.
+ * 
+ * If the list contains the data more than once, the index of the first
+ * occurrence is returned.
+ *  
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or -1 if the
+ * data is not found in this list
+ */
+ssize_t ucx_list_find(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if a list contains a specific element.
+ * 
+ * An element is found, if ucx_list_find() returns a value greater than -1.
+ * 
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the list contains the specified element data
+ * @see ucx_list_find()
+ */
+int ucx_list_contains(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxList with natural merge sort.
+ * 
+ * This function uses O(n) additional temporary memory for merge operations
+ * that is automatically freed after each merge.
+ * 
+ * As the head of the list might change, you <b>MUST</b> call this function
+ * as follows: <code>mylist = ucx_list_sort(mylist, mycmpfnc, mydata);</code>.
+ * 
+ * @param list the list to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func()
+ * @return the sorted list
+ */
+UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the list.
+ * 
+ * If the first element is removed, the list pointer changes. So it is
+ * <i>highly recommended</i> to <i>always</i> update the pointer by calling
+ * <code>mylist = ucx_list_remove(mylist, myelem);</code>.
+ * 
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * is now empty
+ */
+UcxList *ucx_list_remove(UcxList *list, UcxList *element);
+
+/**
+ * Removes an element from the list using a UcxAllocator.
+ * 
+ * See ucx_list_remove() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * @see ucx_list_remove()
+ */
+UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list,
+        UcxList *element);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_LIST_H */
+
diff --git a/ucx/ucx/logging.h b/ucx/ucx/logging.h
new file mode 100644 (file)
index 0000000..32bbbae
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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.
+ */
+/**
+ * Logging API.
+ * 
+ * @file   logging.h
+ * @author Mike Becker, Olaf Wintermann
+ */
+#ifndef UCX_LOGGING_H
+#define UCX_LOGGING_H
+
+#include "ucx.h"
+#include "map.h"
+#include "string.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* leave enough space for custom log levels */
+
+/** Log level for error messages. */
+#define UCX_LOGGER_ERROR        0x00
+    
+/** Log level for warning messages. */
+#define UCX_LOGGER_WARN         0x10
+
+/** Log level for information messages. */
+#define UCX_LOGGER_INFO         0x20
+
+/** Log level for debug messages. */
+#define UCX_LOGGER_DEBUG        0x30
+
+/** Log level for trace messages. */
+#define UCX_LOGGER_TRACE        0x40
+
+/**
+ * Output flag for the log level. 
+ * If this flag is set, the log message will contain the log level.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_LEVEL        0x01
+
+/**
+ * Output flag for the timestmap.
+ * If this flag is set, the log message will contain the timestmap.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_TIMESTAMP    0x02
+
+/**
+ * Output flag for the source.
+ * If this flag is set, the log message will contain the source file and line
+ * number.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_SOURCE       0x04
+
+/**
+ * The UCX Logger object.
+ */
+typedef struct {
+    /** The stream this logger writes its messages to.*/
+    void *stream;
+
+    /**
+     * The write function that shall be used.
+     * For standard file or stdout loggers this might be standard fwrite
+     * (default).
+     */
+    write_func writer;
+
+    /**
+     * The date format for timestamp outputs including the delimiter
+     * (default: <code>"%F %T %z "</code>).
+     * @see UCX_LOGGER_TIMESTAMP
+     */
+    char *dateformat;
+
+    /**
+     * The level, this logger operates on.
+     * If a log command is issued, the message will only be logged, if the log
+     * level of the message is less or equal than the log level of the logger.
+     */
+    unsigned int level;
+
+    /**
+     * A configuration mask for automatic output. 
+     * For each flag that is set, the logger automatically outputs some extra
+     * information like the timestamp or the source file and line number.
+     * See the documentation for the flags for details.
+     */
+    unsigned int mask;
+
+    /**
+     * A map of valid log levels for this logger.
+     * 
+     * The keys represent all valid log levels and the values provide string
+     * representations, that are used, if the UCX_LOGGER_LEVEL flag is set.
+     * 
+     * The exact data types are <code>unsigned int</code> for the key and
+     * <code>const char*</code> for the value.
+     * 
+     * @see UCX_LOGGER_LEVEL
+     */
+    UcxMap* levels;
+} UcxLogger;
+
+/**
+ * Creates a new logger.
+ * @param stream the stream, which the logger shall write to
+ * @param level the level on which the logger shall operate
+ * @param mask configuration mask (cf. UcxLogger.mask)
+ * @return a new logger object
+ */
+UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask);
+
+/**
+ * Destroys the logger.
+ * 
+ * The map containing the valid log levels is also automatically destroyed.
+ * 
+ * @param logger the logger to destroy
+ */
+void ucx_logger_free(UcxLogger* logger);
+
+/**
+ * Internal log function - use macros instead.
+ * 
+ * This function uses the <code>format</code> and variadic arguments for a
+ * printf()-style output of the log message.
+ * 
+ * Dependent on the UcxLogger.mask some information is prepended. The complete
+ * format is:
+ * 
+ * <code>[LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message</code>
+ *
+ * The source file name is reduced to the actual file name. This is necessary to
+ * get consistent behavior over different definitions of the __FILE__ macro.
+ *
+ * <b>Attention:</b> the message (including automatically generated information)
+ * is limited to 4096 characters. The level description is limited to
+ * 256 characters and the timestamp string is limited to 128 characters.
+ * 
+ * @param logger the logger to use
+ * @param level the level to log on
+ * @param file information about the source file
+ * @param line information about the source line number
+ * @param format format string
+ * @param ... arguments
+ * @see ucx_logger_log()
+ */
+void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
+        const unsigned int line, const char* format, ...);
+
+/**
+ * Registers a custom log level.
+ * @param logger the logger
+ * @param level the log level as unsigned integer
+ * @param name a string literal describing the level
+ */
+#define ucx_logger_register_level(logger, level, name) {\
+        unsigned int l; \
+            l = level; \
+            ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \
+        } while (0);
+
+/**
+ * Logs a message at the specified level.
+ * @param logger the logger to use
+ * @param level the level to log the message on
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_log(logger, level, ...) \
+    ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an error message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_error(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an information message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_info(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a warning message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_warn(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a debug message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_debug(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a trace message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_trace(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_LOGGING_H */
diff --git a/ucx/ucx/map.h b/ucx/ucx/map.h
new file mode 100644 (file)
index 0000000..4a9b9a2
--- /dev/null
@@ -0,0 +1,549 @@
+/*
+ * 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 map.h
+ * 
+ * Hash map implementation.
+ * 
+ * This implementation uses murmur hash 2 and separate chaining with linked
+ * lists.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MAP_H
+#define        UCX_MAP_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX maps.
+ * 
+ * The <code>key</code> variable is implicitly defined, but the
+ * <code>value</code> variable must be already declared as type information
+ * cannot be inferred.
+ * 
+ * @param key the variable name for the key
+ * @param value the variable name for the value
+ * @param iter a UcxMapIterator
+ * @see ucx_map_iterator()
+ */
+#define UCX_MAP_FOREACH(key,value,iter) \
+        for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);)
+
+/** Type for the UCX map. @see UcxMap */
+typedef struct UcxMap          UcxMap;
+
+/** Type for a key of a UcxMap. @see UcxKey */
+typedef struct UcxKey          UcxKey;
+
+/** Type for an element of a UcxMap. @see UcxMapElement */
+typedef struct UcxMapElement   UcxMapElement;
+
+/** Type for an iterator over a UcxMap. @see UcxMapIterator */
+typedef struct UcxMapIterator  UcxMapIterator;
+
+/** Structure for the UCX map. */
+struct UcxMap {
+    /** An allocator that is used for the map elements. */
+    UcxAllocator  *allocator;
+    /** The array of map element lists. */
+    UcxMapElement **map;
+    /** The size of the map is the length of the element list array. */
+    size_t        size;
+    /** The count of elements currently stored in this map. */
+    size_t        count;
+};
+
+/** Structure to publicly denote a key of a UcxMap. */
+struct UcxKey {
+    /** The key data. */
+    const void *data;
+    /** The length of the key data. */
+    size_t     len;
+    /** A cache for the hash value of the key data. */
+    int        hash;
+};
+
+/** Internal structure for a key of a UcxMap. */
+struct UcxMapKey {
+    /** The key data. */
+    void    *data;
+    /** The length of the key data. */
+    size_t  len;
+    /** The hash value of the key data. */
+    int     hash;
+};
+
+/** Structure for an element of a UcxMap. */
+struct UcxMapElement {
+    /** The value data. */
+    void              *data;
+    
+    /** A pointer to the next element in the current list. */
+    UcxMapElement     *next;
+    
+    /** The corresponding key. */
+    struct UcxMapKey  key;
+};
+
+/** Structure for an iterator over a UcxMap. */
+struct UcxMapIterator {
+    /** The map to iterate over. */
+    UcxMap const  *map;
+    
+    /** The current map element. */
+    UcxMapElement *cur;
+    
+    /**
+     * The current index of the element list array.
+     * <b>Attention: </b> this is <b>NOT</b> the element index! Do <b>NOT</b>
+     * manually iterate over the map by increasing this index. Use
+     * ucx_map_iter_next().
+     * @see UcxMap.map*/
+    size_t        index;
+};
+
+/**
+ * Creates a new hash map with the specified size.
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new(size_t size);
+
+/**
+ * Creates a new hash map with the specified size using a UcxAllocator.
+ * @param allocator the allocator to use
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size);
+
+/**
+ * Frees a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be freed
+ * @see ucx_map_free_content()
+ */
+void ucx_map_free(UcxMap *map);
+
+/**
+ * Frees the contents of a hash map.
+ * 
+ * This is a convenience function that iterates over the map and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the map's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free or clear the map afterwards, as the contents will be invalid.
+ * 
+ * @param map for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_map_free()
+ * @see ucx_map_clear()
+ */
+void ucx_map_free_content(UcxMap *map, ucx_destructor destr);
+
+/**
+ * Clears a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be cleared
+ * @see ucx_map_free_content()
+ */
+void ucx_map_clear(UcxMap *map);
+
+
+/**
+ * Copies contents from a map to another map using a copy function.
+ * 
+ * <b>Note:</b> The destination map does not need to be empty. However, if it
+ * contains data with keys that are also present in the source map, the contents
+ * are overwritten.
+ * 
+ * @param from the source map
+ * @param to the destination map
+ * @param fnc the copy function or <code>NULL</code> if the pointer address
+ * shall be copied
+ * @param data additional data for the copy function
+ * @return 0 on success or a non-zero value on memory allocation errors
+ */
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ * 
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ * 
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ *
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ *
+ * @param allocator the allocator to use for the cloned map
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+                        UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Increases size of the hash map, if necessary.
+ * 
+ * The load value is 0.75*UcxMap.size. If the element count exceeds the load
+ * value, the map needs to be rehashed. Otherwise no action is performed and
+ * this function simply returns 0.
+ * 
+ * The rehashing process ensures, that the UcxMap.size is at least
+ * 2.5*UcxMap.count. So there is enough room for additional elements without
+ * the need of another soon rehashing.
+ * 
+ * You can use this function to dramatically increase access performance.
+ * 
+ * @param map the map to rehash
+ * @return 1, if a memory allocation error occurred, 0 otherwise
+ */
+int ucx_map_rehash(UcxMap *map);
+
+/**
+ * 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
+ */
+int ucx_map_put(UcxMap *map, UcxKey key, void *value);
+
+/**
+ * Retrieves a value by using a key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+void* ucx_map_get(UcxMap const *map, UcxKey key);
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ */
+void* ucx_map_remove(UcxMap *map, UcxKey key);
+
+/**
+ * Shorthand for putting data with a sstr_t key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_sstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value)
+
+/**
+ * Shorthand for putting data with a C string key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_cstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value)
+
+/**
+ * Shorthand for putting data with an integer key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_int_put(map, key, value) \
+    ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value)
+
+/**
+ * Shorthand for getting data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_sstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for getting data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_cstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for getting data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_int_get(map, key) \
+    ucx_map_get(map, ucx_key(&key, sizeof(int)))
+
+/**
+ * Shorthand for removing data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_sstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for removing data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_cstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for removing data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_int_remove(map, key) \
+    ucx_map_remove(map, ucx_key(&key, sizeof(key)))
+
+/**
+ * Creates a UcxKey based on the given data.
+ * 
+ * This function implicitly computes the hash.
+ * 
+ * @param data the data for the key
+ * @param len the length of the data
+ * @return a UcxKey with implicitly computed hash
+ * @see ucx_hash()
+ */
+UcxKey ucx_key(const void *data, size_t len);
+
+/**
+ * Computes a murmur hash-2.
+ * 
+ * @param data the data to hash
+ * @param len the length of the data
+ * @return the murmur hash-2 of the data
+ */
+int ucx_hash(const char *data, size_t len);
+
+/**
+ * Creates an iterator for a map.
+ * 
+ * <b>Note:</b> A UcxMapIterator iterates over all elements in all element
+ * lists successively. Therefore the order highly depends on the key hashes and
+ * may vary under different map sizes. So generally you may <b>NOT</b> rely on
+ * the iteration order.
+ * 
+ * <b>Note:</b> The iterator is <b>NOT</b> initialized. You need to call
+ * ucx_map_iter_next() at least once before accessing any information. However,
+ * it is not recommended to access the fields of a UcxMapIterator directly.
+ * 
+ * @param map the map to create the iterator for
+ * @return an iterator initialized on the first element of the
+ * first element list
+ * @see ucx_map_iter_next()
+ */
+UcxMapIterator ucx_map_iterator(UcxMap const *map);
+
+/**
+ * Proceeds to the next element of the map (if any).
+ * 
+ * Subsequent calls on the same iterator proceed to the next element and
+ * store the key/value-pair into the memory specified as arguments of this
+ * function.
+ * 
+ * If no further elements are found, this function returns zero and leaves the
+ * last found key/value-pair in memory.
+ * 
+ * @param iterator the iterator to use
+ * @param key a pointer to the memory where to store the key
+ * @param value a pointer to the memory where to store the value
+ * @return 1, if another element was found, 0 if all elements has been processed
+ * @see ucx_map_iterator()
+ */
+int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_MAP_H */
+
diff --git a/ucx/ucx/mempool.h b/ucx/ucx/mempool.h
new file mode 100644 (file)
index 0000000..e4a8ce3
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * 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 mempool.h
+ * 
+ * Memory pool implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define        UCX_MEMPOOL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX mempool structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this pool */
+    UcxAllocator *allocator;
+    
+    /** List of pointers to pooled memory. */
+    void         **data;
+    
+    /** Count of pooled memory items. */
+    size_t       ndata;
+    
+    /** Memory pool size. */
+    size_t       size;
+} UcxMempool;
+
+/** Shorthand for a new default memory pool with a capacity of 16 elements. */
+#define ucx_mempool_new_default() ucx_mempool_new(16)
+
+
+/**
+ * Creates a memory pool with the specified initial size.
+ * 
+ * As the created memory pool automatically grows in size by factor two when
+ * trying to allocate memory on a full pool, it is recommended that you use
+ * a power of two for the initial size.
+ * 
+ * @param n initial pool size (should be a power of two, e.g. 16)
+ * @return a pointer to the new memory pool
+ * @see ucx_mempool_new_default()
+ */
+UcxMempool *ucx_mempool_new(size_t n);
+
+/**
+ * Resizes a memory pool.
+ * 
+ * This function will fail if the new capacity is not sufficient for the
+ * present data.
+ * 
+ * @param pool the pool to resize
+ * @param newcap the new capacity
+ * @return zero on success or non-zero on failure
+ */
+int ucx_mempool_chcap(UcxMempool *pool, size_t newcap);
+
+/**
+ * Allocates pooled memory.
+ * 
+ * @param pool the memory pool
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_mempool_malloc(UcxMempool *pool, size_t n);
+/**
+ * Allocates a pooled memory array.
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param pool the memory pool
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize);
+
+/**
+ * Reallocates pooled memory.
+ * 
+ * If the memory to be reallocated is not contained by the specified pool, the
+ * behavior is undefined.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n);
+
+/**
+ * Frees pooled memory.
+ * 
+ * Before freeing the memory, the specified destructor function (if any)
+ * is called.
+ * 
+ * If you specify memory, that is not pooled by the specified memory pool, the
+ * program will terminate with a call to <code>abort()</code>.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be freed
+ * @see ucx_mempool_set_destr()
+ */
+void ucx_mempool_free(UcxMempool *pool, void *ptr);
+
+/**
+ * Destroys a memory pool.
+ * 
+ * For each element the destructor function (if any) is called and the element
+ * is freed.
+ * 
+ * Each of the registered destructor function that has no corresponding element
+ * within the pool (namely those registered by ucx_mempool_reg_destr) is
+ * called interleaving with the element destruction, but with guarantee to the
+ * order in which they were registered (FIFO order).
+ * 
+ * 
+ * @param pool the mempool to destroy
+ */
+void ucx_mempool_destroy(UcxMempool *pool);
+
+/**
+ * Sets a destructor function for the specified memory.
+ * 
+ * The destructor is automatically called when the memory is freed or the
+ * pool is destroyed.
+ * A destructor for pooled memory <b>MUST NOT</b> free the memory itself,
+ * as this is done by the pool. Use a destructor to free any resources
+ * managed by the pooled object.
+ * 
+ * The only requirement for the specified memory is, that it <b>MUST</b> be
+ * pooled memory by a UcxMempool or an element-compatible mempool. The pointer
+ * to the destructor function is saved in a reserved area before the actual
+ * memory.
+ * 
+ * @param ptr pooled memory
+ * @param func a pointer to the destructor function
+ * @see ucx_mempool_free()
+ * @see ucx_mempool_destroy()
+ */
+void ucx_mempool_set_destr(void *ptr, ucx_destructor func);
+
+/**
+ * Registers a destructor function for the specified (non-pooled) memory.
+ *
+ * This is useful, if you have memory that has not been allocated by a mempool,
+ * but shall be managed by a mempool.
+ * 
+ * This function creates an entry in the specified mempool and the memory will
+ * therefore (logically) convert to pooled memory.
+ * <b>However, this does not cause the memory to be freed automatically!</b>.
+ * If you want to use this function, make the memory pool free non-pooled
+ * memory, the specified destructor function must call <code>free()</code>
+ * by itself. But keep in mind, that you then MUST NOT use this destructor
+ * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it
+ * would cause a double-free.
+ * 
+ * @param pool the memory pool
+ * @param ptr data the destructor is registered for
+ * @param destr a pointer to the destructor function
+ */
+void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_MEMPOOL_H */
+
diff --git a/ucx/ucx/properties.h b/ucx/ucx/properties.h
new file mode 100644 (file)
index 0000000..819d1e5
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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 "ucx.h"
+#include "map.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 sstr_t.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 sstr_t that shall contain the property name
+ * @param value a pointer to the sstr_t 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, sstr_t *name, sstr_t *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, UcxMap *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(UcxMap *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(UcxMap *map, FILE *file);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_PROPERTIES_H */
+
diff --git a/ucx/ucx/stack.h b/ucx/ucx/stack.h
new file mode 100644 (file)
index 0000000..6fe8a06
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * 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 stack.h
+ * 
+ * Default stack memory allocation implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STACK_H
+#define        UCX_STACK_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * UCX stack structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this stack */
+    UcxAllocator allocator;
+    
+    /** Stack size. */
+    size_t size;
+    
+    /** Pointer to the bottom of the stack */
+    char *space;
+    
+    /** Pointer to the top of the stack */
+    char *top;
+} UcxStack;
+
+/**
+ * Metadata for each UCX stack element.
+ */
+struct ucx_stack_metadata {
+    /**
+     * Location of the previous element (<code>NULL</code> if this is the first)
+     */
+    char *prev;
+    
+    /** Size of this element */
+    size_t size;
+};
+
+/**
+ * Initializes UcxStack structure with memory.
+ * 
+ * @param stack a pointer to an uninitialized stack structure
+ * @param space the memory area that shall be managed
+ * @param size size of the memory area
+ * @return a new UcxStack structure
+ */
+void ucx_stack_init(UcxStack *stack, char* space, size_t size);
+
+/**
+ * Allocates stack memory.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory or <code>NULL</code> on stack
+ * overflow
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_stack_malloc(UcxStack *stack, size_t n);
+
+/**
+ * Allocates memory with #ucx_stack_malloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @param data a pointer to the data to copy
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_malloc
+ */
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data);
+
+/**
+ * Allocates an array of stack memory
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
+
+/**
+ * Allocates memory with #ucx_stack_calloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @param data a pointer to the data
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_calloc
+ */
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data);
+
+/**
+ * Reallocates memory on the stack.
+ * 
+ * Shrinking memory is always safe. Extending memory can be very expensive. 
+ * 
+ * @param stack the stack
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n);
+
+/**
+ * Frees memory on the stack.
+ * 
+ * Freeing stack memory behaves in a special way.
+ * 
+ * If the element, that should be freed, is the top most element of the stack,
+ * it is removed from the stack. Otherwise it is marked as freed. Marked
+ * elements are removed, when they become the top most elements of the stack.
+ * 
+ * @param stack a pointer to the stack
+ * @param ptr a pointer to the memory that shall be freed
+ */
+void ucx_stack_free(UcxStack *stack, void *ptr);
+
+
+/**
+ * Returns the size of the top most element.
+ * @param stack a pointer to the stack
+ * @return the size of the top most element
+ */
+#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\
+                                  (stack)->top - 1)->size : 0)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>, if specified.
+ * 
+ * Use #ucx_stack_topsize()# to get the amount of memory that must be available
+ * at the location of <code>dest</code>.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to, or <code>
+ * NULL</code>, if the element shall only be removed.
+ * @see ucx_stack_free
+ * @see ucx_stack_popn
+ */
+#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>.
+ * 
+ * This function copies at most <code>n</code> bytes to the destination, but
+ * the element is always freed as a whole.
+ * If the element was larger than <code>n</code>, the remaining data is lost.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to
+ * @param n copies at most n bytes to <code>dest</code>
+ * @see ucx_stack_pop
+ */
+void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
+
+/**
+ * Returns the remaining available memory on the specified stack.
+ * 
+ * @param stack a pointer to the stack
+ * @return the remaining available memory
+ */
+size_t ucx_stack_avail(UcxStack *stack);
+
+/**
+ * Checks, if the stack is empty.
+ * 
+ * @param stack a pointer to the stack
+ * @return nonzero, if the stack is empty, zero otherwise
+ */
+#define ucx_stack_empty(stack) (!(stack)->top)
+
+/**
+ * Computes a recommended size for the stack memory area. Note, that
+ * reallocations have not been taken into account, so you might need to reserve
+ * twice as much memory to allow many reallocations.
+ * 
+ * @param size the approximate payload
+ * @param elems the approximate count of element allocations
+ * @return a recommended size for the stack space based on the information
+ * provided
+ */
+#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \
+                                    (elems + 1))
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_STACK_H */
+
diff --git a/ucx/ucx/string.h b/ucx/ucx/string.h
new file mode 100644 (file)
index 0000000..90b437a
--- /dev/null
@@ -0,0 +1,1201 @@
+/*
+ * 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.
+ */
+/**
+ * Bounded string implementation.
+ * 
+ * The UCX strings (<code>sstr_t</code>) provide an alternative to C strings.
+ * The main difference to C strings is, that <code>sstr_t</code> does <b>not
+ * need to be <code>NULL</code>-terminated</b>. Instead the length is stored
+ * within the structure.
+ * 
+ * When using <code>sstr_t</code>, developers must be full aware of what type
+ * of string (<code>NULL</code>-terminated) or not) they are using, when 
+ * accessing the <code>char* ptr</code> directly.
+ * 
+ * The UCX string module provides some common string functions, known from
+ * standard libc, working with <code>sstr_t</code>.
+ * 
+ * @file   string.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STRING_H
+#define        UCX_STRING_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+/*
+ * Use this macro to disable the shortcuts if you experience macro collision.
+ */
+#ifndef UCX_NO_SSTR_SHORTCUTS
+/**
+ * Shortcut for a <code>sstr_t struct</code>
+ * or <code>scstr_t struct</code> literal.
+ */
+#define ST(s) { s, sizeof(s)-1 }
+
+/** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
+#define S(s) sstrn(s, sizeof(s)-1)
+
+/** Shortcut for the conversion of a C string to a <code>scstr_t</code>. */
+#define SC(s) scstrn(s, sizeof(s)-1)
+#endif /* UCX_NO_SSTR_SHORTCUTS */
+
+/*
+ * Use this macro to disable the format macros.
+ */
+#ifndef UCX_NO_SSTR_FORMAT_MACROS
+/** Expands a sstr_t or scstr_t to printf arguments. */
+#define SFMT(s) (int) (s).length, (s).ptr
+
+/** Format specifier for a sstr_t or scstr_t. */
+#define PRIsstr ".*s"
+#endif /* UCX_NO_SSTR_FORMAT_MACROS */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+/**
+ * The UCX string structure.
+ */
+typedef struct {
+   /** A pointer to the string
+    * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+} sstr_t;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+typedef struct {
+    /** A constant pointer to the immutable string
+     * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    const char *ptr;
+    /** The length of the string */
+    size_t length;
+} scstr_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#ifdef __cplusplus
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+inline scstr_t s2scstr(sstr_t s) {
+    scstr_t c;
+    c.ptr = s.ptr;
+    c.length = s.length;
+    return c;
+}
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+inline scstr_t s2scstr(scstr_t str) {
+    return str;
+}
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(s) s2scstr(s)
+#else
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+scstr_t ucx_sc2sc(scstr_t str);
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+scstr_t ucx_ss2sc(sstr_t str);
+
+#if __STDC_VERSION__ >= 201112L
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str)
+
+#elif defined(__GNUC__) || defined(__clang__)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) __builtin_choose_expr( \
+        __builtin_types_compatible_p(typeof(str), sstr_t), \
+        ucx_ss2sc, \
+        ucx_sc2sc)(str)
+
+#elif defined(__sun)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \
+       scstr_t ucx_tmp_var_c; \
+       ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\
+       ucx_tmp_var_c.length = ucx_tmp_var_str.length;\
+       ucx_tmp_var_c; })
+#else /* no generics and no builtins */
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * 
+ * This <b>internal</b> function (ab)uses the C standard an expects one single
+ * argument which is then implicitly converted to scstr_t without a warning.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @return the an immutable version of the provided string
+ */
+scstr_t ucx_ss2c_s();
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ucx_ss2c_s(str)
+#endif /* C11 feature test */
+
+#endif /* C++ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Creates a new sstr_t based on a C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstr().
+ * 
+ * @param cstring the C string to wrap
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstrn()
+ */
+sstr_t sstr(char *cstring);
+
+/**
+ * Creates a new sstr_t of the specified length based on a C string.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstrn().
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstr()
+ * @see S()
+ */
+sstr_t sstrn(char *cstring, size_t length);
+
+/**
+ * Creates a new scstr_t based on a constant C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function.
+ * 
+ * @param cstring the C string to wrap
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstrn()
+ */
+scstr_t scstr(const char *cstring);
+
+
+/**
+ * Creates a new scstr_t of the specified length based on a constant C string.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function. * 
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstr()
+ */
+scstr_t scstrn(const char *cstring, size_t length);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count 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
+ */
+size_t scstrnlen(size_t count, ...);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count of the
+ * specified strings, the behavior is undefined.
+ * 
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the cumulated length of all strings
+ */
+#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+sstr_t scstrcat(size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ * 
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see scstrcat()
+ */
+sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see sstrcat()
+ */
+#define sstrcat_a(alloc, count, s1, ...) \
+    scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see sstrsubsl()
+ * @see sstrchr()
+ */
+sstr_t sstrsubs(sstr_t string, size_t start);
+
+/**
+ * Returns a substring with the given length starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see sstrsubs()
+ * @see sstrchr()
+ */
+sstr_t sstrsubsl(sstr_t string, size_t start, size_t length);
+
+/**
+ * Returns a substring of an immutable string starting at the specified
+ * location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+* input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see scstrsubsl()
+ * @see scstrchr()
+ */
+scstr_t scstrsubs(scstr_t string, size_t start);
+
+/**
+ * Returns a substring of an immutable string with a maximum length starting
+ * at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see scstrsubs()
+ * @see scstrchr()
+ */
+scstr_t scstrsubsl(scstr_t 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 <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrchr(sstr_t 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 <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrrchr(sstr_t string, int chr);
+
+/**
+ * Returns an immutable 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 <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrchr(scstr_t string, int chr);
+
+/**
+ * Returns an immutable 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 <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrrchr(scstr_t string, int chr);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+sstr_t scstrsstr(sstr_t string, scstr_t match);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrstr(string, match) scstrsstr(string, SCSTR(match))
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+scstr_t scstrscstr(scstr_t string, scstr_t match);
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrscstr(string, match) scstrscstr(string, SCSTR(match))
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use scstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit_a()
+ */
+sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count);
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use sstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit_a()
+ */
+#define sstrsplit(string, delim, count) \
+    scstrsplit(SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Performing scstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of scstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit()
+ */
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim,
+        ssize_t *count);
+
+/**
+ * Performing sstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of sstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit()
+ */
+#define sstrsplit_a(allocator, string, delim, count) \
+    scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+int scstrcmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+int scstrcasecmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup_a()
+ */
+sstr_t scstrdup(scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see sstrdup_a()
+ */
+#define sstrdup(string) scstrdup(SCSTR(string))
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string))
+
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new sstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new sstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the sstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = sstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * sstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new sstr_t containing the trimmed string
+ */
+sstr_t sstrtrim(sstr_t string);
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new scstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new scstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the scstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = scstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * scstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new scstr_t containing the trimmed string
+ */
+scstr_t scstrtrim(scstr_t string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix))
+
+/**
+ * Checks, if a string has a specific suffix.
+ * 
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrsuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(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 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrcaseprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * 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 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrcaseprefix(string, prefix) \
+  scstrcaseprefix(SCSTR(string), SCSTR(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 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrcasesuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * 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 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrcasesuffix(string, suffix) \
+  scstrcasesuffix(SCSTR(string), SCSTR(suffix))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup()
+ */
+sstr_t scstrlower(scstr_t string);
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower(string) scstrlower(SCSTR(string))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+  * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string);
+
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup()
+ */
+sstr_t scstrupper(scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper(string) scstrupper(SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper_a(allocator, string) scstrupper_a(allocator, string)
+
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @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
+ */
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+        scstr_t pattern, scstr_t 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 <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @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
+ */
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t 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 <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * @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
+ */
+#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @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 sstrreplacen(str, pattern, replacement, replmax) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @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 sstrreplace_a(allocator, str, pattern, replacement) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(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 <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @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 sstrreplace(str, pattern, replacement) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_STRING_H */
diff --git a/ucx/ucx/test.h b/ucx/ucx/test.h
new file mode 100644 (file)
index 0000000..b45b1d1
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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>
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with UCX_TEST_ASSERT()
+ * }
+ * 
+ * UCX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #UCX_TEST_BEGIN
+ *   // tests with UCX_TEST_ASSERT() and/or
+ *   // calls with UCX_TEST_CALL_SUBROUTINE() here
+ *   #UCX_TEST_END
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ *
+ * <b>Note:</b> if a test fails, a longjump is performed
+ * back to the #UCX_TEST_BEGIN macro!
+ * 
+ * <b>Attention:</b> Do not call own functions within a test, that use
+ * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE().
+ * 
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define        UCX_TEST_H
+
+#include "ucx.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
+
+/** Type for the UcxTestSuite. */
+typedef struct UcxTestSuite UcxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*UcxTest)(UcxTestSuite*,FILE*);
+
+/** Type for the internal list of test cases. */
+typedef struct UcxTestList UcxTestList;
+
+/** Structure for the internal list of test cases. */
+struct UcxTestList {
+    
+    /** Test case. */
+    UcxTest test;
+    
+    /** Pointer to the next list element. */
+    UcxTestList *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct UcxTestSuite {
+    
+    /** 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;
+    
+    /**
+     * Internal list of test cases.
+     * Use ucx_test_register() to add tests to this list.
+     */
+    UcxTestList *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @return a new test suite
+ */
+UcxTestSuite* ucx_test_suite_new();
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+void ucx_test_suite_free(UcxTestSuite* 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 <code>EXIT_SUCCESS</code> on success or
+ * <code>EXIT_FAILURE</code> on failure
+ */
+int ucx_test_register(UcxTestSuite* suite, UcxTest test);
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param outstream the stream the log shall be written to
+ */
+void ucx_test_run(UcxTestSuite* suite, FILE* outstream);
+
+/**
+ * Macro for a #UcxTest function header.
+ * 
+ * Use this macro to declare and/or define a #UcxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_)
+
+/**
+ * Marks the begin of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b>
+ * #UCX_TEST_BEGIN.
+ * 
+ * @see #UCX_TEST_END
+ */
+#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\
+        fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        fwrite("... ", 1, 4, _output_);\
+        jmp_buf _env_; \
+        if (!setjmp(_env_)) {
+
+/**
+ * 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 UCX_TEST_ASSERT(condition,message) if (!(condition)) { \
+        fwrite(message".\n", 1, 2+strlen(message), _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    }
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * UCX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see UCX_TEST_CALL_SUBROUTINE()
+ */
+#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\
+        FILE *_output_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN-
+ * #UCX_TEST_END-block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see UCX_TEST_SUBROUTINE()
+ */
+#define UCX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_env_,__VA_ARGS__);
+
+/**
+ * Marks the end of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b>
+ * #UCX_TEST_END.
+ * 
+ * @see #UCX_TEST_BEGIN
+ */
+#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_TEST_H */
+
diff --git a/ucx/ucx/ucx.h b/ucx/ucx/ucx.h
new file mode 100644 (file)
index 0000000..0944def
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+/**
+ * Main UCX Header providing most common definitions.
+ * 
+ * @file   ucx.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_H
+#define        UCX_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   2
+
+/** 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)
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#ifdef _WIN32
+#if !(defined __ssize_t_defined || defined _SSIZE_T_)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#define __ssize_t_defined
+#define _SSIZE_T_
+#endif /* __ssize_t_defined and _SSIZE_T */
+#else /* !_WIN32 */
+#include <sys/types.h>
+#endif /* _WIN32 */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+
+/**
+ * A function pointer to a destructor function.
+ * @see ucx_mempool_setdestr()
+ * @see ucx_mempool_regdestr()
+ */
+typedef void(*ucx_destructor)(void*);
+
+/**
+ * Function pointer to a compare function.
+ * 
+ * The compare function shall take three arguments: the two values that shall be
+ * compared and optional additional data.
+ * The function shall then return -1 if the first argument is less than the
+ * second argument, 1 if the first argument is greater than the second argument
+ * and 0 if both arguments are equal. If the third argument is
+ * <code>NULL</code>, it shall be ignored.
+ */
+typedef int(*cmp_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a distance function.
+ * 
+ * The distance function shall take three arguments: the two values for which
+ * the distance shall be computed and optional additional data.
+ * The function shall then return the signed distance as integer value.
+ */
+typedef intmax_t(*distance_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a copy function.
+ * 
+ * The copy function shall create a copy of the first argument and may use
+ * additional data provided by the second argument. If the second argument is
+ * <code>NULL</code>, it shall be ignored.
+
+ * <b>Attention:</b> if pointers returned by functions of this type may be
+ * passed to <code>free()</code> depends on the implementation of the
+ * respective <code>copy_func</code>.
+ */
+typedef void*(*copy_func)(const void*,void*);
+
+/**
+ * Function pointer to a write function.
+ * 
+ * The signature of the write function shall be compatible to the signature
+ * of standard <code>fwrite</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the source,
+ * the length of one element, the element count and a pointer to the
+ * destination.
+ */
+typedef size_t(*write_func)(const void*, size_t, size_t, void*);
+
+/**
+ * Function pointer to a read function.
+ * 
+ * The signature of the read function shall be compatible to the signature
+ * of standard <code>fread</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the
+ * destination, the length of one element, the element count and a pointer to
+ * the source.
+ */
+typedef size_t(*read_func)(void*, size_t, size_t, void*);
+
+
+
+#if __GNUC__ >= 5 || defined(__clang__)
+#define UCX_MUL_BUILTIN
+
+#if __WORDSIZE == 32
+/**
+ * Alias for <code>__builtin_umul_overflow</code>.
+ * 
+ * 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 ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result)
+#else /* __WORDSIZE != 32 */
+/**
+ * Alias for <code>__builtin_umull_overflow</code>.
+ * 
+ * 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 ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result)
+#endif /* __WORDSIZE */
+
+#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 ucx_szmul(a, b, result) ucx_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 ucx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_H */
+
diff --git a/ucx/ucx/utils.h b/ucx/ucx/utils.h
new file mode 100644 (file)
index 0000000..642397a
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * 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 utils.h
+ * 
+ * Compare, copy and printf functions.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <inttypes.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy().
+ */
+#define UCX_STREAM_COPY_BUFSIZE 4096
+
+/**
+ * Copies a string.
+ * @param s the string to copy
+ * @param data omitted
+ * @return a pointer to a copy of s1 that can be passed to free(void*)
+ */
+void *ucx_strcpy(const void *s, void *data);
+
+/**
+ * Copies a memory area.
+ * @param m a pointer to the memory area
+ * @param n a pointer to the size_t containing the size of the memory area
+ * @return a pointer to a copy of the specified memory area that can
+ * be passed to free(void*)
+ */
+void *ucx_memcpy(const void *m, void *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 <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @param n the maximum number of bytes that shall be copied
+ * @return the total number of bytes copied
+  */
+size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc,
+        char* buf, size_t bufsize, size_t n);
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using a default 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
+ * 
+ * @see #UCX_STREAM_COPY_BUFSIZE
+ */
+#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1)
+
+/**
+ * Shorthand for ucx_stream_bncopy using a default copy buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n maximum number of bytes that shall be copied
+ * @return total number of bytes copied
+ */
+#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, n)
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer.
+ * 
+ * @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 <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @return total number of bytes copied
+ */
+#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        buf, bufsize, (size_t)-1)
+
+/**
+ * Wraps the strcmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param data omitted
+ * @return the result of strcmp(s1, s2)
+ */
+int ucx_cmp_str(const void *s1, const void *s2, void *data);
+
+/**
+ * Wraps the strncmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param n a pointer to the size_t containing the third strncmp parameter
+ * @return the result of strncmp(s1, s2, *n)
+ */
+int ucx_cmp_strn(const void *s1, const void *s2, void *n);
+
+/**
+ * Wraps the sstrcmp function.
+ * @param s1 sstr one
+ * @param s2 sstr two
+ * @param data ignored
+ * @return the result of sstrcmp(s1, s2)
+ */
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data);
+
+/**
+ * Compares two integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two real numbers of type float.
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @param data if provided: a pointer to precision (default: 1e-6f)
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int ucx_cmp_float(const void *f1, const void *f2, void *data);
+
+/**
+ * Compares two real numbers of type double.
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @param data if provided: a pointer to precision (default: 1e-14)
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int ucx_cmp_double(const void *d1, const void *d2, void *data);
+
+/**
+ * Compares two pointers.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param data omitted
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data);
+
+/**
+ * Compares two memory areas.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param n a pointer to the size_t containing the third parameter for memcmp
+ * @return the result of memcmp(ptr1, ptr2, *n)
+ */
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n);
+
+/**
+ * A <code>printf()</code> 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
+ */
+int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_fprintf().
+ * @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 ucx_fprintf()
+ */
+int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap);
+
+/**
+ * A <code>printf()</code> like function which allocates space for a sstr_t
+ * the result is written to.
+ * 
+ * <b>Attention</b>: The sstr_t data is allocated with the allocators
+ * ucx_allocator_malloc() function. So it is implementation dependent, if
+ * the returned sstr_t.ptr pointer must be passed to the allocators
+ * ucx_allocator_free() function manually.
+ * 
+ * <b>Note</b>: The sstr_t.ptr of the return value will <i>always</i> be
+ * <code>NULL</code>-terminated.
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return a sstr_t containing the formatted string
+ */
+sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_asprintf().
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ap argument list
+ * @return a sstr_t containing the formatted string
+ * @see ucx_asprintf()
+ */
+sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap);
+
+/** Shortcut for ucx_asprintf() with default allocator. */
+#define ucx_sprintf(...) \
+    ucx_asprintf(ucx_default_allocator(), __VA_ARGS__)
+
+/**
+ * A <code>printf()</code> like function which writes the output to a
+ * UcxBuffer.
+ * 
+ * @param buffer the buffer the data is written to
+ * @param ... format string and additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \
+        (write_func)ucx_buffer_write, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_UTILS_H */
+
diff --git a/ucx/utils.c b/ucx/utils.c
new file mode 100644 (file)
index 0000000..101c33f
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+ * 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/utils.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+
+/* COPY FUCNTIONS */
+void* ucx_strcpy(const void* s, void* data) {
+    const char *str = (const char*) s;
+    size_t n = 1+strlen(str);
+    char *cpy = (char*) malloc(n);
+    memcpy(cpy, str, n);
+    return cpy;
+}
+
+void* ucx_memcpy(const void* m, void* n) {
+    size_t k = *((size_t*)n);
+    void *cpy = malloc(k);
+    memcpy(cpy, m, k);
+    return cpy;
+}
+
+size_t ucx_stream_bncopy(void *src, void *dest, read_func readfnc,
+        write_func writefnc, char* buf, size_t bufsize, size_t n) {
+    if(n == 0 || bufsize == 0) {
+        return 0;
+    }
+    
+    char *lbuf;    
+    size_t ncp = 0;
+    
+    if(buf) {
+        lbuf = buf;
+    } else {
+        lbuf = (char*)malloc(bufsize);
+        if(lbuf == NULL) {
+            return 0;
+        }
+    }
+    
+    size_t r;
+    size_t rn = bufsize > n ? n : bufsize;
+    while((r = readfnc(lbuf, 1, rn, src)) != 0) {
+        r = writefnc(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;
+}
+
+/* COMPARE FUNCTIONS */
+
+int ucx_cmp_str(const void *s1, const void *s2, void *data) {
+    return strcmp((const char*)s1, (const char*)s2);
+}
+
+int ucx_cmp_strn(const void *s1, const void *s2, void *n) {
+    return strncmp((const char*)s1, (const char*)s2, *((size_t*) n));
+}
+
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data) {
+    sstr_t a = *(const sstr_t*) s1;
+    sstr_t b = *(const sstr_t*) s2;
+    return sstrcmp(a, b);
+}
+
+int ucx_cmp_int(const void *i1, const void *i2, void *data) {
+   int a = *((const int*) i1);
+   int b = *((const int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_longint(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_longlong(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_int16(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_int32(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_int64(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_uint(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_ulongint(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_uint16(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_uint32(const void *i1, const void *i2, void *data) {
+   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 ucx_cmp_uint64(const void *i1, const void *i2, void *data) {
+   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;
+   }
+}
+
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int*) i1);
+   intmax_t b = *((const int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long int*) i1);
+   intmax_t b = *((const long int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long long*) i1);
+   intmax_t b = *((const long long*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int16_t*) i1);
+   intmax_t b = *((const int16_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int32_t*) i1);
+   intmax_t b = *((const int32_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int64_t*) i1);
+   intmax_t b = *((const int64_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned int*) i1);
+   uintmax_t b = *((const unsigned int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long int*) i1);
+   uintmax_t b = *((const unsigned long int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long long*) i1);
+   uintmax_t b = *((const unsigned long long*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint16_t*) i1);
+   uintmax_t b = *((const uint16_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint32_t*) i1);
+   uintmax_t b = *((const uint32_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint64_t*) i1);
+   uintmax_t b = *((const uint64_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) {
+   float a = *((const float*) f1);
+   float b = *((const float*) f2);
+   float e = !epsilon ? 1e-6f : *((float*)epsilon);
+   if (fabsf(a - b) < e) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) {
+   double a = *((const double*) d1);
+   double b = *((const double*) d2);
+   double e = !epsilon ? 1e-14 : *((double*)epsilon);
+   if (fabs(a - b) < e) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) {
+    const intptr_t p1 = (const intptr_t) ptr1;
+    const intptr_t p2 = (const intptr_t) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1  < p2 ? -1 : 1;
+    }
+}
+
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) {
+    return memcmp(ptr1, ptr2, *((size_t*)n));
+}
+
+/* PRINTF FUNCTIONS */
+
+#ifdef va_copy
+#define UCX_PRINTF_BUFSIZE 256
+#else
+#pragma message("WARNING: C99 va_copy macro not supported by this platform" \
+                " - limiting ucx_*printf to 2 KiB")
+#define UCX_PRINTF_BUFSIZE 0x800
+#endif
+
+int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...) {
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = ucx_vfprintf(stream, wfc, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap) {
+    char buf[UCX_PRINTF_BUFSIZE];
+#ifdef va_copy
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < UCX_PRINTF_BUFSIZE) {
+        return (int)wfc(buf, 1, ret, stream);
+    } else {
+        if (ret == INT_MAX) {
+            errno = ENOMEM;
+            return -1;
+        }
+        
+        int len = ret + 1;
+        char *newbuf = (char*)malloc(len);
+        if (!newbuf) {
+            return -1;
+        }
+        
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        if (ret > 0) {
+            ret = (int)wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+#else
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < UCX_PRINTF_BUFSIZE) {
+        return (int)wfc(buf, 1, ret, stream);
+    } else {
+        errno = ENOMEM;
+        return -1;
+    }
+#endif
+}
+
+sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...) {
+    va_list ap;
+    sstr_t ret;
+    va_start(ap, fmt);
+    ret = ucx_vasprintf(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+sstr_t ucx_vasprintf(UcxAllocator *a, const char *fmt, va_list ap) {
+    sstr_t s;
+    s.ptr = NULL;
+    s.length = 0;
+    char buf[UCX_PRINTF_BUFSIZE];
+#ifdef va_copy
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
+        s.ptr = (char*)almalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t)ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else if (ret == INT_MAX) {
+        errno = ENOMEM;
+    } else  {
+        int len = ret + 1;
+        s.ptr = (char*)almalloc(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;
+            }
+        }
+    }
+#else
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
+        s.ptr = (char*)almalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t)ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else {
+        errno = ENOMEM;
+    }
+#endif
+    return s;
+}
diff --git a/ui/Makefile b/ui/Makefile
new file mode 100644 (file)
index 0000000..865a03c
--- /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/common/context.c b/ui/common/context.c
new file mode 100644 (file)
index 0000000..628a4b4
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * 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 "context.h"
+#include "../ui/window.h"
+#include "document.h"
+#include "types.h"
+
+static UiContext* global_context;
+
+void uic_init_global_context(void) {
+    UcxMempool *mp = ucx_mempool_new(32);
+    global_context = uic_context(NULL, mp);
+}
+
+UiContext* ui_global_context(void) {
+    return global_context;
+}
+
+UiContext* uic_context(UiObject *toplevel, UcxMempool *mp) {
+    UiContext *ctx = ucx_mempool_malloc(mp, sizeof(UiContext));
+    memset(ctx, 0, sizeof(UiContext));
+    ctx->mempool = mp;
+    ctx->obj = toplevel;
+    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    
+    ctx->attach_document = uic_context_attach_document;
+    ctx->detach_document2 = uic_context_detach_document2;
+    
+#ifdef UI_GTK
+    if(toplevel && toplevel->widget) {
+        ctx->accel_group = gtk_accel_group_new();
+        gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group);
+    }
+#endif
+    
+    return ctx;
+}
+
+UiContext* uic_root_context(UiContext *ctx) {
+    return ctx->parent ? uic_root_context(ctx->parent) : ctx;
+}
+
+void uic_context_attach_document(UiContext *ctx, void *document) {
+    ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document);
+    ctx->document = ctx->documents->data;
+    
+    UiContext *doc_ctx = ui_document_context(document);
+    
+    // check if any parent context has an unbound variable with the same name
+    // as any document variable
+    UiContext *var_ctx = ctx;
+    while(var_ctx) {
+        if(var_ctx->vars_unbound && var_ctx->vars_unbound->count > 0) {
+            UcxMapIterator i = ucx_map_iterator(var_ctx->vars_unbound);
+            UiVar *var;
+            // rmkeys holds all keys, that shall be removed from vars_unbound
+            UcxKey *rmkeys = calloc(var_ctx->vars_unbound->count, sizeof(UcxKey));
+            size_t numkeys = 0;
+            UCX_MAP_FOREACH(key, var, i) {
+                UiVar *docvar = ucx_map_get(doc_ctx->vars, key);
+                if(docvar) {
+                    // bind var to document var
+                    uic_copy_binding(var, docvar, TRUE);
+                    rmkeys[numkeys++] = key; // save the key for removal
+                }
+            }
+            // now that we may have bound some vars to the document,
+            // we can remove them from the unbound map
+            for(size_t k=0;k<numkeys;k++) {
+                ucx_map_remove(var_ctx->vars_unbound, rmkeys[k]);
+            }
+        }
+        
+        var_ctx = ctx->parent;
+    }
+}
+
+static void uic_context_unbind_vars(UiContext *ctx) {
+    UcxMapIterator i = ucx_map_iterator(ctx->vars);
+    UiVar *var;
+    UCX_MAP_FOREACH(key, var, i) {
+        if(var->from && var->from_ctx) {
+            uic_save_var2(var);
+            uic_copy_binding(var, var->from, FALSE);
+            ucx_map_put(var->from_ctx->vars_unbound, key, var->from);
+            var->from_ctx = ctx;
+        }
+    }
+    
+    UCX_FOREACH(elm, ctx->documents) {
+        UiContext *subctx = ui_document_context(elm->data);
+        uic_context_unbind_vars(subctx);
+    }
+}
+
+void uic_context_detach_document2(UiContext *ctx, void *document) {
+    // find the document in the documents list
+    UcxList *doc = NULL;
+    UCX_FOREACH(elm, ctx->documents) {
+        if(elm->data == document) {
+            doc = elm;
+            break;
+        }
+    }
+    if(!doc) {
+        return; // document is not a subdocument of this context
+    }
+    
+    ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc);
+    ctx->document = ctx->documents ? ctx->documents->data : NULL;
+    
+    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) {
+    UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL);
+    UCX_FOREACH(elm, ls) {
+        ctx->detach_document2(ctx, elm->data);
+    }
+    ucx_list_free(ls);
+}
+
+static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) {
+    UiVar *var = ucx_map_get(ctx->vars, key);
+    if(!var) {
+        UCX_FOREACH(elm, ctx->documents) {
+            UiContext *subctx = ui_document_context(elm->data);
+            var = ctx_getvar(subctx, key);
+            if(var) {
+                break;
+            }
+        }
+    }
+    return var;
+}
+
+UiVar* uic_get_var(UiContext *ctx, const char *name) {
+    UcxKey key = ucx_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;
+
+    if(!ctx->vars_unbound) {
+        ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16);
+    }
+    ucx_map_cstr_put(ctx->vars_unbound, name, var);
+    
+    return var;
+}
+
+void* uic_create_value(UiContext *ctx, UiVarType type) {
+    void *val = NULL;
+    switch(type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: {
+            val = ui_int_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_DOUBLE: {
+            val = ui_double_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_STRING: {
+            val = ui_string_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_TEXT: {
+            val = ui_text_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_LIST: {
+            val = ui_list_new(ctx, NULL);
+            break;
+        }
+        case UI_VAR_RANGE: {
+            val = ui_range_new(ctx, NULL);
+            break;
+        }
+    }
+    return val;
+}
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) {
+    // check type
+    if(from->type != to->type) {
+        fprintf(stderr, "UI Error: var has incompatible type.\n");
+        return;
+    }
+    
+    void *fromvalue = from->value;
+    // update var
+    if(copytodoc) {
+        to->from = from;
+        to->from_ctx = from->from_ctx;
+    }
+    
+    // copy binding
+    // we don't copy the observer, because the from var has never one
+    switch(from->type) {
+        default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break;
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: {
+            UiInteger *f = fromvalue;
+            UiInteger *t = to->value;
+            if(!f->obj) break;
+            uic_int_copy(f, t);
+            t->set(t, t->value);
+            break;
+        }
+        case UI_VAR_DOUBLE: {
+            UiDouble *f = fromvalue;
+            UiDouble *t = to->value;
+            if(!f->obj) break;
+            uic_double_copy(f, t);
+            t->set(t, t->value);
+            break;
+        }
+        case UI_VAR_STRING: {
+            UiString *f = fromvalue;
+            UiString *t = to->value;
+            if(!f->obj) break;
+            uic_string_copy(f, t);
+            char *tvalue = t->value.ptr ? t->value.ptr : "";
+            t->set(t, tvalue);
+            break;
+        }
+        case UI_VAR_TEXT: {
+            UiText *f = fromvalue;
+            UiText *t = to->value;
+            if(!f->obj) break;
+            uic_text_copy(f, t);
+            char *tvalue = t->value.ptr ? t->value.ptr : "";
+            t->set(t, tvalue);
+            t->setposition(t, t->pos);
+            break;
+        }
+        case UI_VAR_LIST: {
+            UiList *f = fromvalue;
+            UiList *t = to->value;
+            if(!f->obj) break;
+            uic_list_copy(f, t);
+            t->update(t, -1);
+            break;
+        }
+        case UI_VAR_RANGE: {
+            UiRange *f = fromvalue;
+            UiRange *t = to->value;
+            if(!f->obj) break;
+            uic_range_copy(f, t);
+            t->setextent(t, t->extent);
+            t->setrange(t, t->min, t->max);
+            t->set(t, t->value);
+            break;
+        }
+    }
+}
+
+void uic_save_var2(UiVar *var) {
+    switch(var->type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: uic_int_save(var->value); break;
+        case UI_VAR_DOUBLE: uic_double_save(var->value); break;
+        case UI_VAR_STRING: uic_string_save(var->value); break;
+        case UI_VAR_TEXT: uic_text_save(var->value); break;
+        case UI_VAR_LIST: break;
+        case UI_VAR_RANGE: uic_range_save(var->value); break;
+    }
+}
+
+void uic_unbind_var(UiVar *var) {
+    switch(var->type) {
+        case UI_VAR_SPECIAL: break;
+        case UI_VAR_INTEGER: uic_int_unbind(var->value); break;
+        case UI_VAR_DOUBLE: uic_double_unbind(var->value); break;
+        case UI_VAR_STRING: uic_string_unbind(var->value); break;
+        case UI_VAR_TEXT: uic_text_unbind(var->value); break;
+        case UI_VAR_LIST: uic_list_unbind(var->value); break;
+        case UI_VAR_RANGE: uic_range_unbind(var->value); break;
+    }
+}
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) {
+    // TODO: do we need/want this? Why adding vars to a context after
+    // widgets reference these? Workarounds:
+    // 1. add vars to ctx before creating ui
+    // 2. create ui, create new document with vars, attach doc
+    // also it would be possible to create a function, that scans unbound vars
+    // and connects them to available vars
+    /*
+    UiContext *rootctx = uic_root_context(ctx); 
+    UiVar *b = NULL;
+    if(rootctx->bound) {
+        // some widgets are already bound to some vars
+        b = ucx_map_cstr_get(rootctx->bound, name);
+        if(b) {
+            // a widget is bound to a var with this name
+            // if ctx is the root context we can remove the var from bound
+            // because set_doc or detach can't fuck things up
+            if(ctx == rootctx) {
+                ucx_map_cstr_remove(ctx->bound, name);
+                // TODO: free stuff
+            }
+        }
+    }
+    */
+    
+    // create new var and add it to doc's vars
+    UiVar *var = ui_malloc(ctx, sizeof(UiVar));
+    var->type = type;
+    var->value = value;
+    var->from = NULL;
+    var->from_ctx = ctx;
+    size_t oldcount = ctx->vars->count;
+    ucx_map_cstr_put(ctx->vars, name, var);
+    if(ctx->vars->count != 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: implement
+    printf("TODO: implement uic_remove_bound_var\n");
+}
+
+
+// public API
+
+void ui_attach_document(UiContext *ctx, void *document) {
+    uic_context_attach_document(ctx, document);
+}
+
+void ui_detach_document2(UiContext *ctx, void *document) {
+    uic_context_detach_document2(ctx, document);
+}
+
+void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) {
+    ctx->close_callback = fnc;
+    ctx->close_data = udata;
+}
+
+
+void ui_set_group(UiContext *ctx, int group) {
+    if(ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL) == -1) {
+        ctx->groups = ucx_list_append_a(ctx->mempool->allocator, ctx->groups, (void*)(intptr_t)group);
+    }
+    
+    // enable/disable group widgets
+    uic_check_group_widgets(ctx);
+}
+
+void ui_unset_group(UiContext *ctx, int group) {
+    int i = ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL);
+    if(i != -1) {
+        UcxList *elm = ucx_list_get(ctx->groups, i);
+        ctx->groups = ucx_list_remove_a(ctx->mempool->allocator, ctx->groups, elm);
+    }
+    
+    // enable/disable group widgets
+    uic_check_group_widgets(ctx);
+}
+
+int* ui_active_groups(UiContext *ctx, int *ngroups) {
+    if(!ctx->groups) {
+        return NULL;
+    }
+    
+    int nelm = ucx_list_size(ctx->groups);
+    int *groups = calloc(sizeof(int), nelm);
+    
+    int i = 0;
+    UCX_FOREACH(elm, ctx->groups) {
+        groups[i++] = (intptr_t)elm->data;
+    }
+    
+    *ngroups = nelm;
+    return groups;
+}
+
+void uic_check_group_widgets(UiContext *ctx) {
+    int ngroups = 0;
+    int *groups = ui_active_groups(ctx, &ngroups);
+    
+    UCX_FOREACH(elm, ctx->group_widgets) {
+        UiGroupWidget *gw = elm->data;
+        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;
+            }
+        }
+        gw->enable(gw->widget, enable);
+    }
+    
+    if(groups) {
+        free(groups);
+    }
+}
+
+void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
+    // get groups
+    UcxList *groups = NULL;
+    va_list ap;
+    va_start(ap, enable);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        groups = ucx_list_append(groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    uic_add_group_widget(ctx, widget, enable, groups);
+    
+    ucx_list_free(groups);
+}
+
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups) {
+    UcxMempool *mp = ctx->mempool;
+    UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget));
+    
+    gw->widget = widget;
+    gw->enable = enable;
+    gw->numgroups = ucx_list_size(groups);
+    gw->groups = ucx_mempool_calloc(mp, gw->numgroups, sizeof(int));
+    int i = 0;
+    UCX_FOREACH(elm, groups) {
+        gw->groups[i++] = (intptr_t)elm->data;
+    }
+    
+    ctx->group_widgets = ucx_list_append_a(
+            mp->allocator,
+            ctx->group_widgets,
+            gw);
+}
+
+void* ui_malloc(UiContext *ctx, size_t size) {
+    return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL;
+}
+
+void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) {
+    return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL;
+}
+
+void ui_free(UiContext *ctx, void *ptr) {
+    if(ctx) {
+        ucx_mempool_free(ctx->mempool, ptr);
+    }
+}
+
+void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
+    return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL;
+}
+
diff --git a/ui/common/context.h b/ui/common/context.h
new file mode 100644 (file)
index 0000000..14b2ed8
--- /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.
+ */
+
+#ifndef UIC_CONTEXT_H
+#define        UIC_CONTEXT_H
+
+#include "../ui/toolkit.h"
+#include <ucx/map.h>
+#include <ucx/mempool.h>
+#include <ucx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiVar         UiVar;
+typedef struct UiListPtr     UiListPtr;
+typedef struct UiListVar     UiListVar;
+typedef struct UiGroupWidget UiGroupWidget;
+
+typedef enum UiVarType UiVarType;
+
+enum UiVarType {
+    UI_VAR_SPECIAL = 0,
+    UI_VAR_INTEGER,
+    UI_VAR_DOUBLE,
+    UI_VAR_STRING,
+    UI_VAR_TEXT,
+    UI_VAR_LIST,
+    UI_VAR_RANGE
+};
+
+struct UiContext {
+    UiContext     *parent;
+    UiObject      *obj;
+    UcxMempool    *mempool;
+    
+    void          *document;
+    UcxList       *documents;
+    
+    UcxMap        *vars; // manually created context vars
+    UcxMap        *vars_unbound; // unbound vars created by widgets
+    
+    UcxList       *groups; // int list
+    UcxList       *group_widgets; // UiGroupWidget* list
+    
+    void (*attach_document)(UiContext *ctx, void *document);
+    void (*detach_document2)(UiContext *ctx, void *document); 
+    
+    char          *title;
+    
+#ifdef UI_GTK
+    GtkAccelGroup *accel_group;
+#endif
+    
+    ui_callback   close_callback;
+    void          *close_data;
+};
+
+// UiVar replacement, rename it to UiVar when finished
+struct UiVar {
+    void      *value;
+    UiVarType type;
+    UiVar    *from;
+    UiContext *from_ctx;
+};
+
+struct UiGroupWidget {
+    void          *widget;
+    ui_enablefunc enable;
+    int           *groups;
+    int           numgroups;
+};
+
+
+void uic_init_global_context(void);
+
+UiContext* uic_context(UiObject *toplevel, UcxMempool *mp);
+UiContext* uic_root_context(UiContext *ctx);
+void uic_context_set_document(UiContext *ctx, void *document); // deprecated
+void uic_context_detach_document(UiContext *ctx); // deprecated
+
+void uic_context_attach_document(UiContext *ctx, void *document);
+void uic_context_detach_document2(UiContext *ctx, void *document);
+void uic_context_detach_all(UiContext *ctx);
+
+UiVar* uic_get_var(UiContext *ctx, const char *name);
+UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type);
+void* uic_create_value(UiContext *ctx, UiVarType type);
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc);
+void uic_save_var2(UiVar *var);
+void uic_unbind_var(UiVar *var);
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value);
+
+void uic_remove_bound_var(UiContext *ctx, UiVar *var);
+
+void uic_check_group_widgets(UiContext *ctx);
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups);
+
+
+#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..f225949
--- /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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "document.h"
+
+static UcxMap *documents;
+
+void uic_docmgr_init() {
+    documents = ucx_map_new(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) {
+    UcxMempool *mp = ucx_mempool_new(256);
+    UiContext *ctx = ucx_mempool_calloc(mp, 1, sizeof(UiContext));
+    ctx->attach_document = uic_context_attach_document;
+    ctx->detach_document2 = uic_context_detach_document2;
+    ctx->mempool = mp;
+    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    
+    void *document = ucx_mempool_calloc(mp, 1, size);
+    ucx_map_put(documents, ucx_key(&document, sizeof(void*)), ctx);
+    return document;
+}
+
+void ui_document_destroy(void *doc) {
+    // TODO
+}
+
+UiContext* ui_document_context(void *doc) {
+    if(doc) {
+        return ucx_map_get(documents, ucx_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/object.c b/ui/common/object.c
new file mode 100644 (file)
index 0000000..42a31be
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "object.h"
+#include "context.h"
+
+void ui_end(UiObject *obj) {
+    if(!obj->next) {
+        return;
+    }
+    
+    UiObject *prev = NULL;
+    while(obj->next) {
+        prev = obj;
+        obj = obj->next;
+    }
+    
+    if(prev) {
+        // TODO: free last obj
+        prev->next = NULL;
+    }
+}
+
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
+    UiContext *ctx = toplevel->ctx;
+    
+    UiObject *newobj = ucx_mempool_calloc(ctx->mempool, 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..cf0bd3c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_OBJECT_H
+#define        UIC_OBJECT_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
+UiObject* uic_current_obj(UiObject *toplevel);
+
+UiContainer* uic_get_current_container(UiObject *obj);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_OBJECT_H */
+
diff --git a/ui/common/objs.mk b/ui/common/objs.mk
new file mode 100644 (file)
index 0000000..2bb35a7
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# 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 += properties.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..177e98a
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "properties.h"
+#include <ucx/string.h>
+#include <ucx/buffer.h>
+#include <ucx/properties.h>
+
+static UiProperties *application_properties;
+static UiProperties *language;
+
+#ifndef UI_COCOA
+
+static char *locales_dir;
+static char *pixmaps_dir;
+
+#endif
+
+char* ui_getappdir() {
+    if(ui_appname() == NULL) {
+        return NULL;
+    }
+    
+    return ui_configfile(NULL);
+}
+
+char* ui_configfile(char *name) {
+    char *appname = ui_appname();
+    if(!appname) {
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    
+    // add base dir
+    char *homeenv = getenv("HOME");
+    if(homeenv == NULL) {
+        ucx_buffer_free(buf);
+        return NULL;
+    }
+    sstr_t home = sstr(homeenv);
+    
+    ucx_buffer_write(home.ptr, 1, home.length, buf);
+    if(home.ptr[home.length-1] != '/') {
+        ucx_buffer_putc(buf, '/');
+    }
+    
+#ifdef UI_COCOA
+    // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/
+    ucx_buffer_puts(buf, "Library/Application Support/");
+#else
+    // app dir is $HOME/.$APPNAME/
+    ucx_buffer_putc(buf, '.');
+#endif
+    ucx_buffer_puts(buf, appname);
+    ucx_buffer_putc(buf, '/');
+    
+    // add file name
+    if(name) {
+        ucx_buffer_puts(buf, name);
+    }
+    
+    char *path = buf->space;
+    free(buf);
+    return path;  
+}
+
+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 = ucx_map_new(128);
+    
+    if(!ui_appname()) {
+        // applications without name cannot load app properties
+        return;
+    }
+    
+    char *dir = ui_configfile(NULL);
+    if(!dir) {
+        return;
+    }
+    if(ui_mkdir(dir)) {
+        if(errno != EEXIST) {
+            fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir);
+            free(dir);
+            return;
+        }
+    }
+    free(dir);
+    
+    char *path = ui_configfile("application.properties");
+    if(!path) {
+        return;
+    }
+    
+    FILE *file = fopen(path, "r");
+    if(!file) {
+        free(path);
+        return;
+    }
+    
+    if(ucx_properties_load(application_properties, file)) {
+        fprintf(stderr, "Ui Error: Cannot load application properties.\n");
+    }
+    
+    fclose(file);
+    free(path);
+}
+
+void uic_store_app_properties() {
+    char *path = ui_configfile("application.properties");
+    if(!path) {
+        return;
+    }
+    
+    FILE *file = fopen(path, "w");
+    if(!file) {
+        fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path);
+        free(path);
+        return;
+    }
+    
+    if(ucx_properties_store(application_properties, file)) {
+        fprintf(stderr, "Ui Error: Cannot store application properties.\n");
+    }
+    
+    fclose(file);
+    free(path);
+}
+
+
+char* ui_get_property(char *name) {
+    return ucx_map_cstr_get(application_properties, name);
+}
+
+void ui_set_property(char *name, char *value) {
+    ucx_map_cstr_put(application_properties, name, value);
+}
+
+void ui_set_default_property(char *name, char *value) {
+    char *v = ucx_map_cstr_get(application_properties, name);
+    if(!v) {
+        ucx_map_cstr_put(application_properties, name, value);
+    }
+}
+
+
+
+static char* uic_concat_path(const char *base, const char *p, const char *ext) {
+    size_t baselen = strlen(base);
+    
+    UcxBuffer *buf = ucx_buffer_new(NULL, 32, UCX_BUFFER_AUTOEXTEND);
+    if(baselen > 0) {
+        ucx_buffer_write(base, 1, baselen, buf);
+        if(base[baselen - 1] != '/') {
+            ucx_buffer_putc(buf, '/');
+        }
+    }
+    ucx_buffer_write(p, 1, strlen(p), buf);
+    if(ext) {
+        ucx_buffer_write(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) {
+    UcxMap *lang = ucx_map_new(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);
+    
+    ucx_map_rehash(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 ucx_map_cstr_get(language, name);
+}
+
diff --git a/ui/common/properties.h b/ui/common/properties.h
new file mode 100644 (file)
index 0000000..26281d1
--- /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 <ucx/map.h>
+#include <ucx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+#define UI_HOME "USERPROFILE"
+#else
+#define UI_HOME "HOME"
+#endif
+
+void uic_load_app_properties();
+void uic_store_app_properties();
+
+int uic_load_language_file(const char *path);
+char* uic_get_image_path(const char *imgfilename);
+    
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_PROPERTIES_H */
+
diff --git a/ui/common/types.c b/ui/common/types.c
new file mode 100644 (file)
index 0000000..18933f7
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * 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 <ucx/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 = NULL;
+    list->iter = NULL;
+    
+    list->update = NULL;
+    list->obj = NULL;
+    
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_LIST, list);
+    }
+    
+    return list;
+}
+
+void ui_list_free(UiList *list) {
+    ucx_list_free(list->data);
+    free(list);
+}
+
+void* ui_list_first(UiList *list) {
+    UcxList *elm = list->data;
+    list->iter = elm;
+    return elm ? elm->data : NULL;
+}
+
+void* ui_list_next(UiList *list) {
+    UcxList *elm = list->iter;
+    if(elm) {
+        elm = elm->next;
+        if(elm) {
+            list->iter = elm;
+            return elm->data;
+        }
+    }
+    return NULL;
+}
+
+void* ui_list_get(UiList *list, int i) {
+    UcxList *elm = ucx_list_get(list->data, i);
+    if(elm) {
+        list->iter = elm;
+        return elm->data;
+    } else {
+        return NULL;
+    }
+}
+
+int ui_list_count(UiList *list) {
+    UcxList *elm = list->data;
+    return (int)ucx_list_size(elm);
+}
+
+void ui_list_append(UiList *list, void *data) {
+    list->data = ucx_list_append(list->data, data);
+}
+
+void ui_list_prepend(UiList *list, void *data) {
+    list->data = ucx_list_prepend(list->data, data);
+}
+
+void ui_list_clear(UiList *list) {
+    ucx_list_free(list->data);
+    list->data = NULL;
+}
+
+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);
+    
+    UcxList *cols = NULL;
+    int type;
+    while((type = va_arg(ap, int)) != -1) {
+        char *name = va_arg(ap, char*);
+        
+        UiColumn *column = malloc(sizeof(UiColumn));
+        column->type = type;
+        column->name = name;
+        
+        cols = ucx_list_append(cols, column);
+    }
+    
+    va_end(ap);
+    
+    size_t len = ucx_list_size(cols);
+    info->columns = len;
+    info->types = ui_calloc(ctx, len, sizeof(UiModelType));
+    info->titles = ui_calloc(ctx, len, sizeof(char*));
+    
+    int i = 0;
+    UCX_FOREACH(elm, cols) {
+        UiColumn *c = elm->data;
+        info->types[i] = c->type;
+        info->titles[i] = c->name;
+        free(c);
+        i++;
+    }
+    ucx_list_free(cols);
+    
+    return info;
+}
+
+void ui_model_free(UiContext *ctx, UiModel *mi) {
+    ucx_mempool_free(ctx->mempool, mi->types);
+    ucx_mempool_free(ctx->mempool, mi->titles);
+    ucx_mempool_free(ctx->mempool, mi);
+}
+
+// types
+
+// public functions
+UiInteger* ui_int_new(UiContext *ctx, char *name) {
+    UiInteger *i = ui_malloc(ctx, sizeof(UiInteger));
+    memset(i, 0, sizeof(UiInteger));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_INTEGER, i);
+    }
+    return i;
+}
+
+UiDouble* ui_double_new(UiContext *ctx, char *name) {
+    UiDouble *d = ui_malloc(ctx, sizeof(UiDouble));
+    memset(d, 0, sizeof(UiDouble));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_DOUBLE, d);
+    }
+    return d;
+}
+
+UiString* ui_string_new(UiContext *ctx, char *name) {
+    UiString *s = ui_malloc(ctx, sizeof(UiString));
+    memset(s, 0, sizeof(UiString));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_STRING, s);
+    }
+    return s;
+}
+
+UiText* ui_text_new(UiContext *ctx, char *name) {
+    UiText *t = ui_malloc(ctx, sizeof(UiText));
+    memset(t, 0, sizeof(UiText));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_TEXT, t);
+    }
+    return t;
+}
+
+UiRange* ui_range_new(UiContext *ctx, char *name) {
+    UiRange *r = ui_malloc(ctx, sizeof(UiRange));
+    memset(r, 0, sizeof(UiRange));
+    if(name) {
+        uic_reg_var(ctx, name, UI_VAR_RANGE, r);
+    }
+    return r;
+}
+
+
+// private functions
+void uic_int_copy(UiInteger *from, UiInteger *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_double_copy(UiDouble *from, UiDouble *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_string_copy(UiString *from, UiString *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->obj = from->obj;
+}
+
+void uic_text_copy(UiText *from, UiText *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->getsubstr = from->getsubstr;
+    to->insert = from->insert;
+    to->setposition = from->setposition;
+    to->position = from->position;
+    to->selection = from->selection;
+    to->length = from->length;
+    to->remove = from->remove;
+    
+    to->obj = from->obj;
+    // do not copy the undo manager
+}
+
+void uic_range_copy(UiRange *from, UiRange *to) {
+    to->get = from->get;
+    to->set = from->set;
+    to->setrange = from->setrange;
+    to->setextent = from->setextent;
+    to->obj = from->obj;
+}
+
+void uic_list_copy(UiList *from, UiList *to) {
+    to->update = from->update;
+    to->obj = from->obj;
+}
+
+
+void uic_int_save(UiInteger *i) {
+    if(!i->obj) return;
+    i->value = i->get(i);
+}
+
+void uic_double_save(UiDouble *d) {
+    if(!d->obj) return;
+    d->value = d->get(d);
+}
+
+void uic_string_save(UiString *s) {
+    if(!s->obj) return;
+    s->get(s);
+}
+
+void uic_text_save(UiText *t) {
+    if(!t->obj) return;
+    t->get(t);
+    t->position(t);
+}
+
+void uic_range_save(UiRange *r) {
+    if(!r->obj) return;
+    r->get(r);
+}
+
+
+void uic_int_unbind(UiInteger *i) {
+    i->get = NULL;
+    i->set = NULL;
+    i->obj = NULL;
+}
+
+void uic_double_unbind(UiDouble *d) {
+    d->get = NULL;
+    d->set = NULL;
+    d->obj = NULL;
+}
+
+void uic_string_unbind(UiString *s) {
+    s->get = NULL;
+    s->set = NULL;
+    s->obj = NULL;
+}
+
+void uic_text_unbind(UiText *t) {
+    t->set = NULL;
+    t->get = NULL;
+    t->getsubstr = NULL;
+    t->insert = NULL;
+    t->setposition = NULL;
+    t->position = NULL;
+    t->selection = NULL;
+    t->length = NULL;
+    t->remove = NULL;
+    t->obj = NULL;
+    t->undomgr = NULL;
+}
+
+void uic_range_unbind(UiRange *r) {
+    r->get = NULL;
+    r->set = NULL;
+    r->setextent = NULL;
+    r->setrange = NULL;
+    r->obj = NULL;
+}
+
+void uic_list_unbind(UiList *l) {
+    l->update = NULL;
+    l->obj = NULL;
+}
diff --git a/ui/common/types.h b/ui/common/types.h
new file mode 100644 (file)
index 0000000..b1d5a2c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_TYPES_H
+#define        UIC_TYPES_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void uic_int_copy(UiInteger *from, UiInteger *to);
+void uic_double_copy(UiDouble *from, UiDouble *to);
+void uic_string_copy(UiString *from, UiString *to);
+void uic_text_copy(UiText *from, UiText *to);
+void uic_range_copy(UiRange *from, UiRange *to);
+void uic_list_copy(UiList *from, UiList *to);
+
+void uic_int_save(UiInteger *i);
+void uic_double_save(UiDouble *d);
+void uic_string_save(UiString *s);
+void uic_text_save(UiText *t);
+void uic_range_save(UiRange *r);
+
+void uic_int_unbind(UiInteger *i);
+void uic_double_unbind(UiDouble *d);
+void uic_string_unbind(UiString *s);
+void uic_text_unbind(UiText *t);
+void uic_range_unbind(UiRange *r);
+void uic_list_unbind(UiList *l);
+    
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TYPES_H */
+
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..0add84f
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "button.h"
+#include "container.h"
+#include <ucx/mempool.h>
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+    GtkWidget *button = gtk_button_new_with_label(label);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = data;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, button, FALSE);
+    
+    return button;
+}
+
+
+void ui_button_clicked(GtkWidget *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = event->value;
+    event->callback(&e, event->userdata);
+}
+
+int64_t ui_toggle_button_get(UiInteger *integer) {
+    GtkToggleButton *button = integer->obj;
+    integer->value = (int)gtk_toggle_button_get_active(button);
+    return integer->value;
+}
+
+void ui_toggle_button_set(UiInteger *integer, int64_t value) {
+    GtkToggleButton *button = integer->obj;
+    integer->value = value;
+    gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = event->var->value;
+    e.intval = gtk_toggle_tool_button_get_active(widget);
+    
+    UiInteger *i = event->var->value;
+    ui_notify_evt(i->observers, &e);
+}
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var) {
+    GtkWidget *button = gtk_check_button_new_with_label(label);
+    
+    // bind value
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = GTK_TOGGLE_BUTTON(button);
+        value->get = ui_toggle_button_get;
+        value->set = ui_toggle_button_set;
+        gtk_toggle_button_set_active(value->obj, value->value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = NULL;
+
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_toggled_obs),
+                event);
+        g_signal_connect(
+                button,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, button, FALSE);
+    
+    return button;
+}
+
+UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) {
+    UiVar *var = NULL;
+    if(value) {
+        var = malloc(sizeof(UiVar));
+        var->value = value;
+        var->type = UI_VAR_SPECIAL;
+    }
+    return ui_checkbox_var(obj, label, var);
+}
+
+UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_checkbox_var(obj, label, var);
+}
+
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) {
+    GSList *rg = NULL;
+    UiInteger *rgroup;
+    
+    if(var) {
+        rgroup = var->value;
+        rg = rgroup->obj;
+    }
+    
+    GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label);
+    rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
+    
+    if(rgroup) {
+        rgroup->obj = rg;
+        rgroup->get = ui_radiobutton_get;
+        rgroup->set = ui_radiobutton_set;
+        
+        ui_radiobutton_set(rgroup, rgroup->value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = NULL;
+        
+        g_signal_connect(
+                rbutton,
+                "clicked",
+                G_CALLBACK(ui_radio_obs),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, rbutton, FALSE);
+    
+    return rbutton;
+}
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+    UiVar *var = NULL;
+    if(rgroup) {
+        var = malloc(sizeof(UiVar));
+        var->value = rgroup;
+        var->type = UI_VAR_SPECIAL;
+    }
+    return ui_radiobutton_var(obj, label, var);
+}
+
+UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_radiobutton_var(obj, label, var);
+}
+
+void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+    UiInteger *i = event->var->value;
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = i->get(i);
+    
+    ui_notify_evt(i->observers, &e);
+}
+
+int64_t ui_radiobutton_get(UiInteger *value) {
+    int selection = 0;
+    GSList *ls = value->obj;
+    int i = 0;
+    guint len = g_slist_length(ls);
+    while(ls) {
+        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ls->data))) {
+            selection = len - i - 1;
+            break;
+        }
+        ls = ls->next;
+        i++;
+    }
+    
+    value->value = selection;
+    return selection;
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+    GSList *ls = value->obj;
+    int s = g_slist_length(ls) - 1 - i;
+    int j = 0;
+    while(ls) {
+        if(j == s) {
+            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ls->data), TRUE);
+            break;
+        }
+        ls = ls->next;
+        j++;
+    }
+    
+    value->value = i;
+}
+
diff --git a/ui/gtk/button.h b/ui/gtk/button.h
new file mode 100644 (file)
index 0000000..eac2337
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define        BUTTON_H
+
+#include "../ui/toolkit.h"
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// event wrapper
+void ui_button_clicked(GtkWidget *widget, UiEventData *event);
+
+
+void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
+
+void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUTTON_H */
+
diff --git a/ui/gtk/container.c b/ui/gtk/container.c
new file mode 100644 (file)
index 0000000..551eb85
--- /dev/null
@@ -0,0 +1,627 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "container.h"
+#include "toolkit.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+void ui_container_begin_close(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(ct->close) {
+        ui_end(obj);
+        return 0;
+    }
+    return 1;
+}
+
+GtkWidget* ui_gtk_vbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+#else
+    return gtk_vbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_gtk_hbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+#else
+    return gtk_hbox_new(FALSE, spacing);
+#endif
+}
+
+/* -------------------- Frame Container (deprecated) -------------------- */
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
+    UiContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiContainer));
+    ct->widget = frame;
+    ct->add = ui_frame_container_add;
+    return ct;
+}
+
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Box Container -------------------- */
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
+    UiBoxContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiBoxContainer));
+    ct->container.widget = box;
+    ct->container.add = ui_box_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiBoxContainer *bc = (UiBoxContainer*)ct;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
+    
+    if(bc->has_fill && fill) {
+        fprintf(stderr, "UiError: container has 2 filled widgets");
+        fill = FALSE;
+    }
+    if(fill) {
+        bc->has_fill = TRUE;
+    }
+    
+    UiBool expand = fill;
+    gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
+    UiGridContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiGridContainer));
+    ct->container.widget = grid;
+    ct->container.add = ui_grid_container_add;
+#ifdef UI_GTK2
+    ct->width = 0;
+    ct->height = 1;
+#endif
+    return (UiContainer*)ct;
+}
+
+#ifdef UI_GTK3
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    
+    if(hexpand) {
+        gtk_widget_set_hexpand(widget, TRUE);
+    }
+    if(vexpand) {
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+    int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1;
+    
+    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1);
+    grid->x += gwidth;
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+#ifdef UI_GTK2
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    
+    gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
+    grid->x++;
+    int nw = grid->x > grid->width ? grid->x : grid->width;
+    if(grid->x > grid->width || grid->y > grid->height) {
+        grid->width = nw;
+        grid->height = grid->y + 1;
+        gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height);
+    }
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
+    UiContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiContainer));
+    ct->widget = scrolledwindow;
+    ct->add = ui_scrolledwindow_container_add;
+    return ct;
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: check if the widget implements GtkScrollable
+#ifdef UI_GTK3
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+#else
+    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget);
+#endif
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
+    UiTabViewContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiTabViewContainer));
+    ct->container.widget = tabview;
+    ct->container.add = ui_tabview_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_notebook_append_page(
+            GTK_NOTEBOOK(ct->widget),
+            widget,
+            gtk_label_new(ct->layout.label));
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_vbox_sp(obj, 0, 0);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_hbox_sp(obj, 0, 0);
+}
+
+static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
+    GtkWidget *ret = box;
+#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(box, margin);
+    gtk_widget_set_margin_end(box, margin);
+#else
+    gtk_widget_set_margin_left(box, margin);
+    gtk_widget_set_margin_right(box, margin);
+#endif
+    gtk_widget_set_margin_top(box, margin);
+    gtk_widget_set_margin_bottom(box, margin);
+#elif defined(UI_GTK2)
+    GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+    gtk_container_add(GTK_CONTAINER(a), box);
+    ret = a;
+#endif
+    return ret;
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(spacing);   
+    GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, vbox);
+    newobj->container = ui_box_container(obj, vbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *hbox = ui_gtk_hbox_new(spacing);
+    GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, hbox);
+    newobj->container = ui_box_container(obj, hbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *widget;
+    
+#ifdef UI_GTK3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(grid, margin);
+    gtk_widget_set_margin_end(grid, margin);
+#else
+    gtk_widget_set_margin_left(grid, margin);
+    gtk_widget_set_margin_right(grid, margin);
+#endif
+    gtk_widget_set_margin_top(grid, margin);
+    gtk_widget_set_margin_bottom(grid, margin);
+    
+    widget = grid;
+#elif defined(UI_GTK2)
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    
+    gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+    
+    if(margin > 0) {
+        GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+        gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+        gtk_container_add(GTK_CONTAINER(a), grid);
+        widget = a;
+    } else {
+        widget = grid;
+    }
+#endif
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, grid);
+    newobj->container = ui_grid_container(obj, grid);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
+    ct->add(ct, sw, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, sw);
+    newobj->container = ui_scrolledwindow_container(obj, sw);
+    uic_obj_add(obj, newobj);
+    
+    return sw;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiObject *tabviewobj = uic_object_new(obj, tabview);
+    tabviewobj->container = ui_tabview_container(obj, tabview);
+    uic_obj_add(obj, tabviewobj);
+    
+    return tabview;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.label = title;
+    ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+/* -------------------- Splitpane -------------------- */
+
+static GtkWidget* create_paned(UiOrientation orientation) {
+#ifdef UI_GTK3
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+    }
+#else
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_hpaned_new();
+        case UI_VERTICAL: return gtk_vpaned_new();
+    }
+#endif
+    return NULL;
+}
+
+UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) {
+    GtkWidget *paned = create_paned(orientation);
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    if(max <= 0) max = INT_MAX;
+    
+    UiPanedContainer *pctn = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiPanedContainer));
+    pctn->container.widget = paned;
+    pctn->container.add = ui_paned_container_add;
+    pctn->current_pane = paned;
+    pctn->orientation = orientation;
+    pctn->max = max;
+    pctn->cur = 0;
+    
+    UiObject *pobj = uic_object_new(obj, paned);
+    pobj->container = (UiContainer*)pctn;
+    
+    uic_obj_add(obj, pobj);
+    
+    return paned;
+}
+
+UIWIDGET ui_hsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_HORIZONTAL);
+}
+
+UIWIDGET ui_vsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_VERTICAL);
+}
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiPanedContainer *pctn = (UiPanedContainer*)ct;
+    
+    gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE;
+    int width = ct->layout.width;
+    ui_reset_layout(ct->layout);
+    
+    if(pctn->cur == 0) {
+        gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize);
+    } else if(pctn->cur < pctn->max-1) {
+        GtkWidget *nextPane = create_paned(pctn->orientation);
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE);
+        gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize);
+        pctn->current_pane = nextPane;
+    } else if(pctn->cur == pctn->max-1) {
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize);
+        width = 0; // disable potential call of gtk_paned_set_position
+    } else {
+        fprintf(stderr, "Splitpane max reached: %d\n", pctn->max);
+        return;
+    }
+    
+    if(width > 0) {
+        gtk_paned_set_position(GTK_PANED(pctn->current_pane), width);
+    }
+    
+    pctn->cur++;
+}
+
+
+/* -------------------- Sidebar (deprecated) -------------------- */
+UIWIDGET ui_sidebar(UiObject *obj) {
+#ifdef UI_GTK3
+    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+    GtkWidget *paned = gtk_hpaned_new();
+#endif
+    gtk_paned_set_position(GTK_PANED(paned), 200);
+    
+    GtkWidget *sidebar = ui_gtk_vbox_new(0);
+    gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE);
+    
+    UiObject *left = uic_object_new(obj, sidebar);
+    UiContainer *ct1 = ui_box_container(obj, sidebar);
+    left->container = ct1;
+    
+    UiObject *right = uic_object_new(obj, sidebar);
+    UiContainer *ct2 = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiContainer));
+    ct2->widget = paned;
+    ct2->add = ui_split_container_add2;
+    right->container = ct2;
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    uic_obj_add(obj, right);
+    uic_obj_add(obj, left);
+    
+    return sidebar;
+}
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: remove
+    gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Document Tabview -------------------- */
+static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) {
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    UiObject *tab = g_object_get_qdata(G_OBJECT(page), q);
+    if(!tab) {
+        return;
+    }
+    
+    //printf("page_change: %d\n", page_num);
+    UiContext *ctx = tab->ctx;
+    uic_context_detach_all(ctx->parent); // TODO: fix?
+    ctx->parent->attach_document(ctx->parent, ctx->document);
+}
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    
+    g_signal_connect(
+                tabview,
+                "switch-page",
+                G_CALLBACK(page_change),
+                NULL);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane));
+    tabbedpane->ctx = uic_current_obj(obj)->ctx;
+    tabbedpane->widget = tabview;
+    tabbedpane->document = NULL;
+    
+    return tabbedpane;
+}
+
+UiObject* ui_document_tab(UiTabbedPane *view) {
+    GtkWidget *frame = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+    // TODO: label
+    gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL);
+    
+    UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject));
+    tab->widget = NULL; // initialization for uic_context()
+    tab->ctx = uic_context(tab, view->ctx->mempool);
+    tab->ctx->parent = view->ctx;
+    tab->ctx->attach_document = uic_context_attach_document;
+    tab->ctx->detach_document2 = uic_context_detach_document2;
+    tab->widget = frame;
+    tab->window = view->ctx->obj->window;
+    tab->container = ui_frame_container(tab, frame);
+    tab->next = NULL;
+    
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    g_object_set_qdata(G_OBJECT(frame), q, tab);
+    
+    return tab;
+}
+
+void ui_tab_set_document(UiContext *ctx, void *document) {
+    // TODO: remove?
+    if(ctx->parent->document) {
+        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+    }
+    //uic_context_set_document(ctx, document);
+    //uic_context_set_document(ctx->parent, document);
+    //ctx->parent->document = document;
+}
+
+void ui_tab_detach_document(UiContext *ctx) {
+    // TODO: remove?
+    //uic_context_detach_document(ctx->parent);
+}
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.vexpand = expand;
+}
+
+void ui_layout_width(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.width = width;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.newline = TRUE;
+}
+
diff --git a/ui/gtk/container.h b/ui/gtk/container.h
new file mode 100644 (file)
index 0000000..f2c0740
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define        CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+    
+typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool);
+
+typedef struct UiDocumentView UiDocumentView;
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+enum UiLayoutBool {
+    UI_LAYOUT_UNDEFINED = 0,
+    UI_LAYOUT_TRUE,
+    UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+    UiLayoutBool fill;
+    UiBool       newline;
+    char         *label;
+    UiBool       hexpand;
+    UiBool       vexpand;
+    int          width;
+    int          gridwidth;
+};
+
+struct UiContainer {
+    GtkWidget *widget;
+    GtkMenu *menu;
+    GtkWidget *current;
+    
+    void (*add)(UiContainer*, GtkWidget*, UiBool);
+    UiLayout layout;
+    
+    int close;
+};
+
+typedef struct UiBoxContainer {
+    UiContainer container;
+    UiBool has_fill;
+} UiBoxContainer;
+
+typedef struct UiGridContainer {
+    UiContainer container;
+    int x;
+    int y;
+#ifdef UI_GTK2
+    int width;
+    int height;
+#endif
+} UiGridContainer;
+
+typedef struct UiPanedContainer {
+    UiContainer container;
+    GtkWidget *current_pane;
+    int orientation;
+    int max;
+    int cur;
+} UiPanedContainer;
+
+typedef struct UiTabViewContainer {
+    UiContainer container;
+} UiTabViewContainer;
+
+GtkWidget* ui_gtk_vbox_new(int spacing);
+GtkWidget* ui_gtk_hbox_new(int spacing);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow);
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview);
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill);
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+
+UiObject* ui_add_document_tab(UiDocumentView *view);
+void ui_tab_set_document(UiContext *ctx, void *document);
+void ui_tab_detach_document(UiContext *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONTAINER_H */
+
diff --git a/ui/gtk/display.c b/ui/gtk/display.c
new file mode 100644 (file)
index 0000000..83ce3d4
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 <ucx/mempool.h>
+#include "../common/context.h"
+#include "../common/object.h"
+
+static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+    gtk_label_set_xalign(GTK_LABEL(widget), xalign);
+    gtk_label_set_yalign(GTK_LABEL(widget), yalign);
+#else
+    gtk_misc_set_alignment(GTK_MISC(widget), xalign, yalign);
+#endif
+}
+
+UIWIDGET ui_label(UiObject *obj, char *label) { 
+    GtkWidget *widget = gtk_label_new(label);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, FALSE);
+    
+    return widget;
+}
+
+UIWIDGET ui_llabel(UiObject *obj, char *label) {
+    UIWIDGET widget = ui_label(obj, label);
+    set_alignment(widget, 0, .5);
+    return widget;
+}
+
+UIWIDGET ui_rlabel(UiObject *obj, char *label) {
+    UIWIDGET widget = ui_label(obj, label);
+    //gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_RIGHT);
+    
+    set_alignment(widget, 1, .5);
+    return widget;
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+    GtkWidget *widget = gtk_label_new("");
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, TRUE);
+    
+    return widget;
+}
+
+UIWIDGET ui_separator(UiObject *obj) {
+#if UI_GTK3
+    GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+    GtkWidget *widget = gtk_hseparator_new();
+#endif
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, FALSE);
+    
+    return widget;
+}
+
+/* ------------------------- progress bar ------------------------- */
+
+UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return ui_progressbar_var(obj, var);
+}
+
+UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
+    return ui_progressbar_var(obj, var);
+}
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var) {
+    GtkWidget *progressbar = gtk_progress_bar_new();
+    if(var && var->value) {
+        UiDouble *value = var->value;
+        value->get = ui_progressbar_get;
+        value->set = ui_progressbar_set;
+        value->obj = progressbar;
+        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 0.5);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, progressbar, FALSE);
+    
+    return progressbar;
+}
+
+double ui_progressbar_get(UiDouble *d) {
+    d->value = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+    return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value);
+    d->value = value;
+}
diff --git a/ui/gtk/display.h b/ui/gtk/display.h
new file mode 100644 (file)
index 0000000..f9d94e8
--- /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 LABEL_H
+#define        LABEL_H
+
+#include "../ui/toolkit.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c
new file mode 100644 (file)
index 0000000..d110ad4
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dnd.h"
+#include <ucx/buffer.h>
+
+#ifdef UI_GTK2LEGACY
+static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    char *uri;
+    int i = 0;
+    while((uri = uris[i]) != NULL) {
+        ucx_buffer_puts(buf, uri);
+        ucx_buffer_puts(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);
+    ucx_buffer_free(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;
+}
diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h
new file mode 100644 (file)
index 0000000..43ab5c2
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
diff --git a/ui/gtk/draw_cairo.c b/ui/gtk/draw_cairo.c
new file mode 100644 (file)
index 0000000..6e1ebca
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_cairo.h"
+
+#ifdef UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+    UiCairoGraphics g;
+    g.g.width = gtk_widget_get_allocated_width(w);
+    g.g.height = gtk_widget_get_allocated_height(w);
+    g.widget = w;
+    g.cr = cr;
+    
+    UiDrawEvent *event = data;
+    UiEvent ev;
+    ev.obj = event->obj;
+    ev.window = event->obj->window;
+    ev.document = event->obj->ctx->document;
+    
+    event->callback(&ev, &g.g, event->userdata);
+    
+    return FALSE;
+}
+#else
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+    UiCairoGraphics g;
+    g.g.width = w->allocation.width;
+    g.g.height = w->allocation.height;
+    g.widget = w;
+    g.cr = gdk_cairo_create(w->window);
+    
+    UiDrawEvent *event = data;
+    UiEvent ev;
+    ev.obj = event->obj;
+    ev.window = event->obj->window;
+    ev.document = event->obj->ctx->document;
+    
+    event->callback(&ev, &g.g, event->userdata);
+    
+    return FALSE;
+}
+#endif
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+#ifdef UI_GTK3
+    g_signal_connect(G_OBJECT(widget),
+            "draw",
+            G_CALLBACK(ui_drawingarea_expose),
+            event);
+#else
+    g_signal_connect(G_OBJECT(widget),
+            "expose_event",
+            G_CALLBACK(ui_canvas_expose),
+            event);
+#endif
+}
+
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    //return gtk_widget_get_pango_context(gr->widget);
+    return pango_cairo_create_context(gr->cr);
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    double dred = (double)red / (double)255;
+    double dgreen = (double)green / (double)255;
+    double dblue = (double)blue / (double)255;
+    cairo_set_source_rgb(gr->cr, dred, dgreen, dblue);
+}
+
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    cairo_set_line_width(gr->cr, 1);
+    cairo_move_to(gr->cr, (double)x1 + 0.5, (double)y1 + 0.5);
+    cairo_line_to(gr->cr, (double)x2 + 0.5, (double)y2 + 0.5);
+    cairo_stroke(gr->cr);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g;
+    cairo_set_line_width(gr->cr, 1);
+    cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h);
+    if(fill) {
+        cairo_fill(gr->cr);
+    } else {
+        cairo_stroke(gr->cr);
+    }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+    UiCairoGraphics *gr = (UiCairoGraphics*)g; 
+    cairo_move_to(gr->cr, x, y);
+    pango_cairo_show_layout(gr->cr, text->layout);
+}
+
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..631beb0
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * 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"
+
+#include <ucx/mempool.h>
+
+UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = i;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
+}
+
+UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = d;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
+}
+
+UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = r;
+    var->type = UI_VAR_SPECIAL;
+    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
+}
+
+UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER);
+    return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER);
+}
+
+UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE);
+    return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE);
+}
+
+UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_RANGE);
+    UiRange *r = var->value;
+    return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE);
+}
+
+UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type) {
+    double min = 0;
+    double max = 1000;
+    if(type == UI_VAR_RANGE) {
+        UiRange *r = var->value;
+        min = r->min;
+        max = r->max;
+    }
+    if(step == 0) {
+        step = 1;
+    }
+#ifdef UI_GTK2LEGACY
+    if(min == max) {
+        max = min + 1;
+    }
+#endif
+    GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step);
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
+    if(var) {
+        double value = 0;
+        UiObserver **obs = NULL;
+        switch(type) {
+            default: break;
+            case UI_VAR_INTEGER: {
+                UiInteger *i = var->value;
+                i->get = ui_spinbutton_getint;
+                i->set = ui_spinbutton_setint;
+                i->obj = spin;
+                value = (double)i->value;
+                obs = &i->observers;
+                break;
+            }
+            case UI_VAR_DOUBLE: {
+                UiDouble *d = var->value;
+                d->get = ui_spinbutton_getdouble;
+                d->set = ui_spinbutton_setdouble;
+                d->obj = spin;
+                value = d->value;
+                obs = &d->observers;
+                break;
+            }
+            case UI_VAR_RANGE: {
+                UiRange *r = var->value;
+                r->get = ui_spinbutton_getrangeval;
+                r->set = ui_spinbutton_setrangeval;
+                r->setrange = ui_spinbutton_setrange;
+                r->setextent = ui_spinbutton_setextent;
+                r->obj = spin;
+                value = r->value;
+                obs = &r->observers;
+                break;
+            }
+        }
+        gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
+        
+        UiVarEventData *event = malloc(sizeof(UiVarEventData));
+        event->obj = obj;
+        event->var = var;
+        event->observers = obs;
+        
+        g_signal_connect(
+                spin,
+                "value-changed",
+                G_CALLBACK(ui_spinner_changed),
+                event);
+        g_signal_connect(
+                spin,
+                "destroy",
+                G_CALLBACK(ui_destroy_vardata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, spin, FALSE);
+    
+    return spin;
+}
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max) {
+    gtk_spin_button_set_range(GTK_SPIN_BUTTON(spinner), min, max);
+}
+
+void ui_spinner_setdigits(UIWIDGET spinner, int digits) {
+    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), digits);
+}
+
+
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = event->var->value;
+    e.intval = 0;
+    
+    UiObserver *observer = *event->observers;
+    ui_notify_evt(observer, &e);
+}
+
+
+int64_t ui_spinbutton_getint(UiInteger *i) {
+    i->value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(i->obj));
+    return i->value;
+}
+
+void ui_spinbutton_setint(UiInteger *i, int64_t val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->obj), (double)val);
+    i->value = val;
+}
+
+double ui_spinbutton_getdouble(UiDouble *d) {
+    d->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->obj));
+    return d->value;
+}
+
+void ui_spinbutton_setdouble(UiDouble *d, double val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->obj), val);
+    d->value = val;
+}
+
+double ui_spinbutton_getrangeval(UiRange *r) {
+    r->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(r->obj));
+    return r->value;
+}
+
+void ui_spinbutton_setrangeval(UiRange *r, double val) {
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->obj), val);
+    r->value = val;
+}
+void ui_spinbutton_setrange(UiRange *r, double min, double max) {
+    gtk_spin_button_set_range(GTK_SPIN_BUTTON(r->obj), min, max);
+    r->min = min;
+    r->max = max;
+}
+
+void ui_spinbutton_setextent(UiRange *r, double extent) {
+    gtk_spin_button_set_increments(GTK_SPIN_BUTTON(r->obj), extent, extent*10);
+    r->extent = extent;
+}
diff --git a/ui/gtk/entry.h b/ui/gtk/entry.h
new file mode 100644 (file)
index 0000000..34961ab
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/* 
+ * File:   entry.h
+ * Author: olaf
+ *
+ * Created on 11. November 2017, 13:38
+ */
+
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include "toolkit.h"
+#include "../ui/entry.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type);
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event);
+
+int64_t ui_spinbutton_getint(UiInteger *i);
+void ui_spinbutton_setint(UiInteger *i, int64_t val);
+
+double ui_spinbutton_getdouble(UiDouble *d);
+void ui_spinbutton_setdouble(UiDouble *d, double val);
+
+double ui_spinbutton_getrangeval(UiRange *r);
+void ui_spinbutton_setrangeval(UiRange *r, double val);
+void ui_spinbutton_setrange(UiRange *r, double min, double max);
+void ui_spinbutton_setextent(UiRange *r, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENTRY_H */
+
diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c
new file mode 100644 (file)
index 0000000..b7c279f
--- /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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "graphics.h"
+#include "container.h"
+#include "../common/object.h"
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+    GtkWidget *widget = gtk_drawing_area_new();
+    
+    if(f) {
+        UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = userdata;
+        ui_connect_draw_handler(widget, event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, widget, TRUE);
+    
+    return widget;
+}
+
+
+static gboolean widget_button_pressed(
+        GtkWidget *widget,
+        GdkEvent *event,
+        gpointer userdata)
+{
+    UiEventData *eventdata = userdata;
+    
+    UiMouseEvent me;
+    me.x = (int)event->button.x;
+    me.y = (int)event->button.y;
+    
+    int exec = 0;
+    if(event->button.type == GDK_BUTTON_PRESS) {
+        exec = 1;
+        me.type = UI_PRESS;
+    } else if(event->button.type == GDK_2BUTTON_PRESS) {
+        exec = 1;
+        me.type = UI_PRESS2;
+    }
+    
+    if(exec) {
+        UiEvent e;
+        e.obj = eventdata->obj;
+        e.window = eventdata->obj->window;
+        e.document = eventdata->obj->ctx->document;
+        e.eventdata = &me;
+        e.intval = 0;
+        eventdata->callback(&e, eventdata->userdata);
+    }
+    return TRUE;
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+#ifdef UI_GTK3
+        *width = gtk_widget_get_allocated_width(drawingarea);
+        *height = gtk_widget_get_allocated_height(drawingarea);
+#else
+        *width = drawingarea->allocation.width;
+        *height = drawingarea->allocation.height;
+#endif
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+    gtk_widget_queue_draw(drawingarea);
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+    gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK);
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->callback = f;
+        event->userdata = u;
+        
+        g_signal_connect(G_OBJECT(widget),
+                "button-press-event",
+                G_CALLBACK(widget_button_pressed),
+                event);
+    } else {
+         // TODO: warning
+    }
+}
+
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g) {
+    UiTextLayout *layout = malloc(sizeof(UiTextLayout));
+    PangoContext *pc = ui_get_pango_context(g);
+    layout->layout = pango_layout_new(pc);
+    return layout;
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+    pango_layout_set_text(layout->layout, str, -1);
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+    pango_layout_set_text(layout->layout, str, len);
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+    PangoFontDescription *fontDesc;
+    fontDesc = pango_font_description_from_string(font);
+    pango_font_description_set_size(fontDesc, size * PANGO_SCALE);
+    pango_layout_set_font_description(layout->layout, fontDesc);
+    pango_font_description_free(fontDesc);
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+    pango_layout_get_size(layout->layout, width, height);
+    *width = *width / PANGO_SCALE;
+    *height = *height / PANGO_SCALE;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+    pango_layout_set_width(layout->layout, width * PANGO_SCALE);
+    pango_layout_set_ellipsize(layout->layout, PANGO_ELLIPSIZE_END);
+    //pango_layout_set_wrap(layout->layout, PANGO_WRAP_WORD_CHAR);
+}
+
+void ui_text_free(UiTextLayout *text) {
+    g_object_unref(text->layout);
+    free(text);
+}
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/image.c b/ui/gtk/image.c
new file mode 100644 (file)
index 0000000..7b37be7
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucx/map.h>
+
+#include "toolkit.h"
+#include "image.h"
+#include "../common/properties.h"
+
+static UcxMap *image_map;
+
+static GtkIconTheme *icon_theme;
+
+void ui_image_init(void) {
+    image_map = ucx_map_new(8);
+    
+    icon_theme = gtk_icon_theme_get_default();
+}
+
+// **** deprecated functions ****
+
+GdkPixbuf* ui_get_image(const char *name) {
+    UiImage *img = ucx_map_cstr_get(image_map, name);
+    if(img) {
+        return img->pixbuf;
+    } else {
+        //ui_add_image(name, name);
+        //return ucx_map_cstr_get(image_map, name);
+        // TODO
+        return NULL;
+    }
+}
+
+// **** new functions ****
+
+static UiIcon* get_icon(const char *name, int size, int scale) {
+#ifdef UI_SUPPORTS_SCALE
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
+#else
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+#endif
+    if(info) {
+        UiIcon *icon = malloc(sizeof(UiIcon));
+        icon->info = info;
+        return icon;
+    }
+    return NULL;
+}
+
+UiIcon* ui_icon(const char *name, int size) {
+    return get_icon(name, size, ui_get_scalefactor());
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+    return get_icon(name, size, 1);
+}
+
+void ui_free_icon(UiIcon *icon) {
+    g_object_unref(icon->info);
+    free(icon);
+}
+
+UiImage* ui_icon_image(UiIcon *icon) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+    if(pixbuf) {
+        UiImage *img = malloc(sizeof(UiImage));
+        img->pixbuf = pixbuf;
+        return img;
+    }
+    return NULL;
+}
+
+UiImage* ui_image(const char *filename) {
+    return ui_named_image(filename, NULL);
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+    char *path =  uic_get_image_path(filename);
+    if(!path) {
+        fprintf(stderr, "UiError: pixmaps directory not set\n");
+        return NULL;
+    }
+    UiImage *img = ui_load_image_from_path(path, name);
+    free(path);
+    return img;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+    GError *error = NULL;
+    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+    if(!pixbuf) {
+        fprintf(stderr, "UiError: Cannot load image: %s\n", path);
+        return NULL;
+    }
+    
+    UiImage *img = malloc(sizeof(UiImage));
+    img->pixbuf = pixbuf;
+    if(name) {
+        ucx_map_cstr_put(image_map, name, img);
+    }
+    return img;
+}
+
+void ui_free_image(UiImage *img) {
+    g_object_unref(img->pixbuf);
+    free(img);
+}
diff --git a/ui/gtk/image.h b/ui/gtk/image.h
new file mode 100644 (file)
index 0000000..d335d54
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef IMAGE_H
+#define        IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+    
+struct UiIcon {
+    GtkIconInfo *info;
+};
+
+struct UiImage {
+    GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c
new file mode 100644 (file)
index 0000000..f7be2f5
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+ * 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 "../ui/properties.h"
+#include "../ui/window.h"
+#include "container.h"
+
+static UcxList *menus;
+static UcxList *current;
+
+void ui_menu(char *label) {
+    // free current menu hierarchy
+    ucx_list_free(current);
+    
+    // create menu
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
+    
+    menu->label  = label;
+    menu->items  = NULL;
+    menu->parent = NULL;    
+    
+    current = ucx_list_prepend(NULL, menu);
+    menus = ucx_list_append(menus, menu);
+    
+}
+
+void ui_submenu(char *label) {
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
+    
+    menu->label  = label;
+    menu->items  = NULL;
+    menu->parent = NULL;
+    
+    // add submenu to current menu
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, 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 = malloc(sizeof(UiMenuItem));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
+    
+    item->label = label;
+    item->userdata = userdata;
+    item->callback = f;
+    item->groups = NULL;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
+    if(!current) {
+        return;
+    }
+    
+    UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
+    
+    item->stockid = stockid;
+    item->userdata = userdata;
+    item->callback = f;
+    item->groups = NULL;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuseparator() {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->add_to = (ui_menu_add_f)add_menuseparator_widget;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_checkitem(char *label, ui_callback f, void *userdata) {
+    if(!current) {
+        return;
+    }
+    
+    UiCheckItem *item = malloc(sizeof(UiCheckItem));
+    item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
+    item->label = label;
+    item->callback = f;
+    item->userdata = userdata;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_checkitem_nv(char *label, char *vname) {
+    if(!current) {
+        return;
+    }
+    
+    UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
+    item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
+    item->varname = vname;
+    item->label = label;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
+    item->callback = f;
+    item->userdata = userdata;
+    item->list = items;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+// private menu functions
+GtkWidget *ui_create_menubar(UiObject *obj) {
+    if(menus == NULL) {
+        return NULL;
+    }
+    
+    GtkWidget *mb = gtk_menu_bar_new();
+    
+    UcxList *ls = menus;
+    while(ls) {
+        UiMenu *menu = ls->data;
+        menu->item.add_to(mb, 0, &menu->item, obj);
+        
+        ls = ls->next;
+    }
+    
+    return mb;
+}
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) {
+    UiMenu *menu = (UiMenu*)item;
+    
+    GtkWidget *menu_widget = gtk_menu_new();
+    GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
+    
+    UcxList *ls = menu->items;
+    int index = 0;
+    while(ls) {
+        UiMenuItemI *i = ls->data;
+        i->add_to(menu_widget, index, i, obj);
+        
+        ls = ls->next;
+        index++;
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item);
+}
+
+void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *i = (UiMenuItem*)item;
+    
+    //GtkWidget *widget = gtk_menu_item_new_with_label(i->title);
+    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(i->label);
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+    
+    if(i->groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
+    }
+}
+
+void add_menuitem_st_widget(
+        GtkWidget *parent,
+        int index,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    UiStMenuItem *i = (UiStMenuItem*)item;
+    
+    GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group);
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+    
+    if(i->groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
+    }
+}
+
+void add_menuseparator_widget(
+        GtkWidget *parent,
+        int index,
+        UiMenuItemI *item,
+        UiObject *obj)
+{
+    gtk_menu_shell_append(
+            GTK_MENU_SHELL(parent),
+            gtk_separator_menu_item_new());
+}
+
+void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiCheckItem *ci = (UiCheckItem*)item;
+    GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+    gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+    
+    if(ci->callback) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = ci->userdata;
+        event->callback = ci->callback;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(ui_menu_event_toggled),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+}
+
+void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiCheckItemNV *ci = (UiCheckItemNV*)item;
+    GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+    gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+    
+    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
+    if(var) {
+        UiInteger *value = var->value;
+        value->obj = widget;
+        value->get = ui_checkitem_get;
+        value->set = ui_checkitem_set;
+        value = 0;
+    } else {
+        // TODO: error
+    }
+}
+
+void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item;
+    UcxMempool *mp = obj->ctx->mempool;
+    
+    UiActiveMenuItemList *ls = ucx_mempool_malloc(
+            mp,
+            sizeof(UiActiveMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = GTK_MENU_SHELL(p);
+    ls->index = index;
+    ls->oldcount = 0;
+    ls->list = il->list;
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    ls->list->observers = ui_add_observer(
+            ls->list->observers,
+            (ui_callback)ui_update_menuitem_list,
+            ls);
+    
+    ui_update_menuitem_list(NULL, ls);
+}
+
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {    
+    // remove old items
+    if(list->oldcount > 0) {
+        int i = 0;
+        GList *mi = gtk_container_get_children(GTK_CONTAINER(list->menu));
+        while(mi) {
+            if(i >= list->index && i < list->index + list->oldcount) {
+                //gtk_container_remove(GTK_CONTAINER(list->menu), mi->data);
+                gtk_widget_destroy(mi->data);
+            }
+            mi = mi->next;
+            i++;
+        }
+    }
+    
+    char *str = ui_list_first(list->list);
+    if(str) {
+        GtkWidget *widget = gtk_separator_menu_item_new();
+        gtk_menu_shell_insert(list->menu, widget, list->index);
+        gtk_widget_show(widget);
+    }
+    int i = 1;
+    while(str) {
+        GtkWidget *widget = gtk_menu_item_new_with_label(str);
+        gtk_menu_shell_insert(list->menu, widget, list->index + i);
+        gtk_widget_show(widget);
+        
+        if(list->callback) {
+            // TODO: use mempool
+            UiEventData *event = malloc(sizeof(UiEventData));
+            event->obj = list->object;
+            event->userdata = list->userdata;
+            event->callback = list->callback;
+            event->value = i - 1;
+
+            g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+            g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+        }
+        
+        str = ui_list_next(list->list);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
+
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) {
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = NULL;
+    evt.intval = event->value;
+    event->callback(&evt, event->userdata);    
+}
+
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) {
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = NULL;
+    evt.intval = gtk_check_menu_item_get_active(ci);
+    event->callback(&evt, event->userdata);    
+}
+
+int64_t ui_checkitem_get(UiInteger *i) {
+    int state = gtk_check_menu_item_get_active(i->obj);
+    i->value = state;
+    return state;
+}
+
+void ui_checkitem_set(UiInteger *i, int64_t value) {
+    i->value = value;
+    gtk_check_menu_item_set_active(i->obj, value);
+}
+
+
+/*
+ * widget menu functions
+ */
+
+static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
+    if(event->type == GDK_BUTTON_PRESS) {
+        GdkEventButton *e = (GdkEventButton*)event;
+        if(e->button == 3) {
+            gtk_widget_show_all(GTK_WIDGET(menu));
+            ui_contextmenu_popup(menu);
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+UIMENU ui_contextmenu(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    return ui_contextmenu_w(obj, ct->current);
+}
+
+UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkMenu *menu = GTK_MENU(gtk_menu_new());
+    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
+    
+    ct->menu = menu;
+    return menu;
+}
+
+void ui_contextmenu_popup(UIMENU menu) {
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+    gtk_menu_popup_at_pointer(menu, NULL);
+#else
+    gtk_menu_popup(menu, NULL, NULL, 0, 0, 0, gtk_get_current_event_time());
+#endif
+}
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
+    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+}
+
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(!ct->menu) {
+        return;
+    }
+    
+    // add groups
+    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
+    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label);
+    gtk_widget_show(widget);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+    }
+}
+
+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
+    GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group);
+    gtk_widget_show(widget);
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                widget,
+                "activate",
+                G_CALLBACK(ui_menu_event_wrapper),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
+    
+    if(groups) {
+        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+    }
+}
diff --git a/ui/gtk/menu.h b/ui/gtk/menu.h
new file mode 100644 (file)
index 0000000..c42b955
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MENU_H
+#define        MENU_H
+
+#include "../ui/menu.h"
+#include <ucx/list.h>
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+typedef struct UiMenuItemI      UiMenuItemI;
+typedef struct UiMenu           UiMenu;
+typedef struct UiMenuItem       UiMenuItem;
+typedef struct UiStMenuItem     UiStMenuItem;
+typedef struct UiCheckItem      UiCheckItem;
+typedef struct UiCheckItemNV    UiCheckItemNV;
+typedef struct UiMenuItemList   UiMenuItemList;
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef GtkWidget*(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
+    
+struct UiMenuItemI {
+    ui_menu_add_f  add_to;
+};
+
+struct UiMenu {
+    UiMenuItemI    item;
+    char           *label;
+    UcxList        *items;
+    UiMenu         *parent;
+};
+
+struct UiMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *label;
+    void           *userdata;
+    UcxList        *groups;
+};
+
+struct UiStMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *stockid;
+    void           *userdata;
+    UcxList        *groups;
+};
+
+struct UiCheckItem {
+    UiMenuItemI    item;
+    char           *label;
+    ui_callback    callback;
+    void           *userdata;
+};
+
+struct UiCheckItemNV {
+    UiMenuItemI    item;
+    char           *label;
+    char           *varname;
+};
+
+struct UiMenuItemList {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    void           *userdata;
+    UiList         *list;
+};
+
+struct UiActiveMenuItemList {
+    UiObject     *object;
+    GtkMenuShell *menu;
+    int          index;
+    int          oldcount;
+    UiList       *list;
+    ui_callback  callback;
+    void         *userdata;
+};
+
+GtkWidget *ui_create_menubar(UiObject *obj);
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event);
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event);
+int64_t ui_checkitem_get(UiInteger *i);
+void ui_checkitem_set(UiInteger *i, int64_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_H */
+
diff --git a/ui/gtk/model.c b/ui/gtk/model.c
new file mode 100644 (file)
index 0000000..6db4e61
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "model.h"
+#include "image.h"
+#include "toolkit.h"
+
+#define IS_UI_LIST_MODEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type))
+#define UI_LIST_MODEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_CAST((obj), list_model_type, UiListModel))
+
+static void list_model_class_init(GObjectClass *cl, gpointer data);
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data);
+static void list_model_init(UiListModel *instance, GObjectClass *cl);
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data);
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data);
+
+static GObjectClass list_model_class;
+static const GTypeInfo list_model_info = {
+    sizeof(GObjectClass),
+    NULL,
+    NULL,
+    (GClassInitFunc)list_model_class_init,
+    NULL,
+    NULL,
+    sizeof(UiListModel),
+    0,
+    (GInstanceInitFunc)list_model_init
+};
+static const GInterfaceInfo list_model_interface_info = {
+    (GInterfaceInitFunc)list_model_interface_init,
+    NULL,
+    NULL
+};
+static GType list_model_type;
+
+static const GInterfaceInfo list_model_dnd_dest_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_dest_interface_init,
+    NULL,
+    NULL
+};
+static const GInterfaceInfo list_model_dnd_src_interface_info = {
+    (GInterfaceInitFunc)list_model_dnd_src_interface_init,
+    NULL,
+    NULL
+};
+
+void ui_list_init() {
+    list_model_type = g_type_register_static(
+            G_TYPE_OBJECT,
+            "UiListModel",
+            &list_model_info,
+            (GTypeFlags)0);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_MODEL,
+            &list_model_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_DEST,
+            &list_model_dnd_dest_interface_info);
+    g_type_add_interface_static(
+            list_model_type,
+            GTK_TYPE_TREE_DRAG_SOURCE,
+            &list_model_dnd_src_interface_info);
+}
+
+static void list_model_class_init(GObjectClass *cl, gpointer data) {
+    cl->dispose = ui_list_model_dispose;
+    cl->finalize = ui_list_model_finalize;
+    
+}
+
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) {
+    i->get_flags       = ui_list_model_get_flags;
+    i->get_n_columns   = ui_list_model_get_n_columns;
+    i->get_column_type = ui_list_model_get_column_type;
+    i->get_iter        = ui_list_model_get_iter;
+    i->get_path        = ui_list_model_get_path;
+    i->get_value       = ui_list_model_get_value;
+    i->iter_next       = ui_list_model_iter_next;
+    i->iter_children   = ui_list_model_iter_children;
+    i->iter_has_child  = ui_list_model_iter_has_child;
+    i->iter_n_children = ui_list_model_iter_n_children;
+    i->iter_nth_child  = ui_list_model_iter_nth_child;
+    i->iter_parent     = ui_list_model_iter_parent;
+}
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) {
+    i->drag_data_received = ui_list_model_drag_data_received;
+    i->row_drop_possible = ui_list_model_row_drop_possible;
+}
+
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) {
+    i->drag_data_delete = ui_list_model_drag_data_delete;
+    i->drag_data_get = ui_list_model_drag_data_get;
+    i->row_draggable = ui_list_model_row_draggable;
+}
+
+static void list_model_init(UiListModel *instance, GObjectClass *cl) {
+    instance->columntypes = NULL;
+    instance->var = NULL;
+    instance->numcolumns = 0;
+    instance->stamp = g_random_int();
+}
+
+static GType ui_gtk_type(UiModelType type) {
+    switch(type) {
+        default: break;
+        case UI_STRING: return G_TYPE_STRING;
+        case UI_INTEGER: return G_TYPE_INT;
+    }
+    return G_TYPE_INVALID;
+}
+
+static void ui_model_set_value(GType type, void *data, GValue *value) {
+    switch(type) {
+        default: break;
+        case G_TYPE_OBJECT: {
+            value->g_type = G_TYPE_OBJECT;
+            g_value_set_object(value, data);
+            return;
+        }
+        case G_TYPE_STRING: {
+            value->g_type = G_TYPE_STRING;
+            g_value_set_string(value, data);
+            return;
+        }
+        case G_TYPE_INT: {
+            value->g_type = G_TYPE_INT;
+            int *i = data;
+            g_value_set_int(value, *i);
+            return;
+        }
+    }
+    value->g_type = G_TYPE_INVALID; 
+}
+
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) {
+    UiListModel *model = g_object_new(list_model_type, NULL);
+    model->obj = obj;
+    model->model = info;
+    model->var = var;
+    model->columntypes = calloc(sizeof(GType), 2 * info->columns);
+    int ncol = 0;
+    for(int i=0;i<info->columns;i++) {
+        UiModelType type = info->types[i];
+        if(type == UI_ICON_TEXT) {
+            model->columntypes[ncol] = G_TYPE_OBJECT;
+            ncol++;
+            model->columntypes[ncol] = G_TYPE_STRING;
+        } else {
+            model->columntypes[ncol] = ui_gtk_type(info->types[i]);
+        }
+        ncol++;
+    }
+    model->numcolumns = ncol;
+    return model;
+}
+
+void ui_list_model_dispose(GObject *obj) {
+    
+}
+
+void ui_list_model_finalize(GObject *obj) {
+    
+}
+
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) {
+    return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
+}
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) {
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0);
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    return model->numcolumns;
+}
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) {
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID);
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID);
+    return model->columntypes[index];
+}
+
+gboolean ui_list_model_get_iter(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreePath *path)
+{
+    g_assert(IS_UI_LIST_MODEL(tree_model));
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    // check the depth of the path
+    // a list must have a depth of 1
+    gint depth = gtk_tree_path_get_depth(path);
+    g_assert(depth == 1);
+    
+    // get row
+    gint *indices = gtk_tree_path_get_indices(path);
+    gint row = indices[0];
+    
+    // check row
+    if(row == 0) {
+        // we don't need to count if the first element is requested
+        if(list->first(list) == NULL) {
+            return FALSE;
+        }
+    } else if(row >= list->count(list)) {
+        return FALSE;
+    }
+    
+    // the UiList has an integrated iterator
+    // we only get a value to adjust it
+    void *val = NULL;
+    if(row == 0) {
+        val = list->first(list);
+    } else {
+        val = list->get(list, row);
+    }
+    
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)(intptr_t)row; // list->index
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+GtkTreePath* ui_list_model_get_path(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL);
+    g_return_val_if_fail(iter != NULL, NULL);
+    g_return_val_if_fail(iter->user_data != NULL, NULL);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    
+    GtkTreePath *path = gtk_tree_path_new();
+    gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index
+    
+    return path;
+}
+
+void ui_list_model_get_value(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        gint column,
+        GValue *value)
+{
+    g_return_if_fail(IS_UI_LIST_MODEL(tree_model));
+    g_return_if_fail(iter != NULL);
+    g_return_if_fail(iter->user_data != NULL);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    g_return_if_fail(column < model->numcolumns);
+    
+    // TODO: return correct value from column
+    
+    //value->g_type = G_TYPE_STRING;
+    list->iter = iter->user_data;
+    //list->index = (int)(intptr_t)iter->user_data2;
+    //list->current = iter->user_data3;
+    if(model->model->getvalue) {
+        void *data = model->model->getvalue(iter->user_data3, column);
+        if(model->columntypes[column] == G_TYPE_OBJECT) {
+            UiImage *img = data;
+            ui_model_set_value(model->columntypes[column], img->pixbuf, value);
+        } else {
+            ui_model_set_value(model->columntypes[column], data, value);
+        }
+    } else {
+        value->g_type = G_TYPE_INVALID;
+    }
+}
+
+gboolean ui_list_model_iter_next(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    g_return_val_if_fail(iter != NULL, FALSE);
+    //g_return_val_if_fail(iter->user_data != NULL, FALSE);
+    
+    if(!iter->user_data) {
+        return FALSE;
+    }
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    list->iter = iter->user_data;
+    //list->index = (int)(intptr_t)iter->user_data2;
+    void *val = list->next(list);
+    iter->user_data = list->iter;
+    intptr_t index = (intptr_t)iter->user_data2;
+    index++;
+    //iter->user_data2 = (gpointer)(intptr_t)list->index;
+    iter->user_data2 = (gpointer)index;
+    iter->user_data3 = val;
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    if(parent) {
+        return FALSE;
+    }
+    
+    /*
+     * a list element has no children
+     * we set the iter to the first element
+     */
+    void *val = list->first(list);
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)0;
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_has_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    return FALSE;
+}
+
+gint ui_list_model_iter_n_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter)
+{
+    g_assert(IS_UI_LIST_MODEL(tree_model));
+    
+    if(!iter) {
+        // return number of rows
+        UiListModel *model = UI_LIST_MODEL(tree_model);
+        UiList *list = model->var->value;
+        return list->count(list);
+    }
+    
+    return 0;
+}
+
+gboolean ui_list_model_iter_nth_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent,
+        gint n)
+{
+    g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+    
+    if(parent) {
+        return FALSE;
+    }
+    
+    UiListModel *model = UI_LIST_MODEL(tree_model);
+    UiList *list = model->var->value;
+    
+    // check n
+    if(n == 0) {
+        // we don't need to count if the first element is requested
+        if(list->first(list) == NULL) {
+            return FALSE;
+        }
+    } else if(n >= list->count(list)) {
+        return FALSE;
+    }
+    
+    void *val = list->get(list, n);
+    iter->stamp = model->stamp;
+    iter->user_data = list->iter;
+    iter->user_data2 = (gpointer)(intptr_t)n; // list->index
+    iter->user_data3 = val;
+    
+    return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_parent(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *child)
+{
+    return FALSE;
+}
+
+// ****** dnd ******
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+        GtkSelectionData  *selection_data)
+{
+    //printf("drag received\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->drop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->drop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+       GtkSelectionData  *selection_data)
+{
+    //printf("row_drop_possible\n");
+    UiListModel *model = UI_LIST_MODEL(drag_dest);
+    if(model->model->candrop) {
+        gint *indices = gtk_tree_path_get_indices(dest_path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        return model->model->candrop(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path)
+{
+    //printf("row_draggable\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->candrag) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        return model->model->candrag(&e, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data)
+{
+    //printf("drag_data_get\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        UiSelection s;
+        s.data = selection_data;
+        model->model->data_get(&e, &s, model->var->value, row);
+    }
+    return TRUE;
+}
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path)
+{
+    //printf("drag_data_delete\n");
+    UiListModel *model = UI_LIST_MODEL(drag_source);
+    if(model->model->data_get) {
+        gint *indices = gtk_tree_path_get_indices(path);
+        gint row = indices[0];
+        UiEvent e;
+        e.obj = model->obj;
+        e.window = e.obj->window;
+        e.document = e.obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = 0;
+        model->model->data_delete(&e, model->var->value, row);
+    }
+    return TRUE;
+}
diff --git a/ui/gtk/model.h b/ui/gtk/model.h
new file mode 100644 (file)
index 0000000..e237826
--- /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.
+ */
+
+#ifndef MODEL_H
+#define        MODEL_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../ui/tree.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif    
+
+typedef struct UiListModel        UiListModel;
+
+/*
+ * UiList to GtkTreeModel wrapper
+ */
+struct UiListModel {
+    GObject  object;
+    UiObject *obj;
+    UiModel  *model;
+    UiVar   *var;
+    GType    *columntypes;
+    int      numcolumns;
+    int      stamp;
+};
+
+/*
+ * initialize the class and register the type
+ */
+void ui_list_init();
+
+/*
+ * Creates a UiListModel for a given UiList
+ */
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info);
+
+void ui_list_model_dispose(GObject *obj);
+void ui_list_model_finalize(GObject *obj);
+
+
+// interface functions
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model);
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model);
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index);
+
+gboolean ui_list_model_get_iter(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreePath *path);
+
+GtkTreePath* ui_list_model_get_path(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+void ui_list_model_get_value(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        gint column,
+        GValue *value);
+
+gboolean ui_list_model_iter_next(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent);
+
+gboolean ui_list_model_iter_has_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gint ui_list_model_iter_n_children(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_nth_child(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *parent,
+        gint n);
+
+gboolean ui_list_model_iter_parent(
+        GtkTreeModel *tree_model,
+        GtkTreeIter *iter,
+        GtkTreeIter *child);
+
+/* dnd */
+
+gboolean ui_list_model_drag_data_received(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest,
+        GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_drop_possible(
+        GtkTreeDragDest   *drag_dest,
+        GtkTreePath       *dest_path,
+       GtkSelectionData  *selection_data);
+
+gboolean ui_list_model_row_draggable(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path);
+
+gboolean ui_list_model_drag_data_get(
+        GtkTreeDragSource   *drag_source,
+        GtkTreePath         *path,
+        GtkSelectionData    *selection_data);
+
+gboolean ui_list_model_drag_data_delete(
+        GtkTreeDragSource *drag_source,
+        GtkTreePath       *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MODEL_H */
diff --git a/ui/gtk/objs.mk b/ui/gtk/objs.mk
new file mode 100644 (file)
index 0000000..3043aad
--- /dev/null
@@ -0,0 +1,50 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+GTK_SRC_DIR = ui/gtk/
+GTK_OBJPRE = $(OBJ_DIR)$(GTK_SRC_DIR)
+
+# some objects are defined in config.mk
+GTKOBJ += toolkit.o
+GTKOBJ += window.o
+GTKOBJ += container.o
+GTKOBJ += menu.o
+GTKOBJ += toolbar.o
+GTKOBJ += button.o
+GTKOBJ += display.o
+GTKOBJ += text.o
+GTKOBJ += model.o
+GTKOBJ += tree.o
+GTKOBJ += image.o
+GTKOBJ += graphics.o
+GTKOBJ += range.o
+GTKOBJ += entry.o
+GTKOBJ += dnd.o
+
+TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
+TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
diff --git a/ui/gtk/range.c b/ui/gtk/range.c
new file mode 100644 (file)
index 0000000..c191b0a
--- /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 <ucx/mempool.h>
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
+#ifdef UI_GTK3
+    GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL);
+#else
+    GtkWidget *scrollbar;
+    if(orientation == UI_HORIZONTAL) {
+        scrollbar = gtk_hscrollbar_new(NULL);
+    } else {
+        scrollbar = gtk_hscrollbar_new(NULL);
+    }
+#endif
+    
+    if(range) {
+        range->get = ui_scrollbar_get;
+        range->set = ui_scrollbar_set;
+        range->setrange = ui_scrollbar_setrange;
+        range->setextent = ui_scrollbar_setextent;
+        range->obj = scrollbar;
+    }
+    
+    if(f) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = userdata;
+        event->callback = f;
+        event->value = 0;
+        
+        g_signal_connect(
+                G_OBJECT(scrollbar),
+                "value-changed",
+                G_CALLBACK(ui_scrollbar_value_changed),
+                event);
+        g_signal_connect(
+                scrollbar,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scrollbar, FALSE);
+    
+    return scrollbar;
+}
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
+}
+
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+    return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
+}
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = event->value;
+    event->callback(&e, event->userdata); 
+    return TRUE;
+}
+
+double ui_scrollbar_get(UiRange *range) {
+    double value = gtk_range_get_value(GTK_RANGE(range->obj));
+    range->value = value;
+    return value;
+}
+
+void   ui_scrollbar_set(UiRange *range, double value) {
+    gtk_range_set_value(GTK_RANGE(range->obj), value);
+    range->value = value;
+}
+
+void   ui_scrollbar_setrange(UiRange *range, double min, double max) {
+    gtk_range_set_range(GTK_RANGE(range->obj), min, max);
+    range->min = min;
+    range->max = max;
+}
+
+void   ui_scrollbar_setextent(UiRange *range, double extent) {
+    GtkAdjustment *a = gtk_range_get_adjustment(GTK_RANGE(range->obj));
+#ifdef UI_GTK2LEGACY
+    a->page_size = extent;
+#else
+    gtk_adjustment_set_page_size(a, extent);
+#endif
+#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18)
+    gtk_adjustment_changed(a);
+#endif
+    range->extent = extent;
+}
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..2b86ec2
--- /dev/null
@@ -0,0 +1,657 @@
+/*
+ * 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"
+
+
+static void selection_handler(
+        GtkTextBuffer *buf,
+        GtkTextIter *location,
+        GtkTextMark *mark,
+        UiTextArea *textview)
+{
+    const char *mname = gtk_text_mark_get_name(mark);
+    if(mname) {
+        GtkTextIter begin;
+        GtkTextIter end;
+        int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
+        if(sel != textview->last_selection_state) {
+            if(sel) {
+                ui_set_group(textview->ctx, UI_GROUP_SELECTION);
+            } else {
+                ui_unset_group(textview->ctx, UI_GROUP_SELECTION);
+            }
+        }
+        textview->last_selection_state = sel;
+    }
+}
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+    GtkWidget *text_area = gtk_text_view_new();
+    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
+    g_signal_connect(
+            text_area,
+            "realize",
+            G_CALLBACK(ui_textarea_realize_event),
+            NULL);
+    
+    UiTextArea *uitext = malloc(sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    uitext->last_selection_state = 0;
+    
+    g_signal_connect(
+                text_area,
+                "destroy",
+                G_CALLBACK(ui_textarea_destroy),
+                uitext);
+    
+    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    
+    // font and padding
+    PangoFontDescription *font;
+    font = pango_font_description_from_string("Monospace");
+    gtk_widget_modify_font(text_area, font);
+    pango_font_description_free(font);
+    
+    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
+    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
+    
+    // add
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // bind value
+    UiText *value = var->value;
+    if(value) {
+        GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
+        
+        if(value->value.ptr) {
+            gtk_text_buffer_set_text(buf, value->value.ptr, -1);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->get = ui_textarea_get;
+        value->set = ui_textarea_set;
+        value->getsubstr = ui_textarea_getsubstr;
+        value->insert = ui_textarea_insert;
+        value->setposition = ui_textarea_setposition;
+        value->position = ui_textarea_position;
+        value->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->remove = ui_textarea_remove;
+        value->value.ptr = NULL;
+        value->value.free = NULL;
+        value->obj = buf;
+        if(!value->undomgr) {
+            value->undomgr = ui_create_undomgr();
+        }
+        
+        g_signal_connect(
+                buf,
+                "changed",
+                G_CALLBACK(ui_textbuf_changed),
+                uitext);
+        
+        // register undo manager
+        g_signal_connect(
+                buf,
+                "insert-text",
+                G_CALLBACK(ui_textbuf_insert),
+                var);
+        g_signal_connect(
+                buf,
+                "delete-range",
+                G_CALLBACK(ui_textbuf_delete),
+                var); 
+        g_signal_connect(
+                buf,
+                "mark-set",
+                G_CALLBACK(selection_handler),
+                uitext);
+    }
+    
+    return scroll_area;
+}
+
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
+    ui_destroy_boundvar(textarea->ctx, textarea->var);
+    free(textarea);
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    var->from = NULL;
+    var->from_ctx = NULL;
+    return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+    if(var) {
+        return ui_textarea_var(obj, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
+    return gtk_bin_get_child(GTK_BIN(textarea));
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter ib;
+    GtkTextIter ie;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
+    char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    GtkTextIter offset;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
+    gtk_text_buffer_insert(text->obj, &offset, str, -1);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    GtkTextIter iter;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
+    gtk_text_buffer_place_cursor(text->obj, &iter);
+}
+
+int ui_textarea_position(UiText *text) {
+    GtkTextIter begin;
+    GtkTextIter end;
+    gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
+    text->pos = gtk_text_iter_get_offset(&begin);
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    GtkTextIter b;
+    GtkTextIter e;
+    gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
+    *begin = gtk_text_iter_get_offset(&b);
+    *end = gtk_text_iter_get_offset(&e);
+}
+
+int ui_textarea_length(UiText *text) {
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    return gtk_text_iter_get_offset(&end);
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter ib;
+    GtkTextIter ie;
+    gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
+    gtk_text_buffer_delete(buf, &ib, &ie);
+}
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
+    gtk_widget_grab_focus(widget);
+}
+
+
+
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
+    UiText *value = textarea->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textarea->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textarea->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+// undo manager functions
+
+void ui_textbuf_insert(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *location,
+        char *text,
+        int length,
+        void *data)
+{
+    UiVar *var = data;
+    UiText *value = var->value;
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    if(mgr->cur) {
+        UcxList *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            while(elm) {
+                elm->prev = NULL;   
+                UcxList *next = elm->next;
+                ui_free_textbuf_op(elm->data);
+                free(elm);
+                elm = next;
+            }
+        }
+        
+        UiTextBufOp *last_op = mgr->cur->data;
+        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->type = UI_TEXTBUF_INSERT;
+    op->start = gtk_text_iter_get_offset(location);
+    op->end = op->start+length;
+    op->len = length;
+    op->text = dpstr;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+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) {
+        UcxList *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            while(elm) {
+                elm->prev = NULL;   
+                UcxList *next = elm->next;
+                ui_free_textbuf_op(elm->data);
+                free(elm);
+                elm = next;
+            }
+        }
+    }
+    
+    char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    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;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return 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->data;
+        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;
+    
+    UcxList *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->data;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_delete(value->obj, &begin, &end);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
+    GtkWidget *textfield = gtk_entry_new();
+    
+    UiTextField *uitext = malloc(sizeof(UiTextField));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    
+    g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(ui_textfield_destroy),
+                uitext);
+    
+    if(width > 0) {
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
+    }
+    if(frameless) {
+        // TODO: gtk2legacy workaroud
+        gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
+    }
+    if(password) {
+        gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, textfield, FALSE);
+    
+    if(var) {
+        UiString *value = var->value;
+        if(value->value.ptr) {
+            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            value->value.free(value->value.ptr);
+            value->value.ptr = NULL;
+            value->value.free = NULL;
+        }
+        
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+        value->value.ptr = NULL;
+        value->value.free = NULL;
+        value->obj = GTK_ENTRY(textfield);
+        
+        g_signal_connect(
+                textfield,
+                "changed",
+                G_CALLBACK(ui_textfield_changed),
+                uitext);
+    }
+    
+    return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+    if(var) {
+        return create_textfield_var(obj, width, frameless, password, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiVar *var = NULL;
+    if(value) {
+        var = malloc(sizeof(UiVar));
+        var->value = value;
+        var->type = UI_VAR_SPECIAL;
+        var->from = NULL;
+        var->from_ctx = NULL;
+    }
+    return create_textfield_var(obj, width, frameless, password, var);
+}
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
+    if(textfield->var) {
+        ui_destroy_boundvar(textfield->ctx, textfield->var);
+    }
+    free(textfield);
+}
+
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+    UiString *value = textfield->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textfield->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textfield->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    gtk_entry_set_text(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
diff --git a/ui/gtk/text.h b/ui/gtk/text.h
new file mode 100644 (file)
index 0000000..0eefa2a
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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 <ucx/list.h>
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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;
+
+typedef struct UiUndoMgr {
+    UcxList *begin;
+    UcxList *cur;
+    int     length;
+    int     event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+    UiContext *ctx;
+    UiVar    *var;
+    int       last_selection_state;
+} UiTextArea;
+
+typedef struct UiTextField {
+    UiContext *ctx;
+    UiVar    *var;
+    // TODO: validatefunc
+} UiTextField;
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
+
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+void ui_textarea_remove(UiText *text, int begin, int end);
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data);
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea);
+
+void ui_textbuf_insert(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *location,
+        char *text,
+        int len,
+        void *data);
+void ui_textbuf_delete(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *start,
+        GtkTextIter *end,
+        void *data);
+UiUndoMgr* ui_create_undomgr();
+void ui_free_textbuf_op(UiTextBufOp *op);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield);
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEXT_H */
+
diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c
new file mode 100644 (file)
index 0000000..3365722
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "toolbar.h"
+#include "button.h"
+#include "image.h"
+#include "tree.h"
+#include <ucx/mempool.h>
+#include "../common/context.h"
+
+static UcxMap *toolbar_items;
+static UcxList *defaults;
+
+void ui_toolbar_init() {
+    toolbar_items = ucx_map_new(16);
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
+    ui_toolitem_img(name, label, NULL, f, udata);
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
+    ui_toolitem_stgr(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *userdata) {
+    ui_toolitem_stgri(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+    va_list ap;
+    va_start(ap, userdata);
+    ui_toolitem_vstgr(name, stockid, 0, f, userdata, ap);
+    va_end(ap);
+}
+
+void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+    va_list ap;
+    va_start(ap, userdata);
+    ui_toolitem_vstgr(name, stockid, 1, f, userdata, ap);
+    va_end(ap);
+}
+
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
+    UiToolItem *item = malloc(sizeof(UiToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+    item->label = label;
+    item->image = img;
+    item->callback = f;
+    item->userdata = udata;
+    item->isimportant = 0;
+    item->groups = NULL;
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_vstgr(
+        char *name,
+        char *stockid,
+        int isimportant,
+        ui_callback f,
+        void *userdata,
+        va_list ap)
+{
+    UiStToolItem *item = malloc(sizeof(UiStToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
+    item->stockid = stockid;
+    item->callback = f;
+    item->userdata = userdata;
+    item->groups = NULL;
+    item->isimportant = isimportant;
+    
+    // add groups
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = label;
+    item->image = img;
+    item->stockid = NULL;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = i;
+    item->var = NULL;
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = NULL;
+    item->image = NULL;
+    item->stockid = stockid;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = i;
+    item->var = NULL;
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = label;
+    item->image = img;
+    item->stockid = NULL;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = NULL;
+    item->var = intvar;
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar) {
+    UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem));
+    item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+    item->label = NULL;
+    item->image = NULL;
+    item->stockid = stockid;
+    item->groups = NULL;
+    item->isimportant = 0;
+    item->value = NULL;
+    item->var = intvar;
+    
+    ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+
+void ui_toolbar_combobox(
+        char *name,
+        UiList *list,
+        ui_getvaluefunc getvalue,
+        ui_callback f,
+        void *udata)
+{
+    UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
+    cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    var->from = NULL;
+    var->from_ctx = NULL;
+    cb->var = var;
+    cb->getvalue = getvalue;
+    cb->callback = f;
+    cb->userdata = udata;
+    
+    ucx_map_cstr_put(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;
+    
+    ucx_map_cstr_put(toolbar_items, name, cb);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+    char *s = strdup(name);
+    defaults = ucx_list_append(defaults, s);
+}
+
+GtkWidget* ui_create_toolbar(UiObject *obj) {
+    if(!defaults) {
+        return NULL;
+    }
+    
+    GtkWidget *toolbar = gtk_toolbar_new();
+#ifdef UI_GTK3
+    gtk_style_context_add_class(
+            gtk_widget_get_style_context(toolbar),
+            GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
+#endif
+    
+    GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+    UCX_FOREACH(elm, defaults) {
+        UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data);
+        if(item) {
+            item->add_to(tb, item, obj);
+        } else if(!strcmp(elm->data, "@separator")) {
+            gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1);
+        } else {
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
+        }
+    }
+    
+    return toolbar;
+}
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj) {
+    GtkToolItem *button = gtk_tool_button_new(NULL, item->label);
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->image) {
+        GdkPixbuf *pixbuf = ui_get_image(item->image);
+        GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+        gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+    } else {
+        gtk_tool_item_set_is_important(button, TRUE);
+    }
+    
+    if(item->callback) {
+        UiEventData *event = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+    }
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) {
+    GtkToolItem *button = gtk_tool_button_new_from_stock(item->stockid);
+    gtk_tool_item_set_homogeneous(button, FALSE);
+    if(item->isimportant) {
+        gtk_tool_item_set_is_important(button, TRUE);
+    }
+    
+    if(item->callback) {
+        UiEventData *event = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = item->userdata;
+        event->callback = item->callback;
+        
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(ui_button_clicked),
+                event);
+    }
+    
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj) {
+    GtkToolItem *button;
+    if(item->stockid) {
+        button = gtk_toggle_tool_button_new_from_stock(item->stockid);
+    } else {
+        button = gtk_toggle_tool_button_new();
+        gtk_tool_item_set_homogeneous(button, FALSE);
+        if(item->label) {
+            gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->label);
+        }
+        if(item->image) {
+            GdkPixbuf *pixbuf = ui_get_image(item->image);
+            GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+            gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image);
+        }    
+    }
+    
+    UiVar *var;
+    if(item->value) {
+        var = malloc(sizeof(UiVar));
+        var->value = item->value;
+        var->type = UI_VAR_SPECIAL;
+        var->from = NULL;
+        var->from_ctx = NULL;
+    } else {
+        var = uic_create_var(obj->ctx, item->var, UI_VAR_INTEGER);
+    }
+    
+    if(var->value) {
+        UiInteger *i = var->value;
+        i->get = ui_tool_toggle_button_get;
+        i->set = ui_tool_toggle_button_set;
+        i->obj = button;
+        
+        if(i->value != 0) {
+            gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE);
+        }
+    }
+    
+    // register event
+    // the event func will call the UiInteger observer callbacks
+    UiEventData *event = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = var;
+    event->callback = NULL;
+
+    g_signal_connect(
+            button,
+            "toggled",
+            G_CALLBACK(ui_tool_button_toggled),
+            event);
+    
+    // add item to toolbar
+    gtk_toolbar_insert(tb, button, -1);
+    
+    if(item->groups) {
+        uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+    }
+}
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_toggle_tool_button_get_active(widget);
+    
+    UiVar *var = event->userdata;
+    UiInteger *i = var->value;
+    
+    ui_notify_evt(i->observers, &e);
+}
+
+int64_t ui_tool_toggle_button_get(UiInteger *integer) {
+    integer->value = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj));
+    return integer->value;
+}
+
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value) {
+    gboolean s = value != 0 ? TRUE : FALSE;
+    gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj), s);
+    integer->value = s;
+}
+
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) {
+    UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+    modelinfo->getvalue = cb->getvalue;
+    UiListModel *model = ui_list_model_new(obj, cb->var, modelinfo);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+    GtkToolItem *item = gtk_tool_item_new();
+    gtk_container_add(GTK_CONTAINER(item), combobox);
+    gtk_toolbar_insert(tb, item, -1);
+}
+
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj) {
+    UiVar *var = uic_create_var(obj->ctx, cb->listname, UI_VAR_LIST);
+    if(var) {
+        UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+        modelinfo->getvalue = cb->getvalue;
+        UiListModel *model = ui_list_model_new(obj, var, modelinfo);
+        
+        GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+        GtkToolItem *item = gtk_tool_item_new();
+        gtk_container_add(GTK_CONTAINER(item), combobox);
+        gtk_toolbar_insert(tb, item, -1);
+    }
+}
+
diff --git a/ui/gtk/toolbar.h b/ui/gtk/toolbar.h
new file mode 100644 (file)
index 0000000..68e95ac
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLBAR_H
+#define        TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include <ucx/map.h>
+#include <ucx/list.h>
+
+#include "model.h"
+#include "tree.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolItemI      UiToolItemI;
+typedef struct UiToolItem       UiToolItem;
+typedef struct UiStToolItem     UiStToolItem;
+typedef struct UiToggleToolItem UiToggleToolItem;
+
+typedef struct UiToolbarComboBox   UiToolbarComboBox;
+typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
+
+typedef void(*ui_toolbar_add_f)(GtkToolbar*, UiToolItemI*, UiObject*);
+
+struct UiToolItemI {
+    ui_toolbar_add_f  add_to;
+};
+
+struct UiToolItem {
+    UiToolItemI    item;
+    const char     *label;
+    const char     *image;
+    ui_callback    callback;
+    void           *userdata;
+    const char     *varname;
+    UcxList        *groups;
+    int            isimportant;
+};
+
+struct UiStToolItem {
+    UiToolItemI    item;
+    const char     *stockid;
+    ui_callback    callback;
+    void           *userdata;
+    const char     *varname;
+    UcxList        *groups;
+    int            isimportant;
+};
+
+struct UiToggleToolItem {
+    UiToolItemI    item;
+    const char     *label;
+    const char     *image;
+    const char     *stockid;
+    UiInteger      *value;
+    const char     *var;
+    UcxList        *groups;
+    int            isimportant;
+};
+
+struct UiToolbarComboBox {
+    UiToolItemI         item;
+    UiVar               *var;
+    ui_getvaluefunc getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+struct UiToolbarComboBoxNV {
+    UiToolItemI         item;
+    char                *listname;
+    ui_getvaluefunc getvalue;
+    ui_callback         callback;
+    void                *userdata;
+};
+
+void ui_toolbar_init();
+
+void ui_toolitem_vstgr(
+        char *name,
+        char *stockid,
+        int isimportant,
+        ui_callback f,
+        void *userdata,
+        va_list ap);
+
+GtkWidget* ui_create_toolbar(UiObject *obj);
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj);
+
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj);
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_update(UiEvent *event, void *combobox);
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event);
+int64_t ui_tool_toggle_button_get(UiInteger *integer);
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLBAR_H */
+
diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c
new file mode 100644 (file)
index 0000000..a6e0484
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "toolkit.h"
+#include "toolbar.h"
+#include "model.h"
+#include "image.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+
+#include <ucx/utils.h>
+
+#include <pthread.h>
+
+#ifndef UI_GTK2
+static GtkApplication *app;
+#endif
+
+static char *application_name;
+
+static ui_callback   startup_func;
+static void          *startup_data;
+static ui_callback   open_func;
+void                 *open_data;
+static ui_callback   exit_func;
+void                 *exit_data;
+
+static ui_callback   appclose_fnc;
+static void          *appclose_udata;
+
+static UiObject      *active_window;
+
+static int scale_factor = 1;
+
+void ui_init(char *appname, int argc, char **argv) {
+    uic_init_global_context();
+    
+    gtk_init(&argc, &argv);
+    application_name = appname;
+    
+    uic_docmgr_init();
+    ui_toolbar_init();
+    
+    // init custom types
+    ui_list_init();
+    
+    ui_image_init();
+    
+    uic_load_app_properties();
+    
+#ifdef UI_SUPPORTS_SCALE
+    scale_factor = gdk_monitor_get_scale_factor(
+            gdk_display_get_primary_monitor(gdk_display_get_default()));
+#endif
+}
+
+char* ui_appname() {
+    return application_name;
+}
+
+void ui_onstartup(ui_callback f, void *userdata) {
+    startup_func = f;
+    startup_data = userdata;
+}
+
+void ui_onopen(ui_callback f, void *userdata) {
+    open_func = f;
+    open_data = userdata;
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+    exit_func = f;
+    exit_data = userdata;
+}
+
+
+#ifndef UI_GTK2
+static void app_startup(GtkApplication* app, gpointer userdata) {
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
+}
+
+static void app_activate(GtkApplication* app, gpointer userdata) {
+    printf("activate\n");
+}
+#endif
+
+void ui_main() {
+#ifndef UI_GTK2
+    sstr_t appid = ucx_sprintf(
+            "ui.%s",
+            application_name ? application_name : "application1");
+    
+    app = gtk_application_new(
+            appid.ptr,
+            G_APPLICATION_FLAGS_NONE);
+    g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
+    g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
+    g_application_run(G_APPLICATION (app), 0, NULL);
+    g_object_unref (app);
+    
+    free(appid.ptr);
+#else
+    if(startup_func) {
+        startup_func(NULL, startup_data);
+    }
+    gtk_main();
+#endif
+    if(exit_func) {
+        exit_func(NULL, exit_data);
+    }
+    uic_store_app_properties();
+}
+
+#ifndef UI_GTK2
+void ui_app_quit() {
+    g_application_quit(G_APPLICATION(app));
+}
+
+GtkApplication* ui_get_application() {
+    return app;
+}
+#endif
+
+void ui_show(UiObject *obj) {
+    uic_check_group_widgets(obj->ctx);
+    gtk_widget_show_all(obj->widget);
+}
+
+void ui_close(UiObject *obj) {
+    gtk_widget_destroy(obj->widget);
+}
+
+
+static gboolean ui_job_finished(void *data) {
+    UiJob *job = data;
+    
+    UiEvent event;
+    event.obj = job->obj;
+    event.window = job->obj->window;
+    event.document = job->obj->ctx->document;
+    event.intval = 0;
+    event.eventdata = NULL;
+
+    job->finish_callback(&event, job->finish_data);
+    free(job);
+    return FALSE;
+}
+
+static void* ui_jobthread(void *data) {
+    UiJob *job = data;
+    int result = job->job_func(job->job_data);
+    if(!result) {
+        g_idle_add(ui_job_finished, job);
+    }
+    return NULL;
+}
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+    UiJob *job = malloc(sizeof(UiJob));
+    job->obj = obj;
+    job->job_func = tf;
+    job->job_data = td;
+    job->finish_callback = f;
+    job->finish_data = fd;
+    pthread_t pid;
+    pthread_create(&pid, NULL, ui_jobthread, job);
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+    gtk_widget_set_sensitive(widget, enabled);
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+    gtk_widget_set_no_show_all(widget, !value);
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+    if(visible) {
+        gtk_widget_set_no_show_all(widget, FALSE);
+        gtk_widget_show_all(widget);
+    } else {
+        gtk_widget_hide(widget);
+    }
+}
+
+void ui_clipboard_set(char *str) {
+    GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    gtk_clipboard_set_text(cb, str, strlen(str));
+}
+
+char* ui_clipboard_get() {
+    GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    char *str = gtk_clipboard_wait_for_text(cb);
+    if(str) {
+        char *copy = strdup(str);
+        g_free(str);
+        return copy;
+    } else {
+        return NULL;
+    }
+}
+
+int ui_get_scalefactor() {
+    return scale_factor;
+}
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata) {
+    free(userdata);
+}
+
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) {
+    ui_destroy_boundvar(data->obj->ctx, data->var);
+    free(data);
+}
+
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+    if(var->type == UI_VAR_SPECIAL) {
+        free(var);
+    } else {
+        uic_remove_bound_var(ctx, var);
+    }
+}
+
+void ui_set_active_window(UiObject *obj) {
+    active_window = obj;
+}
+
+UiObject *ui_get_active_window() {
+    return active_window;
+}
+
+
diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h
new file mode 100644 (file)
index 0000000..48f4d10
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define        TOOLKIT_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+typedef struct UiEventData {
+    UiObject    *obj;
+    ui_callback callback;
+    void        *userdata;
+    int         value;
+} UiEventData;
+
+typedef struct UiVarEventData {
+    UiObject   *obj;
+    UiVar     *var;
+    UiObserver **observers;
+} UiVarEventData;
+
+
+typedef struct UiJob {
+    UiObject      *obj;
+    ui_threadfunc job_func;
+    void          *job_data;
+    ui_callback   finish_callback;
+    void          *finish_data;
+} UiJob;
+
+struct UiSelection {
+    GtkSelectionData *data;
+};
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+#ifndef UI_GTK2
+void ui_app_quit();
+GtkApplication* ui_get_application();
+#endif
+
+int ui_get_scalefactor();
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata);
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
+
+void ui_set_active_window(UiObject *obj);
+UiObject *ui_get_active_window();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
diff --git a/ui/gtk/tree.c b/ui/gtk/tree.c
new file mode 100644 (file)
index 0000000..d93a6f0
--- /dev/null
@@ -0,0 +1,562 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+
+#include "tree.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+    return column == 0 ? elm : NULL;
+}
+
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+    
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+#if GTK_MINOR_VERSION >= 8
+    gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    // TODO: implement for older gtk3
+#endif
+#else
+    // TODO: implement for gtk2
+#endif
+    
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = getvalue;
+    UiList *list = var->value;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    
+    UiListView *listview = malloc(sizeof(UiListView));
+    listview->obj = obj;
+    listview->widget = view;
+    listview->var = var;
+    listview->model = model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->obj = listview;
+    
+    // add callback
+    if(f) {
+        UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+        event->obj = obj;
+        event->userdata = udata;
+        event->activate = f;
+        event->selection = NULL;
+
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    ct->current = view;
+    
+    return scroll_area;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_listview_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_listview_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+    printf("drag begin\n");
+    
+}
+
+static void drag_end(
+        GtkWidget *widget,
+        GdkDragContext *context,
+        guint time,
+        gpointer udata)
+{
+    printf("drag end\n");
+    
+}
+
+static GtkTargetEntry targetentries[] =
+    {
+      { "STRING",        0, 0 },
+      { "text/plain",    0, 1 },
+      { "text/uri-list", 0, 2 },
+    };
+
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
+    // create treeview
+    GtkWidget *view = gtk_tree_view_new();
+    
+    int addi = 0;
+    for(int i=0;i<model->columns;i++) {
+        GtkTreeViewColumn *column = NULL;
+        if(model->types[i] == UI_ICON_TEXT) {
+            column = gtk_tree_view_column_new();
+            gtk_tree_view_column_set_title(column, model->titles[i]);
+            
+            GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+            GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
+            
+            gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
+            gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
+            
+            
+            gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
+            gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+            
+            addi++;
+        } else {
+            GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+            column = gtk_tree_view_column_new_with_attributes(
+                model->titles[i],
+                renderer,
+                "text",
+                i + addi,
+                NULL);
+        }
+        gtk_tree_view_column_set_resizable(column, TRUE);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+    }
+    
+    //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+    //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+    
+#endif
+    
+    UiList *list = var->value;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    
+    //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+    //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+       
+    // add TreeView as observer to the UiList to update the TreeView if the
+    // data changes
+    UiListView *tableview = malloc(sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->widget = view;
+    tableview->var = var;
+    tableview->model = model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                tableview);
+    
+    // bind var
+    list->update = ui_listview_update;
+    list->obj = tableview;
+    
+    // add callback
+    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = cb.activate;
+    event->selection = cb.selection;
+    event->userdata = cb.userdata;
+    if(cb.activate) {
+        g_signal_connect(
+                view,
+                "row-activated",
+                G_CALLBACK(ui_listview_activate_event),
+                event);
+    }
+    if(cb.selection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    // TODO: destroy callback
+
+    
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+    
+    // add widget to the current container
+    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // ct->current should point to view, not scroll_area, to make it possible
+    // to add a context menu
+    ct->current = view;
+    
+    return scroll_area;
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_table_var(obj, var, model, cb);
+}
+
+UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_table_var(obj, var, model, cb);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+    GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
+    if(c) {
+        return c->data;
+    }
+    return NULL;
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+    int al = 16;
+    char **targets = calloc(16, sizeof(char*));
+    targets[0] = target0;
+    
+    int i = 1;
+    char *target;
+    while((target = va_arg(ap, char*)) != NULL) {
+        if(i >= al) {
+            al *= 2;
+            targets = realloc(targets, al*sizeof(char*));
+        }
+        targets[i] = target;
+        i++;
+    }
+    
+    *nelm = i;
+    return targets;
+}
+
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+    GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+    for(int i=0;i<nelm;i++) {
+        targets[i].target = str[i];
+    }
+    return targets;
+}
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_source(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            GDK_BUTTON1_MASK,
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+    va_list ap;
+    va_start(ap, target0);
+    int nelm;
+    char **targets = targets2array(target0, ap, &nelm);
+    va_end(ap);
+    ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+    free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+    GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+    gtk_tree_view_enable_model_drag_dest(
+            GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+            t,
+            nelm,
+            GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+    free(t);
+}
+
+void ui_listview_update(UiList *list, int i) {
+    UiListView *view = list->obj;
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model));
+    g_object_unref(G_OBJECT(model));
+    // TODO: free old model
+}
+
+void ui_listview_destroy(GtkWidget *w, UiListView *v) {
+    gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    // TODO: destroy model?
+    free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+    gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL);
+    ui_destroy_boundvar(v->obj->ctx, v->var);
+    // TODO: destroy model?
+    free(v);
+}
+
+
+void ui_listview_activate_event(
+        GtkTreeView *treeview,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event)
+{
+    UiListSelection *selection = ui_listview_selection(
+            gtk_tree_view_get_selection(treeview),
+            event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = selection;
+    e.intval = selection->count > 0 ? selection->rows[0] : -1;
+    event->activate(&e, event->userdata);
+    
+    if(selection->count > 0) {
+        free(selection->rows);
+    }
+    free(selection);
+}
+
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event)
+{
+    UiListSelection *selection = ui_listview_selection(treeselection, event);
+    
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = selection;
+    e.intval = selection->count > 0 ? selection->rows[0] : -1;
+    event->selection(&e, event->userdata);
+    
+    if(selection->count > 0) {
+        free(selection->rows);
+    }
+    free(selection);
+}
+
+UiListSelection* ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event)
+{
+    GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+    
+    UiListSelection *ls = malloc(sizeof(UiListSelection));
+    ls->count = g_list_length(rows);
+    ls->rows = calloc(ls->count, sizeof(int));
+    GList *r = rows;
+    int i = 0;
+    while(r) {
+        GtkTreePath *path = r->data;
+        ls->rows[i] = ui_tree_path_list_index(path);
+        r = r->next;
+        i++;
+    }
+    return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+    int depth = gtk_tree_path_get_depth(path);
+    if(depth == 0) {
+        fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+        return -1;
+    }
+    int *indices = gtk_tree_path_get_indices(path);
+    return indices[depth - 1];
+}
+
+
+/* --------------------------- ComboBox ---------------------------  */
+
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+    return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = list;
+    var->type = UI_VAR_SPECIAL;
+    return ui_combobox_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+    if(var) {
+        return ui_combobox_var(obj, var, getvalue, f, udata);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+    model->getvalue = getvalue;
+    UiListModel *listmodel = ui_list_model_new(obj, var, model);
+    
+    GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata);
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, combobox, FALSE);
+    return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) {
+    GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
+    
+    UiListView *uicbox = malloc(sizeof(UiListView));
+    uicbox->obj = obj;
+    uicbox->widget = combobox;
+    uicbox->var = model->var;
+    uicbox->model = model->model;
+    
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_combobox_destroy),
+                uicbox);
+    
+    // bind var
+    UiList *list = model->var->value;
+    list->update = ui_combobox_modelupdate;
+    list->obj = uicbox;
+    
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+    gtk_cell_layout_set_attributes(
+            GTK_CELL_LAYOUT(combobox),
+            renderer,
+            "text",
+            0,
+            NULL);
+    gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+    
+    // add callback
+    if(f) {
+        UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = udata;
+        event->callback = f;
+        event->value = 0;
+
+        g_signal_connect(
+                combobox,
+                "changed",
+                G_CALLBACK(ui_combobox_change_event),
+                event);
+    }
+    
+    return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+    UiEvent event;
+    event.obj = e->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = gtk_combo_box_get_active(widget);
+    e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+    UiListView *view = list->obj;
+    UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+    gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model));
+}
+
diff --git a/ui/gtk/tree.h b/ui/gtk/tree.h
new file mode 100644 (file)
index 0000000..e771ea1
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define        TREE_H
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+#include "model.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    UiObject    *obj;
+    GtkWidget   *widget;
+    UiVar       *var;
+    UiModel     *model;
+} UiListView;
+
+typedef struct UiTreeEventData {
+    UiObject    *obj;
+    ui_callback activate;
+    ui_callback selection;
+    void        *userdata;
+} UiTreeEventData;
+    
+void* ui_strmodel_getvalue(void *elm, int column);
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
+void ui_listview_update(UiList *list, int i);
+void ui_combobox_destroy(GtkWidget *w, UiListView *v);
+void ui_listview_destroy(GtkWidget *w, UiListView *v);
+
+void ui_listview_activate_event(
+        GtkTreeView *tree_view,
+        GtkTreePath *path,
+        GtkTreeViewColumn *column,
+        UiTreeEventData *event);
+void ui_listview_selection_event(
+        GtkTreeSelection *treeselection,
+        UiTreeEventData *event);
+UiListSelection* ui_listview_selection(
+        GtkTreeSelection *selection,
+        UiTreeEventData *event);
+int ui_tree_path_list_index(GtkTreePath *path);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_modelupdate(UiList *list, int i);
+        
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
diff --git a/ui/gtk/window.c b/ui/gtk/window.c
new file mode 100644 (file)
index 0000000..7313b0b
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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 "menu.h"
+#include "toolbar.h"
+#include "container.h"
+
+static int nwindows = 0;
+
+static int window_default_width = 650;
+static int window_default_height = 550;
+
+void ui_exit_event(GtkWidget *widget, gpointer data) {
+    UiObject *obj = data;
+    UiEvent ev;
+    ev.window = obj->window;
+    ev.document = obj->ctx->document;
+    ev.obj = obj;
+    ev.eventdata = NULL;
+    ev.intval = 0;
+    
+    if(obj->ctx->close_callback) {
+        obj->ctx->close_callback(&ev, obj->ctx->close_data);
+    }
+    // TODO: free UiObject
+    
+    nwindows--;
+#ifdef UI_GTK2
+    if(nwindows == 0) {
+        gtk_main_quit();
+    }
+#endif
+}
+
+static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+    UcxMempool *mp = ucx_mempool_new(256);
+    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject)); 
+    
+#ifndef UI_GTK2
+    obj->widget = gtk_application_window_new(ui_get_application());
+#else
+    obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+    
+    
+    obj->ctx = uic_context(obj, mp);
+    obj->window = window_data;
+    
+    if(title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(obj->widget), title);
+    }
+    
+    char *width = ui_get_property("ui.window.width");
+    char *height = ui_get_property("ui.window.height");
+    if(width && height) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                atoi(width),
+                atoi(height));
+    } else {
+        gtk_window_set_default_size(
+                GTK_WINDOW(obj->widget),
+                window_default_width,
+                window_default_height);
+    }
+    
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(0);
+    gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
+    
+    if(!simple) {
+        // menu
+        GtkWidget *mb = ui_create_menubar(obj);
+        if(mb) {
+            gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+        }
+
+        // toolbar
+        GtkWidget *tb = ui_create_toolbar(obj);
+        if(tb) {
+            gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+        }
+    }
+    
+    // window content
+    // the content has a (TODO: not yet) configurable frame
+    GtkWidget *frame = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+    gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+    
+    // content vbox
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    gtk_container_add(GTK_CONTAINER(frame), content_box);
+    obj->container = ui_box_container(obj, content_box);
+    
+    nwindows++;
+    return obj;
+}
+
+
+UiObject* ui_window(char *title, void *window_data) {
+    return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+    return create_window(title, window_data, TRUE);
+}
+
+static char* ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action) {
+    char *button;
+    char *title;
+    
+    if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+        button = GTK_STOCK_OPEN;
+        title = "Datei Ã¶ffnen...";
+    } else {
+        button = GTK_STOCK_SAVE;
+        title = "Datei speichern...";
+    }
+    
+    GtkWidget *dialog = gtk_file_chooser_dialog_new(
+                                title,
+                                GTK_WINDOW(obj->widget),
+                                action,
+                                GTK_STOCK_CANCEL,
+                                GTK_RESPONSE_CANCEL,
+                                button,
+                                GTK_RESPONSE_ACCEPT,
+                                NULL);
+    if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+        char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+        gtk_widget_destroy(dialog);
+        char *copy = strdup(file);
+        g_free(file);
+        return copy;
+    } else {
+        gtk_widget_destroy(dialog);
+        return NULL;
+    }
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN);
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+    return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE);
+}
+
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..49d0701
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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 <ucx/mempool.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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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;
+            group->buttons = ucx_list_append(group->buttons, button);
+            group->ref++;
+        } else {
+            group = malloc(sizeof(RadioButtonGroup));
+            group->buttons = ucx_list_append(NULL, 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 = ucx_list_find(group->buttons, group->current, NULL, NULL);
+    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);
+    
+    UcxList *elm = ucx_list_get(group->buttons, i);
+    if(elm) {
+        Widget button = elm->data;
+        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..b317dd3
--- /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 {
+    UcxList *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..a90a678
--- /dev/null
@@ -0,0 +1,805 @@
+/*
+ * 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"
+
+#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 = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            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 = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            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 = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            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;
+    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) {
+        grid->current = ucx_list_append(grid->current, widget);
+    } else {
+        grid->current = ucx_list_append(grid->current, widget);
+        grid->lines = ucx_list_append(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;
+    
+    UcxList *rowdim = NULL;
+    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;
+    UCX_FOREACH(row, grid->lines) {
+        int rheight = 0;
+        int i=0;
+        int sum_width = 0;
+        UCX_FOREACH(elm, row->data) {
+            Widget w = elm->data;
+            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;
+            }
+        }
+        rowdim = ucx_list_append(rowdim, (void*)(intptr_t)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);
+        ucx_list_free(rowdim);
+        return;
+    }
+    
+    
+    // adjust the positions of all children
+    int y = 0;
+    UCX_FOREACH(row, grid->lines) {
+        int x = 0;       
+        int i=0;
+        UCX_FOREACH(elm, row->data) {
+            Widget w = elm->data;
+            XtVaSetValues(
+                    w,
+                    XmNx, x,
+                    XmNy, y,
+                    XmNwidth, coldim[i],
+                    XmNheight, rowdim->data,
+                    NULL);
+            
+            x += coldim[i];
+            i++;
+        }
+        y += (intptr_t)rowdim->data;
+        rowdim = rowdim->next;
+    }
+    
+    ucx_list_free(rowdim);
+}
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
+    UiContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            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 = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            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;
+    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;
+    tabview->tabs = ucx_list_append(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);
+        UcxList *elm = ucx_list_get(ct->tabs, tab);
+        if(elm) {
+            XtManageChild(elm->data);
+            ct->current = elm->data;
+        } 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;
+    UCX_FOREACH(elm, v->tabs) {
+        UiTab *tab = elm->data;
+        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 = NULL;
+    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->mempool);
+    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
+    v->tabs = ucx_list_append_a(view->ctx->mempool->allocator, 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 = ucx_list_find(pane->tabs, tab, NULL, NULL);
+    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..57c2c3b
--- /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 <ucx/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;
+    UcxList     *lines;
+    UcxList     *current;
+    int         columnspacing;
+    int         rowspacing;
+};
+
+struct UiTabViewContainer {
+    UiContainer container;
+    UiContext   *context;
+    Widget      widget;
+    UcxList     *tabs;
+    Widget      current;
+};
+
+struct MotifTabbedPane {
+    UiTabbedPane view;
+    Widget       tabbar;
+    UcxList      *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..dd1444e
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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"
+#include <ucx/mempool.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..a92f9c8
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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 = ucx_mempool_malloc(obj->ctx->mempool, 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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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);
+    
+}
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..15a248d
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * 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"
+
+UcxList *menus;
+UcxList *current;
+
+void ui_menu(char *label) {
+    // free current menu hierarchy
+    ucx_list_free(current);
+    
+    // create menu
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
+    
+    menu->label  = label;
+    menu->items  = NULL;
+    menu->parent = NULL;    
+    
+    current = ucx_list_prepend(NULL, menu);
+    menus = ucx_list_append(menus, menu);
+    
+}
+
+void ui_submenu(char *label) {
+    UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.add_to = (ui_menu_add_f)add_menu_widget;
+    
+    menu->label  = label;
+    menu->items  = NULL;
+    menu->parent = NULL;
+    
+    // add submenu to current menu
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, 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);
+}
+
+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 = malloc(sizeof(UiMenuItem));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
+    
+    item->label = label;
+    item->userdata = userdata;
+    item->callback = f;
+    item->groups = NULL;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
+    if(!current) {
+        return;
+    }
+    
+    UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
+    
+    item->stockid = stockid;
+    item->userdata = userdata;
+    item->callback = f;
+    item->groups = NULL;
+    
+    // add groups
+    va_list ap;
+    va_start(ap, userdata);
+    int group;
+    while((group = va_arg(ap, int)) != -1) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuseparator() {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemI *item = malloc(sizeof(UiMenuItemI));
+    item->add_to = (ui_menu_add_f)add_menuseparator_widget;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+
+void ui_checkitem(char *label, ui_callback f, void *userdata) {
+    if(!current) {
+        return;
+    }
+    
+    UiCheckItem *item = malloc(sizeof(UiCheckItem));
+    item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
+    item->label = label;
+    item->callback = f;
+    item->userdata = userdata;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_checkitem_nv(char *label, char *vname) {
+    if(!current) {
+        return;
+    }
+    
+    UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
+    item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
+    item->varname = vname;
+    item->label = label;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
+    if(!current) {
+        return;
+    }
+    
+    UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
+    item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
+    item->callback = f;
+    item->userdata = userdata;
+    item->list = items;
+    
+    UiMenu *cm = current->data;
+    cm->items = ucx_list_append(cm->items, item);
+}
+
+
+// private menu functions
+void ui_create_menubar(UiObject *obj) {
+    if(!menus) {
+        return;
+    }
+    
+    Widget menubar = XmCreateMenuBar(obj->widget, "main_list", NULL, 0);
+    XtManageChild(menubar);
+    
+    UcxList *ls = menus;
+    int menu_index = 0;
+    while(ls) {
+        UiMenu *menu = ls->data;
+        menu_index += menu->item.add_to(menubar, menu_index, &menu->item, obj);
+        
+        ls = ls->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);
+    
+    UcxList *ls = menu->items;
+    int menu_index = 0;
+    while(ls) {
+        UiMenuItemI *mi = ls->data;
+        menu_index += mi->add_to(m, menu_index, mi, obj);
+        ls = ls->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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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;
+    UcxMempool *mp = obj->ctx->mempool;
+    
+    UiActiveMenuItemList *ls = ucx_mempool_malloc(
+            mp,
+            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
+    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
+    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
+    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);
+    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..d8919cb
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiMenuItemI      UiMenuItemI;
+typedef struct UiMenu           UiMenu;
+typedef struct UiMenuItem       UiMenuItem;
+typedef struct UiStMenuItem     UiStMenuItem;
+typedef struct UiCheckItem      UiCheckItem;
+typedef struct UiCheckItemNV    UiCheckItemNV;
+typedef struct UiMenuItemList   UiMenuItemList;
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
+    
+struct UiMenuItemI {
+    ui_menu_add_f  add_to;
+};
+
+struct UiMenu {
+    UiMenuItemI    item;
+    char           *label;
+    UcxList        *items;
+    UiMenu         *parent;
+};
+
+struct UiMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *label;
+    void           *userdata;
+    UcxList        *groups;
+};
+
+struct UiStMenuItem {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    char           *stockid;
+    void           *userdata;
+    UcxList        *groups;
+};
+
+struct UiCheckItem {
+    UiMenuItemI    item;
+    char           *label;
+    ui_callback    callback;
+    void           *userdata;
+};
+
+struct UiCheckItemNV {
+    UiMenuItemI    item;
+    char           *label;
+    char           *varname;
+};
+
+struct UiMenuItemList {
+    UiMenuItemI    item;
+    ui_callback    callback;
+    void           *userdata;
+    UiList         *list;
+};
+
+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..07d7ada
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 <ucx/mempool.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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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..1747cf6
--- /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 <ucx/map.h>
+
+static UcxMap *stock_items;
+
+void ui_stock_init() {
+    stock_items = ucx_map_new(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
+    
+    ucx_map_cstr_put(stock_items, id, i);
+}
+
+UiStockItem* ui_get_stock_item(char *id) {
+    UiStockItem *item = ucx_map_cstr_get(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..a4835c3
--- /dev/null
@@ -0,0 +1,488 @@
+/*
+ * 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 = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->last_selection_state = 0;
+    XtAddCallback(
+                text_area,
+                XmNmotionVerifyCallback,
+                (XtCallbackProc)ui_text_selection_callback,
+                uitext);
+    
+    // bind value
+    if(var->value) {
+        UiText *value = var->value;
+        if(value->value.ptr) {
+            XmTextSetString(text_area, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textarea_set;
+        value->get = ui_textarea_get;
+        value->getsubstr = ui_textarea_getsubstr;
+        value->insert = ui_textarea_insert;
+        value->setposition = ui_textarea_setposition;
+        value->position = ui_textarea_position;
+        value->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->value.ptr = NULL;
+        value->obj = text_area;
+        
+        if(!value->undomgr) {
+            value->undomgr = ui_create_undomgr();
+        }
+        
+        XtAddCallback(
+                text_area,
+                XmNmodifyVerifyCallback,
+                (XtCallbackProc)ui_text_modify_callback,
+                var);
+    }
+    
+    return text_area;
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+    if(var) {
+        return ui_textarea_var(obj, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    char *str = XmTextGetString(text->obj);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    XmTextSetString(text->obj, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    int length = end - begin;
+    char *str = XtMalloc(length + 1);
+    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    text->value.ptr = NULL;
+    XmTextInsert(text->obj, pos, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+    long begin;
+    long end;
+    XmTextGetSelectionPosition(text->obj, &begin, &end);
+    text->pos = begin;
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+    return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+void ui_text_set(UiText *text, char *str) {
+    if(text->set) {
+        text->set(text, str);
+    } else {
+        if(text->value.ptr) {
+            text->value.free(text->value.ptr);
+        }
+        text->value.ptr = XtNewString(str);
+        text->value.free = (ui_freefunc)XtFree;
+    }
+}
+
+char* ui_text_get(UiText *text) {
+    if(text->get) {
+        return text->get(text);
+    } else {
+        return text->value.ptr;
+    }
+}
+
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return 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) {
+        UcxList *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            while(elm) {
+                elm->prev = NULL;   
+                UcxList *next = elm->next;
+                ui_free_textbuf_op(elm->data);
+                free(elm);
+                elm = next;
+            }
+        }
+        
+        if(type == UI_TEXTBUF_INSERT) {
+            UiTextBufOp *last_op = mgr->cur->data;
+            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->type = type;
+    op->start = txv->startPos;
+    op->end = txv->endPos + 1;
+    op->len = length;
+    op->text = str;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+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->data;
+        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;
+    
+    UcxList *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->data;
+        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..0f44f95
--- /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.
+ */
+
+#ifndef TEXT_H
+#define        TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+#include <ucx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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;
+    
+typedef struct UiUndoMgr {
+    UcxList *begin;
+    UcxList *cur;
+    int     length;
+    int     event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+    UiContext *ctx;
+    int last_selection_state;
+} UiTextArea;
+    
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+
+UiUndoMgr* ui_create_undomgr();
+void ui_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..3456dd3
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * 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 <ucx/mempool.h>
+#include "../common/context.h"
+
+static UcxMap *toolbar_items;
+static UcxList *defaults;
+
+void ui_toolbar_init() {
+    toolbar_items = ucx_map_new(16);
+}
+
+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;
+    
+    ucx_map_cstr_put(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) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    ucx_map_cstr_put(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;
+    
+    ucx_map_cstr_put(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) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    ucx_map_cstr_put(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) {
+        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+    }
+    va_end(ap);
+    
+    ucx_map_cstr_put(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;
+    
+    ucx_map_cstr_put(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;
+    
+    ucx_map_cstr_put(toolbar_items, name, cb);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+    char *s = strdup(name);
+    defaults = ucx_list_append(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);
+    
+    UCX_FOREACH(elm, defaults) {
+        UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data);
+        if(item) {
+            item->add_to(toolbar, item, obj);
+        } else if(!strcmp(elm->data, "@separator")) {
+            // TODO
+        } else {
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
+        }
+    }
+    
+    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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                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 = ucx_mempool_malloc(
+                obj->ctx->mempool,
+                sizeof(UiListView));
+    
+    UiVar *var = ucx_mempool_malloc(obj->ctx->mempool, 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..22d0a5d
--- /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 <ucx/map.h>
+#include <ucx/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;
+    UcxList        *groups;
+    Boolean        isimportant;
+};
+
+struct UiStToolItem {
+    UiToolItemI    item;
+    char           *stockid;
+    ui_callback    callback;
+    void           *userdata;
+    UcxList        *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..9757f64
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * 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 <ucx/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);
+        
+    }
+}
+
+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..0620e0f
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * 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 <ucx/utils.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 = ucx_mempool_malloc(obj->ctx->mempool, 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;
+            sstr_t str = ucx_asprintf(ucx_default_allocator(), "%d", *val);
+            return str.ptr;
+        }
+        default: break;
+    }
+    *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..041ab7f
--- /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 "toolkit.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+#include "../ui/window.h"
+#include "../common/context.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) {
+    UcxMempool *mp = ucx_mempool_new(256);
+    UiObject *obj = ucx_mempool_calloc(mp, 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/ui/button.h b/ui/ui/button.h
new file mode 100644 (file)
index 0000000..cbf8fe8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_BUTTON_H
+#define        UI_BUTTON_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data);
+
+UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value);
+UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname);
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup);
+UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname);
+
+
+#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..423342a
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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
+    
+#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_VBOX(obj) for(ui_vbox(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_HBOX(obj) for(ui_hbox(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_VBOX_SP(obj, margin, spacing) for(ui_vbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_HBOX_SP(obj, margin, spacing) for(ui_hbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_GRID(obj) for(ui_grid(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define UI_GRID_SP(obj, margin, columnspacing, rowspacing) for(ui_grid_sp(obj,margin,columnspacing,rowspacing);ui_container_finish(obj);ui_container_begin_close(obj))
+    
+void ui_end(UiObject *obj);
+    
+UIWIDGET ui_vbox(UiObject *obj);
+UIWIDGET ui_hbox(UiObject *obj);
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing);
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing);
+
+UIWIDGET ui_grid(UiObject *obj);
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing);
+
+UIWIDGET ui_scrolledwindow(UiObject *obj);
+
+UIWIDGET ui_sidebar(UiObject *obj);
+
+UIWIDGET ui_hsplitpane(UiObject *obj, int max);
+UIWIDGET ui_vsplitpane(UiObject *obj, int max);
+
+UIWIDGET ui_tabview(UiObject *obj);
+void ui_tab(UiObject *obj, char *title);
+void ui_select_tab(UIWIDGET tabview, int tab);
+
+// box container layout functions
+void ui_layout_fill(UiObject *obj, UiBool fill);
+// grid container layout functions
+void ui_layout_hexpand(UiObject *obj, UiBool expand);
+void ui_layout_vexpand(UiObject *obj, UiBool expand);
+void ui_layout_width(UiObject *obj, int width);
+void ui_layout_gridwidth(UiObject *obj, int width);
+void ui_newline(UiObject *obj);
+
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
+
+UiObject* ui_document_tab(UiTabbedPane *view);
+
+
+/* used for macro */
+void ui_container_begin_close(UiObject *obj);
+int ui_container_finish(UiObject *obj);
+
+
+#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..0673488
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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
+    
+/* label widgets */
+UIWIDGET ui_label(UiObject *obj, char *label);
+UIWIDGET ui_llabel(UiObject *obj, char *label);
+UIWIDGET ui_rlabel(UiObject *obj, char *label);
+UIWIDGET ui_space(UiObject *obj);
+UIWIDGET ui_separator(UiObject *obj);
+
+/* progress bar */
+UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value);
+UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname);
+
+
+#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..71efd36
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_DND_H
+#define UI_DND_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#define UI_DND_FILE_TARGET "XdndDirectSave0"
+    
+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);
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm);
+
+
+#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..5d63351
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ENTRY_H
+#define UI_ENTRY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i);
+UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d);
+UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r);
+
+UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname);
+UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname);
+UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname);
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
+void ui_spinner_setdigits(UIWIDGET spinner, int digits);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ENTRY_H */
+
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/image.h b/ui/ui/image.h
new file mode 100644 (file)
index 0000000..efd6f70
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_IMAGE_H
+#define UI_IMAGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UiIcon* ui_icon(const char *name, int size);
+UiIcon* ui_icon_unscaled(const char *name, int size);
+void ui_free_icon(UiIcon *icon);
+
+UiImage* ui_icon_image(UiIcon *icon);
+UiImage* ui_image(const char *filename);
+UiImage* ui_named_image(const char *filename, const char *name);
+UiImage* ui_load_image_from_path(const char *path, const char *name);
+void ui_free_image(UiImage *img);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_IMAGE_H */
+
diff --git a/ui/ui/menu.h b/ui/ui/menu.h
new file mode 100644 (file)
index 0000000..a472839
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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
+
+/*
+ * application menu functions
+ */
+void ui_menu(char *label);
+void ui_submenu(char *label);
+void ui_submenu_end();
+
+void ui_menuitem(char *label, ui_callback f, void *userdata);
+void ui_menuitem_st(char *stockid, ui_callback f, void *userdata);
+void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...);
+void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...);
+
+void ui_menuseparator();
+
+void ui_checkitem(char *label, ui_callback f, void *userdata);
+void ui_checkitem_nv(char *label, char *vname);
+
+void ui_menuitem_list(UiList *items, ui_callback f, void *userdata);
+
+/*
+ * widget menu functions
+ */
+UIMENU ui_contextmenu(UiObject *obj);
+UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget);
+void ui_contextmenu_popup(UIMENU menu);
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata);
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata);
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...);
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...);
+
+#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..2675812
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_PROPERTIES_H
+#define        UI_PROPERTIES_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UcxMap UiProperties;
+    
+char* ui_getappdir();
+char* ui_configfile(char *name);
+
+char* ui_get_property(char *name);
+void  ui_set_property(char *name, char *value);
+
+void ui_set_default_property(char *name, char *value);
+
+void ui_locales_dir(char *path);
+void ui_pixmaps_dir(char *path);
+
+void ui_load_lang(char *locale);
+void ui_load_lang_def(char *locale, char *default_locale);
+    
+char* uistr(char *name);
+char* uistr_n(char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_PROPERTIES_H */
+
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..7df2ce3
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value);
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname);
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+
+void ui_text_undo(UiText *value);
+void ui_text_redo(UiText *value);
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value);
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname);
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value);
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname);
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value);
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname);
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value);
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname);
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value);
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname);
+
+        
+#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..f5b364d
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLBAR_H
+#define        UI_TOOLBAR_H
+
+#include "toolkit.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata);
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata);
+void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *udata);
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...);
+void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...);
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata);
+
+void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i);
+void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i);
+void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar);
+void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar);
+
+void ui_toolbar_combobox(char *name, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+void ui_toolbar_combobox_str(char *name, UiList *list, ui_callback f, void *udata);
+void ui_toolbar_combobox_nv(char *name, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+void ui_toolbar_add_default(char *name);
+
+#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..d967d45
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLKIT_H
+#define        UI_TOOLKIT_H
+
+#include <inttypes.h>
+
+#ifdef UI_COCOA
+
+#ifdef __OBJC__
+#import <Cocoa/Cocoa.h>
+#define UIWIDGET NSView*
+#define UIMENU   NSMenu*
+#else
+typedef void* UIWIDGET;
+typedef void* UIMENU;
+#endif
+
+#elif UI_GTK2 || UI_GTK3
+
+#include <gtk/gtk.h>
+#define UIWIDGET GtkWidget*
+#define UIMENU   GtkMenu*
+#define UI_GTK
+
+#elif UI_MOTIF
+
+#include <Xm/XmAll.h> 
+#define UIWIDGET Widget
+#define UIMENU   Widget
+
+#elif defined(UI_QT4) || defined(UI_QT5)
+#ifdef __cplusplus
+#include <QApplication>
+#include <QWidget>
+#include <QMenu>
+#define UIWIDGET QWidget*
+#define UIMENU   QMenu*
+#else /* __cplusplus */
+#define UIWIDGET void*
+#define UIMENU   void*
+#endif
+
+#elif UI_WPF
+#define UIWIDGET void*
+#define UIMENU   void*
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_GROUP_SELECTION  20000
+    
+/* public types */
+typedef int UiBool;
+
+typedef struct UiObject     UiObject;
+typedef struct UiEvent      UiEvent;
+typedef struct UiMouseEvent UiMouseEvent;
+typedef struct UiObserver   UiObserver;
+
+typedef struct UiInteger    UiInteger;
+typedef struct UiDouble     UiDouble;
+typedef struct UiString     UiString;
+typedef struct UiText       UiText;
+typedef struct UiList       UiList;
+typedef struct UiRange      UiRange;
+
+typedef struct UiStr        UiStr;
+
+/* begin opaque types */
+typedef struct UiContext    UiContext;
+typedef struct UiContainer  UiContainer;
+
+typedef struct UiIcon       UiIcon;
+typedef struct UiImage      UiImage;
+
+typedef struct UiSelection  UiSelection;
+/* end opaque types */
+
+typedef struct UiTabbedPane UiTabbedPane;
+
+enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
+
+
+  
+typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
+
+typedef void*(*ui_getvaluefunc)(void*, int);
+
+typedef int(*ui_threadfunc)(void*);
+
+typedef void(*ui_freefunc)(void*);
+
+typedef void(*ui_enablefunc)(void*, UiBool);
+
+struct UiObject {
+    /*
+     * native widget
+     */
+    UIWIDGET    widget;
+    
+    /*
+     * user window data
+     */
+    void        *window;
+    
+    /*
+     * window context
+     */
+    UiContext   *ctx;
+    
+    /*
+     * container interface
+     */
+    UiContainer *container;
+    
+    /*
+     * next container object
+     */
+    UiObject    *next;
+};
+
+struct UiTabbedPane {
+    /*
+     * native widget
+     */
+    UIWIDGET  widget;
+    
+    /*
+     * current document
+     */
+    void      *document;
+    
+    /*
+     * parent context
+     */
+    UiContext *ctx;
+};
+
+struct UiEvent {
+    UiObject *obj;
+    void     *document;
+    void     *window;
+    void     *eventdata;
+    int      intval;
+};
+
+struct UiMouseEvent {
+    int x;
+    int y;
+    enum UiMouseEventType type;
+    int button;
+};
+
+struct UiObserver {
+    ui_callback callback;
+    void *data;
+    UiObserver *next;
+};
+
+struct UiStr {
+    char *ptr;
+    void (*free)(void *v);
+};
+
+struct UiInteger {
+    int64_t  (*get)(UiInteger*);
+    void (*set)(UiInteger*, int64_t);
+    void *obj;
+    
+    int64_t value;
+    UiObserver *observers;
+};
+
+struct UiDouble {
+    double  (*get)(UiDouble*);
+    void (*set)(UiDouble*, double);
+    void *obj;
+    
+    double value;
+    UiObserver *observers;
+};
+
+struct UiString {
+    char* (*get)(UiString*);
+    void  (*set)(UiString*, char*);
+    void  *obj;
+    
+    UiStr value;
+    UiObserver *observers;
+};
+
+struct UiText {
+    void  (*set)(UiText*, char*);
+    char* (*get)(UiText*);
+    char* (*getsubstr)(UiText*, int, int); /* text, begin, end */
+    void  (*insert)(UiText*, int, char*);
+    void  (*setposition)(UiText*,int);
+    int   (*position)(UiText*);
+    void  (*selection)(UiText*, int*, int*); /* text, begin, end */
+    int   (*length)(UiText*);
+    void  (*remove)(UiText*, int, int); /* text, begin, end */
+    UiStr value;
+    int   pos;
+    void  *obj;
+    void  *undomgr;
+    // TODO: replacefunc, ...
+    UiObserver *observers;
+};
+    
+/*
+ * abstract list
+ */
+struct UiList {
+    /* get the first element */
+    void*(*first)(UiList *list);
+    /* get the next element */
+    void*(*next)(UiList *list);
+    /* get the nth element */
+    void*(*get)(UiList *list, int i);
+    /* get the number of elements */
+    int(*count)(UiList *list);
+    /* iterator changes after first() next() and get() */
+    void *iter;
+    /* private - implementation dependent */
+    void *data;
+    
+    /* binding function */
+    void (*update)(UiList *list, int i);
+    /* binding object */
+    void *obj;
+    
+    /* list of observers */
+    UiObserver *observers;
+};
+
+struct UiRange {
+    double (*get)(UiRange *range);
+    void   (*set)(UiRange *range, double value);
+    void   (*setrange)(UiRange *range, double min, double max);
+    void   (*setextent)(UiRange *range, double extent);
+    double value;
+    double min;
+    double max;
+    double extent;
+    void   *obj;
+    /* list of observers */
+    UiObserver *observers;
+};
+
+
+void ui_init(char *appname, int argc, char **argv);
+char* ui_appname();
+
+UiContext* ui_global_context(void);
+
+void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
+
+void ui_onstartup(ui_callback f, void *userdata);
+void ui_onopen(ui_callback f, void *userdata);
+void ui_onexit(ui_callback f, void *userdata);
+
+void ui_main();
+void ui_show(UiObject *obj);
+void ui_close(UiObject *obj);
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd);
+
+void* ui_document_new(size_t size);
+void  ui_document_destroy(void *doc);
+
+void ui_set_document(UiObject *obj, void *document);    // deprecated
+void ui_detach_document(UiObject *obj);                 // deprecated
+void* ui_get_document(UiObject *obj);                   // deprecated
+void ui_set_subdocument(void *document, void *sub);     // deprecated
+void ui_detach_subdocument(void *document, void *sub);  // deprecated
+void* ui_get_subdocument(void *document);               // deprecated
+
+UiContext* ui_document_context(void *doc);
+
+void ui_attach_document(UiContext *ctx, void *document);
+void ui_detach_document2(UiContext *ctx, void *document);
+
+void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
+
+void ui_set_group(UiContext *ctx, int group);
+void ui_unset_group(UiContext *ctx, int group);
+int* ui_active_groups(UiContext *ctx, int *ngroups);
+    
+void* ui_malloc(UiContext *ctx, size_t size);
+void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
+void  ui_free(UiContext *ctx, void *ptr);
+void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+
+// types
+
+UiInteger* ui_int_new(UiContext *ctx, char *name);
+UiDouble* ui_double_new(UiContext *ctx, char *name);
+UiString* ui_string_new(UiContext *ctx, char *name);
+UiText* ui_text_new(UiContext *ctx, char *name);
+UiRange* ui_range_new(UiContext *ctx, char *name);
+
+UiObserver* ui_observer_new(ui_callback f, void *data);
+UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer);
+UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data);
+void ui_notify(UiObserver *observer, void *data);
+void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data);
+void ui_notify_evt(UiObserver *observer, UiEvent *event);
+
+
+UiList* ui_list_new(UiContext *ctx, char *name);
+void* ui_list_first(UiList *list);
+void* ui_list_next(UiList *list);
+void* ui_list_get(UiList *list, int i);
+int   ui_list_count(UiList *list);
+void  ui_list_append(UiList *list, void *data);
+void  ui_list_prepend(UiList *list, void *data);
+void ui_list_clear(UiList *list);
+void  ui_list_addobsv(UiList *list, ui_callback f, void *data);
+void  ui_list_notify(UiList *list);
+
+void ui_clipboard_set(char *str);
+char* ui_clipboard_get();
+
+void ui_add_image(char *imgname, char *filename); // TODO: remove?
+
+// general widget functions
+void ui_set_enabled(UIWIDGET widget, int enabled);
+void ui_set_show_all(UIWIDGET widget, int value);
+void ui_set_visible(UIWIDGET widget, int visible);
+
+
+#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..1bb603d
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TREE_H
+#define        UI_TREE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiModel         UiModel;
+typedef struct UiListCallbacks UiListCallbacks;
+typedef struct UiListSelection UiListSelection;
+
+typedef enum UiModelType {
+    UI_STRING = 0,
+    UI_INTEGER,
+    UI_ICON,
+    UI_ICON_TEXT,
+} UiModelType;
+
+struct UiModel {
+    /*
+     * number of columns
+     */
+    int columns;
+    
+    /*
+     * array of column types
+     * array length is the number of columns
+     */
+    UiModelType *types;
+    
+    /*
+     * array of column titles
+     * array length is the number of columns
+     */
+    char **titles;
+    
+    /*
+     * function for translating model data to view data
+     * first argument is the pointer returned by UiList->get or UiTree->get
+     * second argument is the column index
+     * TODO: return
+     */
+    void*(*getvalue)(void*, int);
+    
+    UiBool(*candrop)(UiEvent*, UiSelection*, UiList*, int);
+    void(*drop)(UiEvent*, UiSelection*, UiList*, int);
+    UiBool(*candrag)(UiEvent*, UiList*, int);
+    void(*data_get)(UiEvent*, UiSelection*, UiList*, int);
+    void(*data_delete)(UiEvent*, UiList*, int);
+};
+
+struct UiListCallbacks {
+    /*
+     * selection callback
+     */
+    ui_callback activate;
+    
+    /*
+     * cursor callback
+     */
+    ui_callback selection;
+    
+    /*
+     * userdata for all callbacks
+     */
+    void *userdata;
+};
+
+struct UiListSelection {
+    /*
+     * number of selected items
+     */
+    int count;
+    
+    /*
+     * indices of selected rows
+     */
+    int *rows;
+};
+
+UiModel* ui_model(UiContext *ctx, ...);
+void ui_model_free(UiContext *ctx, UiModel *mi);
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata);
+UIWIDGET ui_listview_nv(UiObject *obj, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb);
+UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb);
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata);
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+#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..a7f7030
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_H
+#define        UI_H
+
+#include "toolkit.h"
+#include "container.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "window.h"
+#include "stock.h"
+#include "button.h"
+#include "text.h"
+#include "properties.h"
+#include "tree.h"
+#include "graphics.h"
+#include "entry.h"
+#include "range.h"
+#include "image.h"
+#include "display.h"
+#include "dnd.h"
+
+#endif /* UI_H */
+
diff --git a/ui/ui/window.h b/ui/ui/window.h
new file mode 100644 (file)
index 0000000..93bebe9
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WINDOW_H
+#define        UI_WINDOW_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UiObject* ui_window(char *title, void *window_data);
+UiObject* ui_simplewindow(char *title, void *window_data);
+
+char* ui_openfiledialog(UiObject *obj);
+char* ui_savefiledialog(UiObject *obj);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+