--- /dev/null
+build
+config.mk
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+all: config.mk
+ $(MAKE) -f make/Makefile.mk
+
+config.mk:
+ ./configure
+
+clean: FORCE
+ rm -fR build/*
+
+FORCE:
--- /dev/null
+#!/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
+
+
--- /dev/null
+#
+# 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 $@ $<
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
+
+
+
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+#
+# 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:
+
--- /dev/null
+#
+# clang toolchain config
+#
+
+CFLAGS =
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
--- /dev/null
+#!/bin/sh
+
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.name}=`${var.value}`
+#else
+${var.name}=${var.value}
+#end
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=/usr
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$PREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=
+#end
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+ echo "Cannot create tmp dir"
+ echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.isDefault()} )
+${feature.getVarName()}=on
+#end
+#end
+
+# help text
+printhelp()
+{
+ echo "Usage: $0 [OPTIONS]..."
+ cat << __EOF__
+Installation directories:
+ --prefix=PREFIX path prefix for architecture-independent files
+ [/usr]
+ --exec-prefix=EPREFIX path prefix for architecture-dependent files
+ [PREFIX]
+
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR system configuration files [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+
+#if( $options.size() > 0 )
+Options:
+#foreach( $opt in $options )
+ --${opt.getArgument()}=${opt.getValuesString()}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+#if( $feature.default )
+ --disable-${feature.arg}
+#else
+ --enable-${feature.arg}
+#end
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments
+#
+#set( $D = '$' )
+for ARG in $@
+do
+ case "$ARG" in
+ "--prefix="*) PREFIX=${D}{ARG#--prefix=} ;;
+ "--exec-prefix="*) EPREFIX=${D}{ARG#--exec-prefix=} ;;
+ "--bindir="*) BINDIR=${D}{ARG#----bindir=} ;;
+ "--sbindir="*) SBINDIR=${D}{ARG#--sbindir=} ;;
+ "--libdir="*) LIBDIR=${D}{ARG#--libdir=} ;;
+ "--libexecdir="*) LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
+ "--datadir="*) DATADIR=${D}{ARG#--datadir=} ;;
+ "--sysconfdir="*) SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
+ "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
+ "--localstatedir="*) LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
+ "--includedir="*) INCLUDEDIR=${D}{ARG#--includedir=} ;;
+ "--infodir="*) INFODIR=${D}{ARG#--infodir=} ;;
+ "--mandir"*) MANDIR=${D}{ARG#--mandir} ;;
+ "--help"*) printhelp; exit 1 ;;
+ #foreach( $opt in $options )
+ "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
+ #end
+ #foreach( $feature in $features )
+ "--enable-${feature.arg}") ${feature.getVarName()}=on ;;
+ "--disable-${feature.arg}") unset ${feature.getVarName()} ;;
+ #end
+ "-"*) echo "unknown option: $ARG"; exit 1 ;;
+ esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+ BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+ SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+ LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+ LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+ DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+ SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+ SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+ LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+ INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+ INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+ MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+ PKG_CONFIG=pkg-config
+else
+ PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+ PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+ PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+ PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+ PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+ PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+ PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+ PLATFORM_NAME=$p
+ break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ $p = $1 ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+isnotplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ $p = $1 ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+#foreach( $var in $vars )
+${var.name}=$${var.name}
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=$PREFIX
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$EPREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=$BINDIR
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=$SBINDIR
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=$LIBDIR
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=$LIBEXECDIR
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=$DATADIR
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=$SYSCONFDIR
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=$LOCALSTATEDIR
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=$INCLUDEDIR
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=$INFODIR
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=$MANDIR
+#end
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+ echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+ echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+ echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+#foreach( $dependency in $namedDependencies )
+dependency_${dependency.name}()
+{
+ printf "checking for ${dependency.name}... "
+ #foreach( $sub in $dependency.getSubdependencies() )
+ # dependency $sub.name $sub.getPlatformString()
+ while true
+ do
+ #if( $sub.platform )
+ if isnotplatform "${sub.platform}"; then
+ break
+ fi
+ #end
+ #foreach( $not in $sub.getNotList() )
+ if isplatform "${not}"; then
+ break
+ fi
+ #end
+ #if( $sub.pkgconfig.size() > 0 )
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ #end
+ #foreach( $pkg in $sub.pkgconfig )
+ $PKG_CONFIG $pkg.getPkgConfigParam()
+ if [ $? -ne 0 ] ; then
+ break
+ fi
+ CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+ LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+ #end
+ #foreach( $flags in $sub.flags )
+ #if( $flags.exec )
+ $flags.value > /dev/null
+ if [ $? -eq 0 ]; then
+ $flags.varName="$$flags.varName `$flags.value`"
+ else
+ break
+ fi
+ #else
+ $flags.varName="$$flags.varName $flags.value"
+ #end
+ #end
+ #foreach( $test in $sub.tests )
+ $test > /dev/null
+ if [ $? -ne 0 ]; then
+ break
+ fi
+ #end
+ #if ( $sub.make.length() > 0 )
+ cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name
+$sub.make
+__EOF__
+ #end
+ echo yes
+ return 0
+ done
+
+ #end
+ echo no
+ return 1
+}
+#end
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# general dependencies
+CFLAGS=
+LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+ #if( $dependency.platform )
+ if isnotplatform "${dependency.platform}"; then
+ break
+ fi
+ #end
+ #foreach( $not in $dependency.getNotList() )
+ if isplatform "${not}"; then
+ break
+ fi
+ #end
+ while true
+ do
+ #if( $dependency.pkgconfig.size() > 0 )
+ if [ -z "$PKG_CONFIG" ]; then
+ ERROR=1
+ break
+ fi
+ #end
+ #foreach( $pkg in $dependency.pkgconfig )
+ printf "checking for pkg-config package $pkg.getPkgConfigParam()... "
+ $PKG_CONFIG $pkg.getPkgConfigParam()
+ if [ $? -ne 0 ]; then
+ echo no
+ ERROR=1
+ break
+ fi
+ echo yes
+ CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+ LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+ #end
+
+ #foreach( $flags in $dependency.flags )
+ #if( $flags.exec )
+ $flags.value > /dev/null
+ if [ $? -ne 0 ]; then
+ $flags.varName="$$flags.varName `$flags.value`"
+ else
+ ERROR=1
+ break
+ fi
+ #else
+ $flags.varName="$$flags.varName $flags.value"
+ #end
+ #end
+ #if ( $dependency.make.length() > 0 )
+ cat >> $TEMP_DIR/make.mk << __EOF__
+$dependency.make
+__EOF__
+ #end
+
+ break
+ done
+
+ break
+done
+#end
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+ echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+ echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+ echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+ VERR=0
+ #foreach( $dep in $val.dependencies )
+ dependency_$dep
+ if [ $? -ne 0 ]; then
+ VERR=1
+ fi
+ #end
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ #foreach( $def in $val.defines )
+ CFLAGS="$CFLAGS ${def.toFlags()}"
+ #end
+ #if( $val.hasMake() )
+ cat >> $TEMP_DIR/make.mk << __EOF__
+$val.make
+__EOF__
+ #end
+ return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+#foreach( $target in $targets )
+#if ( $target.name )
+# Target: $target.name
+#else
+# Target
+#end
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+dependency_$dependency
+if [ $? -ne 0 ]; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+ ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+ # check dependency
+ dependency_$dependency
+ if [ $? -ne 0 ]; then
+ # "auto" features can fail and are just disabled in this case
+ if [ $${feature.getVarName()} != "auto" ]; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+ ERROR=1
+ fi
+ fi
+#end
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z ${D}${opt.getVarName()} ]; then
+ SAVED_ERROR=$ERROR
+ SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+ ERROR=0
+ while true
+ do
+ #foreach( $optdef in $opt.defaults )
+ #if( $optdef.platform )
+ if isplatform "$optdef.platform"; then
+ #end
+ $optdef.func
+ if [ $? -eq 0 ]; then
+ echo " ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options
+ ERROR=0
+ break
+ fi
+ #if( $optdef.platform )
+ fi
+ #end
+ #end
+ break
+ done
+ if [ $ERROR -ne 0 ]; then
+ SAVED_ERROR=1
+ fi
+ ERROR=$SAVED_ERROR
+ DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+ if false; then
+ false
+ #foreach( $optval in $opt.values )
+ elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then
+ echo " ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options
+ $optval.func
+ if [ $? -ne 0 ]; then
+ ERROR=1
+ fi
+ #end
+ fi
+fi
+#end
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+ echo "${target.getCFlags()} += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+ echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+ echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#end
+if [ $ERROR -ne 0 ]; then
+ echo
+ echo "Error: Unresolved dependencies"
+ echo $DEPENDENCIES_FAILED
+ rm -Rf $TEMP_DIR
+ exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo " PREFIX: $PREFIX"
+echo " TOOLCHAIN: $TOOLCHAIN_NAME"
+#if ( $options.size() > 0 )
+echo "Options:"
+cat $TEMP_DIR/options
+#end
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
--- /dev/null
+#
+# gcc toolchain config
+#
+
+CFLAGS =
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+MSBUILD = MSBuild.exe
+
+CFLAGS = -std=gnu99 -c -O2 -m64
+COFLAGS = -o
+LDFLAGS =
+LOFLAGS = -o
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT = .exe
+
+PACKAGE_SCRIPT = package_windows.sh
\ No newline at end of file
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS += -std=gnu99 -g -I/usr/include/libxml2
+LDFLAGS += -lxml2 -lz -lpthread -licucore -lm
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT =
+
+PACKAGE_SCRIPT = package_osx.sh
--- /dev/null
+#!/bin/sh
+
--- /dev/null
+<?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>
+
--- /dev/null
+#
+# suncc toolchain
+#
+
+CFLAGS =
+LDFLAGS =
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
--- /dev/null
+#!/bin/sh
+#
+# toolchain detection
+#
+
+C_COMPILERS="cc gcc clang suncc"
+CPP_COMPILERS="CC g++ clang++ sunCC"
+unset CC_ARG_CHECKED
+unset TOOLCHAIN_DETECTION_ERROR
+unset TOOLCHAIN_NAME
+
+check_c_compiler()
+{
+ cat > $TEMP_DIR/test.c << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+ printf("clang\n");
+#elif defined(__GNUC__)
+ printf("gcc\n");
+#elif defined(__sun)
+ printf("suncc\n");
+#else
+ printf("unknown\n");
+#endif
+ return 0;
+}
+__EOF__
+ rm -f $TEMP_DIR/checkcc
+ $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
+
+ if [ $? -ne 0 ]; then
+ return 1
+ fi
+ return 0
+}
+
+check_cpp_compiler()
+{
+ cat > $TEMP_DIR/test.cpp << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+ std::cout << "clang" << std::endl;
+#elif defined(__GNUC__)
+ std::cout << "gcc" << std::endl;
+#elif defined(__sun)
+ std::cout << "suncc" << std::endl;
+#else
+ std::cout << "unknown" << std::endl;
+#endif
+ return 0;
+}
+__EOF__
+ rm -f $TEMP_DIR/checkcc
+ $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
+
+ if [ $? -ne 0 ]; then
+ return 1
+ fi
+ return 0
+}
+
+printf "detect C compiler... "
+
+for COMP in $C_COMPILERS
+do
+ check_c_compiler $COMP
+ if [ $? -ne 0 ]; then
+ if [ ! -z "$CC" ]; then
+ if [ $COMP = $CC ]; then
+ echo "$CC is not a working C Compiler"
+ TOOLCHAIN_DETECTION_ERROR="error"
+ break
+ fi
+ fi
+ else
+ TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+ USE_TOOLCHAIN=$TOOLCHAIN_NAME
+ if [ $COMP = "cc" ]; then
+ # we have found a working compiler, but in case
+ # the compiler is gcc or clang, we try to use
+ # these commands and not 'cc'
+ TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+ if [ $TOOLCHAIN_NAME = "gcc" ]; then
+ check_c_compiler "gcc"
+ if [ $? -eq 0 ]; then
+ COMP=gcc
+ USE_TOOLCHAIN="gcc"
+ fi
+ fi
+ if [ $TOOLCHAIN_NAME = "clang" ]; then
+ check_c_compiler "clang"
+ if [ $? -eq 0 ]; then
+ COMP=clang
+ USE_TOOLCHAIN="clang"
+ fi
+ fi
+ fi
+
+ TOOLCHAIN_NAME=$USE_TOOLCHAIN
+ TOOLCHAIN_CC=$COMP
+ echo $COMP
+ break
+ fi
+done
+if [ -z $TOOLCHAIN_CC ]; then
+ echo "not found"
+fi
+
+printf "detect C++ compiler... "
+
+for COMP in $CPP_COMPILERS
+do
+ check_cpp_compiler $COMP
+ if [ $? -ne 0 ]; then
+ if [ ! -z "$CXX" ]; then
+ if [ $COMP = $CXX ]; then
+ echo "$CC is not a working C++ Compiler"
+ TOOLCHAIN_DETECTION_ERROR="error"
+ break
+ fi
+ fi
+ else
+ if [ $COMP = "CC" ]; then
+ # we have found a working compiler, but in case
+ # the compiler is gcc or clang, we try to use
+ # these commands and not 'cc'
+ TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+ USE_TOOLCHAIN=$TOOLCHAIN_NAME
+ if [ $TOOLCHAIN_NAME = "gcc" ]; then
+ check_cpp_compiler "g++"
+ if [ $? -eq 0 ]; then
+ COMP=g++
+ USE_TOOLCHAIN="gcc"
+ fi
+ fi
+ if [ $TOOLCHAIN_NAME = "clang" ]; then
+ check_cpp_compiler "clang++"
+ if [ $? -eq 0 ]; then
+ COMP=clang++
+ USE_TOOLCHAIN="clang"
+ fi
+ fi
+ fi
+
+ TOOLCHAIN_NAME=$USE_TOOLCHAIN
+ TOOLCHAIN_CXX=$COMP
+ echo $COMP
+ break
+ fi
+done
+if [ -z $TOOLCHAIN_CXX ]; then
+ echo "not found"
+fi
+
+TOOLCHAIN_LD=$TOOLCHAIN_CC
+
+if [ -z "$TOOLCHAIN_NAME" ]; then
+ TOOLCHAIN_DETECTION_ERROR="error"
+else
+ cat >> $TEMP_DIR/config.mk << __EOF__
+# toolchain
+__EOF__
+ echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
+ if [ ! -z "$TOOLCHAIN_CXX" ]; then
+ echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
+ fi
+ echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
+ echo >> $TEMP_DIR/config.mk
+
+ cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
+ else
+ echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
+ echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
+ fi
+fi
--- /dev/null
+#
+# 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
+
--- /dev/null
+#
+# 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 $<
+
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+#
+# 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 $<
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+hello = HALLO WELT!
--- /dev/null
+hello = HELLO WORLD!
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>10K549</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>de_DE</string>
+ <key>CFBundleExecutable</key>
+ <string>mk12</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.yourcompany.toolkit</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>toolkit</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string></string>
+ <key>DTPlatformBuild</key>
+ <string>10M2518</string>
+ <key>DTPlatformVersion</key>
+ <string>PG</string>
+ <key>DTSDKBuild</key>
+ <string>10M2518</string>
+ <key>DTSDKName</key>
+ <string>macosx10.6</string>
+ <key>DTXcode</key>
+ <string>0400</string>
+ <key>DTXcodeBuild</key>
+ <string>10M2518</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.7</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>CFBundleDisplayName</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>LSApplicationCategoryType</key>
+ <string></string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string></string>
+ <key>CFBundleTypeName</key>
+ <string>DocumentType</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+<!--
+ <key>NSDocumentClass</key>
+ <string>Document</string>
+-->
+ </dict>
+ </array>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
--- /dev/null
+APPL????
\ No newline at end of file
--- /dev/null
+#
+# 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 $<
+
--- /dev/null
+UCX is a library for common data structures, algorithms and string functions.
+
+More informations at: https://develop.uap-core.de/ucx/
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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>&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);
+}
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/**
+ * @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>
+ * - or -
+ * <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;
+ }
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+OBJ_DIR = ../build/
+
+include common/objs.mk
+
+UI_LIB = ../build/lib/libuitk.$(LIB_EXT)
+
+include $(TOOLKIT)/objs.mk
+OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
+
+all: $(UI_LIB)
+
+include $(TOOLKIT)/Makefile
+
+$(COMMON_OBJPRE)%.o: common/%.c
+ $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_DOCUMENT_H
+#define UIC_DOCUMENT_H
+
+#include "../ui/toolkit.h"
+#include "context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void uic_docmgr_init();
+void uic_document_addvar(void *doc, char *name, int type, size_t vs);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_DOCUMENT_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_OBJECT_H
+#define UIC_OBJECT_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
+UiObject* uic_current_obj(UiObject *toplevel);
+
+UiContainer* uic_get_current_container(UiObject *obj);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_OBJECT_H */
+
--- /dev/null
+#
+# 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)
+
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_TYPES_H
+#define UIC_TYPES_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void uic_int_copy(UiInteger *from, UiInteger *to);
+void uic_double_copy(UiDouble *from, UiDouble *to);
+void uic_string_copy(UiString *from, UiString *to);
+void uic_text_copy(UiText *from, UiText *to);
+void uic_range_copy(UiRange *from, UiRange *to);
+void uic_list_copy(UiList *from, UiList *to);
+
+void uic_int_save(UiInteger *i);
+void uic_double_save(UiDouble *d);
+void uic_string_save(UiString *s);
+void uic_text_save(UiText *t);
+void uic_range_save(UiRange *r);
+
+void uic_int_unbind(UiInteger *i);
+void uic_double_unbind(UiDouble *d);
+void uic_string_unbind(UiString *s);
+void uic_text_unbind(UiText *t);
+void uic_range_unbind(UiRange *r);
+void uic_list_unbind(UiList *l);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TYPES_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(GTK_OBJPRE)%.o: gtk/%.c
+ $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "../ui/toolkit.h"
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// event wrapper
+void ui_button_clicked(GtkWidget *widget, UiEventData *event);
+
+
+void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
+
+void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUTTON_H */
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool);
+
+typedef struct UiDocumentView UiDocumentView;
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+enum UiLayoutBool {
+ UI_LAYOUT_UNDEFINED = 0,
+ UI_LAYOUT_TRUE,
+ UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+ UiLayoutBool fill;
+ UiBool newline;
+ char *label;
+ UiBool hexpand;
+ UiBool vexpand;
+ int width;
+ int gridwidth;
+};
+
+struct UiContainer {
+ GtkWidget *widget;
+ GtkMenu *menu;
+ GtkWidget *current;
+
+ void (*add)(UiContainer*, GtkWidget*, UiBool);
+ UiLayout layout;
+
+ int close;
+};
+
+typedef struct UiBoxContainer {
+ UiContainer container;
+ UiBool has_fill;
+} UiBoxContainer;
+
+typedef struct UiGridContainer {
+ UiContainer container;
+ int x;
+ int y;
+#ifdef UI_GTK2
+ int width;
+ int height;
+#endif
+} UiGridContainer;
+
+typedef struct UiPanedContainer {
+ UiContainer container;
+ GtkWidget *current_pane;
+ int orientation;
+ int max;
+ int cur;
+} UiPanedContainer;
+
+typedef struct UiTabViewContainer {
+ UiContainer container;
+} UiTabViewContainer;
+
+GtkWidget* ui_gtk_vbox_new(int spacing);
+GtkWidget* ui_gtk_hbox_new(int spacing);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow);
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview);
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill);
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+
+UiObject* ui_add_document_tab(UiDocumentView *view);
+void ui_tab_set_document(UiContext *ctx, void *document);
+void ui_tab_detach_document(UiContext *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONTAINER_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#include "../ui/toolkit.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_cairo.h"
+
+#ifdef UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+ UiCairoGraphics g;
+ g.g.width = gtk_widget_get_allocated_width(w);
+ g.g.height = gtk_widget_get_allocated_height(w);
+ g.widget = w;
+ g.cr = cr;
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+
+ return FALSE;
+}
+#else
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+ UiCairoGraphics g;
+ g.g.width = w->allocation.width;
+ g.g.height = w->allocation.height;
+ g.widget = w;
+ g.cr = gdk_cairo_create(w->window);
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+
+ return FALSE;
+}
+#endif
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+#ifdef UI_GTK3
+ g_signal_connect(G_OBJECT(widget),
+ "draw",
+ G_CALLBACK(ui_drawingarea_expose),
+ event);
+#else
+ g_signal_connect(G_OBJECT(widget),
+ "expose_event",
+ G_CALLBACK(ui_canvas_expose),
+ event);
+#endif
+}
+
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ //return gtk_widget_get_pango_context(gr->widget);
+ return pango_cairo_create_context(gr->cr);
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ double dred = (double)red / (double)255;
+ double dgreen = (double)green / (double)255;
+ double dblue = (double)blue / (double)255;
+ cairo_set_source_rgb(gr->cr, dred, dgreen, dblue);
+}
+
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_set_line_width(gr->cr, 1);
+ cairo_move_to(gr->cr, (double)x1 + 0.5, (double)y1 + 0.5);
+ cairo_line_to(gr->cr, (double)x2 + 0.5, (double)y2 + 0.5);
+ cairo_stroke(gr->cr);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_set_line_width(gr->cr, 1);
+ cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h);
+ if(fill) {
+ cairo_fill(gr->cr);
+ } else {
+ cairo_stroke(gr->cr);
+ }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_move_to(gr->cr, x, y);
+ pango_cairo_show_layout(gr->cr, text->layout);
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_CAIRO_H
+#define DRAW_CAIRO_H
+
+#include "graphics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiCairoGraphics {
+ UiGraphics g;
+ GtkWidget *widget;
+ cairo_t *cr;
+} UiCairoGraphics;
+
+// ui_canvas_expose
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAW_CAIRO_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_gdk.h"
+
+
+gboolean ui_drawingarea_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+ UiGdkGraphics g;
+ g.g.width = w->allocation.width;
+ g.g.height = w->allocation.height;
+ g.widget = w;
+ g.gc = gdk_gc_new(w->window);
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+
+ return FALSE;
+}
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+ g_signal_connect(G_OBJECT(widget),
+ "expose_event",
+ G_CALLBACK(ui_drawingarea_expose),
+ event);
+}
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ return gtk_widget_get_pango_context(gr->widget);
+}
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ GdkColor color;
+ color.red = red * 257;
+ color.green = green * 257;
+ color.blue = blue * 257;
+ gdk_gc_set_rgb_fg_color(gr->gc, &color);
+ //gdk_gc_set_rgb_bg_color(g->gc, &color);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_line(gr->widget->window, gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_rectangle(gr->widget->window, gr->gc, fill, x, y, w, h);
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_layout(gr->widget->window, gr->gc, x, y, text->layout);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_GDK_H
+#define DRAW_GDK_H
+
+#include "graphics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGdkGraphics {
+ UiGraphics g;
+ GtkWidget *widget;
+ GdkGC *gc;
+} UiGdkGraphics;
+
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAW_GDK_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/*
+ * File: entry.h
+ * Author: olaf
+ *
+ * Created on 11. November 2017, 13:38
+ */
+
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include "toolkit.h"
+#include "../ui/entry.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type);
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event);
+
+int64_t ui_spinbutton_getint(UiInteger *i);
+void ui_spinbutton_setint(UiInteger *i, int64_t val);
+
+double ui_spinbutton_getdouble(UiDouble *d);
+void ui_spinbutton_setdouble(UiDouble *d, double val);
+
+double ui_spinbutton_getrangeval(UiRange *r);
+void ui_spinbutton_setrangeval(UiRange *r, double val);
+void ui_spinbutton_setrange(UiRange *r, double min, double max);
+void ui_spinbutton_setextent(UiRange *r, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENTRY_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "graphics.h"
+#include "container.h"
+#include "../common/object.h"
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ GtkWidget *widget = gtk_drawing_area_new();
+
+ if(f) {
+ UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = userdata;
+ ui_connect_draw_handler(widget, event);
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, widget, TRUE);
+
+ return widget;
+}
+
+
+static gboolean widget_button_pressed(
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer userdata)
+{
+ UiEventData *eventdata = userdata;
+
+ UiMouseEvent me;
+ me.x = (int)event->button.x;
+ me.y = (int)event->button.y;
+
+ int exec = 0;
+ if(event->button.type == GDK_BUTTON_PRESS) {
+ exec = 1;
+ me.type = UI_PRESS;
+ } else if(event->button.type == GDK_2BUTTON_PRESS) {
+ exec = 1;
+ me.type = UI_PRESS2;
+ }
+
+ if(exec) {
+ UiEvent e;
+ e.obj = eventdata->obj;
+ e.window = eventdata->obj->window;
+ e.document = eventdata->obj->ctx->document;
+ e.eventdata = &me;
+ e.intval = 0;
+ eventdata->callback(&e, eventdata->userdata);
+ }
+ return TRUE;
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+#ifdef UI_GTK3
+ *width = gtk_widget_get_allocated_width(drawingarea);
+ *height = gtk_widget_get_allocated_height(drawingarea);
+#else
+ *width = drawingarea->allocation.width;
+ *height = drawingarea->allocation.height;
+#endif
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+ gtk_widget_queue_draw(drawingarea);
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+ gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK);
+ if(f) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = u;
+
+ g_signal_connect(G_OBJECT(widget),
+ "button-press-event",
+ G_CALLBACK(widget_button_pressed),
+ event);
+ } else {
+ // TODO: warning
+ }
+}
+
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g) {
+ UiTextLayout *layout = malloc(sizeof(UiTextLayout));
+ PangoContext *pc = ui_get_pango_context(g);
+ layout->layout = pango_layout_new(pc);
+ return layout;
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+ pango_layout_set_text(layout->layout, str, -1);
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+ pango_layout_set_text(layout->layout, str, len);
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+ PangoFontDescription *fontDesc;
+ fontDesc = pango_font_description_from_string(font);
+ pango_font_description_set_size(fontDesc, size * PANGO_SCALE);
+ pango_layout_set_font_description(layout->layout, fontDesc);
+ pango_font_description_free(fontDesc);
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+ pango_layout_get_size(layout->layout, width, height);
+ *width = *width / PANGO_SCALE;
+ *height = *height / PANGO_SCALE;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+ pango_layout_set_width(layout->layout, width * PANGO_SCALE);
+ pango_layout_set_ellipsize(layout->layout, PANGO_ELLIPSIZE_END);
+ //pango_layout_set_wrap(layout->layout, PANGO_WRAP_WORD_CHAR);
+}
+
+void ui_text_free(UiTextLayout *text) {
+ g_object_unref(text->layout);
+ free(text);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAWINGAREA_H
+#define DRAWINGAREA_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiDrawEvent {
+ ui_drawfunc callback;
+ UiObject *obj;
+ void *userdata;
+} UiDrawEvent;
+
+struct UiTextLayout {
+ PangoLayout *layout;
+};
+
+// implemented in draw_*.h
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event);
+PangoContext *ui_get_pango_context(UiGraphics *g);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAWINGAREA_H */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+
+struct UiIcon {
+ GtkIconInfo *info;
+};
+
+struct UiImage {
+ GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "model.h"
+#include "image.h"
+#include "toolkit.h"
+
+#define IS_UI_LIST_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type))
+#define UI_LIST_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), list_model_type, UiListModel))
+
+static void list_model_class_init(GObjectClass *cl, gpointer data);
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data);
+static void list_model_init(UiListModel *instance, GObjectClass *cl);
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data);
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data);
+
+static GObjectClass list_model_class;
+static const GTypeInfo list_model_info = {
+ sizeof(GObjectClass),
+ NULL,
+ NULL,
+ (GClassInitFunc)list_model_class_init,
+ NULL,
+ NULL,
+ sizeof(UiListModel),
+ 0,
+ (GInstanceInitFunc)list_model_init
+};
+static const GInterfaceInfo list_model_interface_info = {
+ (GInterfaceInitFunc)list_model_interface_init,
+ NULL,
+ NULL
+};
+static GType list_model_type;
+
+static const GInterfaceInfo list_model_dnd_dest_interface_info = {
+ (GInterfaceInitFunc)list_model_dnd_dest_interface_init,
+ NULL,
+ NULL
+};
+static const GInterfaceInfo list_model_dnd_src_interface_info = {
+ (GInterfaceInitFunc)list_model_dnd_src_interface_init,
+ NULL,
+ NULL
+};
+
+void ui_list_init() {
+ list_model_type = g_type_register_static(
+ G_TYPE_OBJECT,
+ "UiListModel",
+ &list_model_info,
+ (GTypeFlags)0);
+ g_type_add_interface_static(
+ list_model_type,
+ GTK_TYPE_TREE_MODEL,
+ &list_model_interface_info);
+ g_type_add_interface_static(
+ list_model_type,
+ GTK_TYPE_TREE_DRAG_DEST,
+ &list_model_dnd_dest_interface_info);
+ g_type_add_interface_static(
+ list_model_type,
+ GTK_TYPE_TREE_DRAG_SOURCE,
+ &list_model_dnd_src_interface_info);
+}
+
+static void list_model_class_init(GObjectClass *cl, gpointer data) {
+ cl->dispose = ui_list_model_dispose;
+ cl->finalize = ui_list_model_finalize;
+
+}
+
+static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) {
+ i->get_flags = ui_list_model_get_flags;
+ i->get_n_columns = ui_list_model_get_n_columns;
+ i->get_column_type = ui_list_model_get_column_type;
+ i->get_iter = ui_list_model_get_iter;
+ i->get_path = ui_list_model_get_path;
+ i->get_value = ui_list_model_get_value;
+ i->iter_next = ui_list_model_iter_next;
+ i->iter_children = ui_list_model_iter_children;
+ i->iter_has_child = ui_list_model_iter_has_child;
+ i->iter_n_children = ui_list_model_iter_n_children;
+ i->iter_nth_child = ui_list_model_iter_nth_child;
+ i->iter_parent = ui_list_model_iter_parent;
+}
+
+static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) {
+ i->drag_data_received = ui_list_model_drag_data_received;
+ i->row_drop_possible = ui_list_model_row_drop_possible;
+}
+
+static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) {
+ i->drag_data_delete = ui_list_model_drag_data_delete;
+ i->drag_data_get = ui_list_model_drag_data_get;
+ i->row_draggable = ui_list_model_row_draggable;
+}
+
+static void list_model_init(UiListModel *instance, GObjectClass *cl) {
+ instance->columntypes = NULL;
+ instance->var = NULL;
+ instance->numcolumns = 0;
+ instance->stamp = g_random_int();
+}
+
+static GType ui_gtk_type(UiModelType type) {
+ switch(type) {
+ default: break;
+ case UI_STRING: return G_TYPE_STRING;
+ case UI_INTEGER: return G_TYPE_INT;
+ }
+ return G_TYPE_INVALID;
+}
+
+static void ui_model_set_value(GType type, void *data, GValue *value) {
+ switch(type) {
+ default: break;
+ case G_TYPE_OBJECT: {
+ value->g_type = G_TYPE_OBJECT;
+ g_value_set_object(value, data);
+ return;
+ }
+ case G_TYPE_STRING: {
+ value->g_type = G_TYPE_STRING;
+ g_value_set_string(value, data);
+ return;
+ }
+ case G_TYPE_INT: {
+ value->g_type = G_TYPE_INT;
+ int *i = data;
+ g_value_set_int(value, *i);
+ return;
+ }
+ }
+ value->g_type = G_TYPE_INVALID;
+}
+
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) {
+ UiListModel *model = g_object_new(list_model_type, NULL);
+ model->obj = obj;
+ model->model = info;
+ model->var = var;
+ model->columntypes = calloc(sizeof(GType), 2 * info->columns);
+ int ncol = 0;
+ for(int i=0;i<info->columns;i++) {
+ UiModelType type = info->types[i];
+ if(type == UI_ICON_TEXT) {
+ model->columntypes[ncol] = G_TYPE_OBJECT;
+ ncol++;
+ model->columntypes[ncol] = G_TYPE_STRING;
+ } else {
+ model->columntypes[ncol] = ui_gtk_type(info->types[i]);
+ }
+ ncol++;
+ }
+ model->numcolumns = ncol;
+ return model;
+}
+
+void ui_list_model_dispose(GObject *obj) {
+
+}
+
+void ui_list_model_finalize(GObject *obj) {
+
+}
+
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) {
+ return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
+}
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) {
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0);
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ return model->numcolumns;
+}
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) {
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID);
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID);
+ return model->columntypes[index];
+}
+
+gboolean ui_list_model_get_iter(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ g_assert(IS_UI_LIST_MODEL(tree_model));
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+
+ // check the depth of the path
+ // a list must have a depth of 1
+ gint depth = gtk_tree_path_get_depth(path);
+ g_assert(depth == 1);
+
+ // get row
+ gint *indices = gtk_tree_path_get_indices(path);
+ gint row = indices[0];
+
+ // check row
+ if(row == 0) {
+ // we don't need to count if the first element is requested
+ if(list->first(list) == NULL) {
+ return FALSE;
+ }
+ } else if(row >= list->count(list)) {
+ return FALSE;
+ }
+
+ // the UiList has an integrated iterator
+ // we only get a value to adjust it
+ void *val = NULL;
+ if(row == 0) {
+ val = list->first(list);
+ } else {
+ val = list->get(list, row);
+ }
+
+ iter->stamp = model->stamp;
+ iter->user_data = list->iter;
+ iter->user_data2 = (gpointer)(intptr_t)row; // list->index
+ iter->user_data3 = val;
+
+ return val ? TRUE : FALSE;
+}
+
+GtkTreePath* ui_list_model_get_path(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL);
+ g_return_val_if_fail(iter != NULL, NULL);
+ g_return_val_if_fail(iter->user_data != NULL, NULL);
+
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+
+ GtkTreePath *path = gtk_tree_path_new();
+ gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index
+
+ return path;
+}
+
+void ui_list_model_get_value(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ g_return_if_fail(IS_UI_LIST_MODEL(tree_model));
+ g_return_if_fail(iter != NULL);
+ g_return_if_fail(iter->user_data != NULL);
+
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+
+ g_return_if_fail(column < model->numcolumns);
+
+ // TODO: return correct value from column
+
+ //value->g_type = G_TYPE_STRING;
+ list->iter = iter->user_data;
+ //list->index = (int)(intptr_t)iter->user_data2;
+ //list->current = iter->user_data3;
+ if(model->model->getvalue) {
+ void *data = model->model->getvalue(iter->user_data3, column);
+ if(model->columntypes[column] == G_TYPE_OBJECT) {
+ UiImage *img = data;
+ ui_model_set_value(model->columntypes[column], img->pixbuf, value);
+ } else {
+ ui_model_set_value(model->columntypes[column], data, value);
+ }
+ } else {
+ value->g_type = G_TYPE_INVALID;
+ }
+}
+
+gboolean ui_list_model_iter_next(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+ g_return_val_if_fail(iter != NULL, FALSE);
+ //g_return_val_if_fail(iter->user_data != NULL, FALSE);
+
+ if(!iter->user_data) {
+ return FALSE;
+ }
+
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+ list->iter = iter->user_data;
+ //list->index = (int)(intptr_t)iter->user_data2;
+ void *val = list->next(list);
+ iter->user_data = list->iter;
+ intptr_t index = (intptr_t)iter->user_data2;
+ index++;
+ //iter->user_data2 = (gpointer)(intptr_t)list->index;
+ iter->user_data2 = (gpointer)index;
+ iter->user_data3 = val;
+ return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_children(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+
+ if(parent) {
+ return FALSE;
+ }
+
+ /*
+ * a list element has no children
+ * we set the iter to the first element
+ */
+ void *val = list->first(list);
+ iter->stamp = model->stamp;
+ iter->user_data = list->iter;
+ iter->user_data2 = (gpointer)0;
+ iter->user_data3 = val;
+
+ return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_has_child(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ return FALSE;
+}
+
+gint ui_list_model_iter_n_children(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ g_assert(IS_UI_LIST_MODEL(tree_model));
+
+ if(!iter) {
+ // return number of rows
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+ return list->count(list);
+ }
+
+ return 0;
+}
+
+gboolean ui_list_model_iter_nth_child(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE);
+
+ if(parent) {
+ return FALSE;
+ }
+
+ UiListModel *model = UI_LIST_MODEL(tree_model);
+ UiList *list = model->var->value;
+
+ // check n
+ if(n == 0) {
+ // we don't need to count if the first element is requested
+ if(list->first(list) == NULL) {
+ return FALSE;
+ }
+ } else if(n >= list->count(list)) {
+ return FALSE;
+ }
+
+ void *val = list->get(list, n);
+ iter->stamp = model->stamp;
+ iter->user_data = list->iter;
+ iter->user_data2 = (gpointer)(intptr_t)n; // list->index
+ iter->user_data3 = val;
+
+ return val ? TRUE : FALSE;
+}
+
+gboolean ui_list_model_iter_parent(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ return FALSE;
+}
+
+// ****** dnd ******
+
+gboolean ui_list_model_drag_data_received(
+ GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data)
+{
+ //printf("drag received\n");
+ UiListModel *model = UI_LIST_MODEL(drag_dest);
+ if(model->model->drop) {
+ gint *indices = gtk_tree_path_get_indices(dest_path);
+ gint row = indices[0];
+ UiEvent e;
+ e.obj = model->obj;
+ e.window = e.obj->window;
+ e.document = e.obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ UiSelection s;
+ s.data = selection_data;
+ model->model->drop(&e, &s, model->var->value, row);
+ }
+ return TRUE;
+}
+
+gboolean ui_list_model_row_drop_possible(
+ GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data)
+{
+ //printf("row_drop_possible\n");
+ UiListModel *model = UI_LIST_MODEL(drag_dest);
+ if(model->model->candrop) {
+ gint *indices = gtk_tree_path_get_indices(dest_path);
+ gint row = indices[0];
+ UiEvent e;
+ e.obj = model->obj;
+ e.window = e.obj->window;
+ e.document = e.obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ UiSelection s;
+ s.data = selection_data;
+ return model->model->candrop(&e, &s, model->var->value, row);
+ }
+ return TRUE;
+}
+
+gboolean ui_list_model_row_draggable(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ //printf("row_draggable\n");
+ UiListModel *model = UI_LIST_MODEL(drag_source);
+ if(model->model->candrag) {
+ gint *indices = gtk_tree_path_get_indices(path);
+ gint row = indices[0];
+ UiEvent e;
+ e.obj = model->obj;
+ e.window = e.obj->window;
+ e.document = e.obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ return model->model->candrag(&e, model->var->value, row);
+ }
+ return TRUE;
+}
+
+gboolean ui_list_model_drag_data_get(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data)
+{
+ //printf("drag_data_get\n");
+ UiListModel *model = UI_LIST_MODEL(drag_source);
+ if(model->model->data_get) {
+ gint *indices = gtk_tree_path_get_indices(path);
+ gint row = indices[0];
+ UiEvent e;
+ e.obj = model->obj;
+ e.window = e.obj->window;
+ e.document = e.obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ UiSelection s;
+ s.data = selection_data;
+ model->model->data_get(&e, &s, model->var->value, row);
+ }
+ return TRUE;
+}
+
+gboolean ui_list_model_drag_data_delete(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ //printf("drag_data_delete\n");
+ UiListModel *model = UI_LIST_MODEL(drag_source);
+ if(model->model->data_get) {
+ gint *indices = gtk_tree_path_get_indices(path);
+ gint row = indices[0];
+ UiEvent e;
+ e.obj = model->obj;
+ e.window = e.obj->window;
+ e.document = e.obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ model->model->data_delete(&e, model->var->value, row);
+ }
+ return TRUE;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MODEL_H
+#define MODEL_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../ui/tree.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListModel UiListModel;
+
+/*
+ * UiList to GtkTreeModel wrapper
+ */
+struct UiListModel {
+ GObject object;
+ UiObject *obj;
+ UiModel *model;
+ UiVar *var;
+ GType *columntypes;
+ int numcolumns;
+ int stamp;
+};
+
+/*
+ * initialize the class and register the type
+ */
+void ui_list_init();
+
+/*
+ * Creates a UiListModel for a given UiList
+ */
+UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info);
+
+void ui_list_model_dispose(GObject *obj);
+void ui_list_model_finalize(GObject *obj);
+
+
+// interface functions
+
+GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model);
+
+gint ui_list_model_get_n_columns(GtkTreeModel *tree_model);
+
+GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index);
+
+gboolean ui_list_model_get_iter(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+
+GtkTreePath* ui_list_model_get_path(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+
+void ui_list_model_get_value(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+
+gboolean ui_list_model_iter_next(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_children(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+
+gboolean ui_list_model_iter_has_child(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+
+gint ui_list_model_iter_n_children(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+
+gboolean ui_list_model_iter_nth_child(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+
+gboolean ui_list_model_iter_parent(
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+
+/* dnd */
+
+gboolean ui_list_model_drag_data_received(
+ GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest,
+ GtkSelectionData *selection_data);
+
+gboolean ui_list_model_row_drop_possible(
+ GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data);
+
+gboolean ui_list_model_row_draggable(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+
+gboolean ui_list_model_drag_data_get(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data);
+
+gboolean ui_list_model_drag_data_delete(
+ GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MODEL_H */
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+GTK_SRC_DIR = ui/gtk/
+GTK_OBJPRE = $(OBJ_DIR)$(GTK_SRC_DIR)
+
+# some objects are defined in config.mk
+GTKOBJ += toolkit.o
+GTKOBJ += window.o
+GTKOBJ += container.o
+GTKOBJ += menu.o
+GTKOBJ += toolbar.o
+GTKOBJ += button.o
+GTKOBJ += display.o
+GTKOBJ += text.o
+GTKOBJ += model.o
+GTKOBJ += tree.o
+GTKOBJ += image.o
+GTKOBJ += graphics.o
+GTKOBJ += range.o
+GTKOBJ += entry.o
+GTKOBJ += dnd.o
+
+TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
+TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event);
+
+double ui_scrollbar_get(UiRange *range);
+void ui_scrollbar_set(UiRange *range, double value);
+void ui_scrollbar_setrange(UiRange *range, double min, double max);
+void ui_scrollbar_setextent(UiRange *range, double extent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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);
+ }
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define TOOLKIT_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+typedef struct UiEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ int value;
+} UiEventData;
+
+typedef struct UiVarEventData {
+ UiObject *obj;
+ UiVar *var;
+ UiObserver **observers;
+} UiVarEventData;
+
+
+typedef struct UiJob {
+ UiObject *obj;
+ ui_threadfunc job_func;
+ void *job_data;
+ ui_callback finish_callback;
+ void *finish_data;
+} UiJob;
+
+struct UiSelection {
+ GtkSelectionData *data;
+};
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+#ifndef UI_GTK2
+void ui_app_quit();
+GtkApplication* ui_get_application();
+#endif
+
+int ui_get_scalefactor();
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata);
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
+
+void ui_set_active_window(UiObject *obj);
+UiObject *ui_get_active_window();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+
+#include "tree.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+ return column == 0 ? elm : NULL;
+}
+
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+ return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ // create treeview
+ GtkWidget *view = gtk_tree_view_new();
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+#if GTK_MINOR_VERSION >= 8
+ gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+ // TODO: implement for older gtk3
+#endif
+#else
+ // TODO: implement for gtk2
+#endif
+
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = getvalue;
+ UiList *list = var->value;
+ UiListModel *listmodel = ui_list_model_new(obj, var, model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+
+ UiListView *listview = malloc(sizeof(UiListView));
+ listview->obj = obj;
+ listview->widget = view;
+ listview->var = var;
+ listview->model = model;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ listview);
+
+ // bind var
+ list->update = ui_listview_update;
+ list->obj = listview;
+
+ // add callback
+ if(f) {
+ UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+ event->obj = obj;
+ event->userdata = udata;
+ event->activate = f;
+ event->selection = NULL;
+
+ g_signal_connect(
+ view,
+ "row-activated",
+ G_CALLBACK(ui_listview_activate_event),
+ event);
+ }
+
+ // add widget to the current container
+ GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ gtk_container_add(GTK_CONTAINER(scroll_area), view);
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, scroll_area, TRUE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ ct->current = view;
+
+ return scroll_area;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = list;
+ var->type = UI_VAR_SPECIAL;
+ return ui_listview_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ return ui_listview_var(obj, var, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+ printf("drag begin\n");
+
+}
+
+static void drag_end(
+ GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer udata)
+{
+ printf("drag end\n");
+
+}
+
+static GtkTargetEntry targetentries[] =
+ {
+ { "STRING", 0, 0 },
+ { "text/plain", 0, 1 },
+ { "text/uri-list", 0, 2 },
+ };
+
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
+ // create treeview
+ GtkWidget *view = gtk_tree_view_new();
+
+ int addi = 0;
+ for(int i=0;i<model->columns;i++) {
+ GtkTreeViewColumn *column = NULL;
+ if(model->types[i] == UI_ICON_TEXT) {
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column, model->titles[i]);
+
+ GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+ GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
+
+ gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
+ gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
+
+
+ gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
+ gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+
+ addi++;
+ } else {
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ model->titles[i],
+ renderer,
+ "text",
+ i + addi,
+ NULL);
+ }
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+ }
+
+ //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+ //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+
+#endif
+
+ UiList *list = var->value;
+ UiListModel *listmodel = ui_list_model_new(obj, var, model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+
+ //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+ //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+
+ // add TreeView as observer to the UiList to update the TreeView if the
+ // data changes
+ UiListView *tableview = malloc(sizeof(UiListView));
+ tableview->obj = obj;
+ tableview->widget = view;
+ tableview->var = var;
+ tableview->model = model;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ tableview);
+
+ // bind var
+ list->update = ui_listview_update;
+ list->obj = tableview;
+
+ // add callback
+ UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+ event->obj = obj;
+ event->activate = cb.activate;
+ event->selection = cb.selection;
+ event->userdata = cb.userdata;
+ if(cb.activate) {
+ g_signal_connect(
+ view,
+ "row-activated",
+ G_CALLBACK(ui_listview_activate_event),
+ event);
+ }
+ if(cb.selection) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(view));
+ g_signal_connect(
+ selection,
+ "changed",
+ G_CALLBACK(ui_listview_selection_event),
+ event);
+ }
+ // TODO: destroy callback
+
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+
+ // add widget to the current container
+ GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ gtk_container_add(GTK_CONTAINER(scroll_area), view);
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, scroll_area, TRUE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ ct->current = view;
+
+ return scroll_area;
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = list;
+ var->type = UI_VAR_SPECIAL;
+ return ui_table_var(obj, var, model, cb);
+}
+
+UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ return ui_table_var(obj, var, model, cb);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+ GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
+ if(c) {
+ return c->data;
+ }
+ return NULL;
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+ int al = 16;
+ char **targets = calloc(16, sizeof(char*));
+ targets[0] = target0;
+
+ int i = 1;
+ char *target;
+ while((target = va_arg(ap, char*)) != NULL) {
+ if(i >= al) {
+ al *= 2;
+ targets = realloc(targets, al*sizeof(char*));
+ }
+ targets[i] = target;
+ i++;
+ }
+
+ *nelm = i;
+ return targets;
+}
+
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+ GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+ for(int i=0;i<nelm;i++) {
+ targets[i].target = str[i];
+ }
+ return targets;
+}
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) {
+ va_list ap;
+ va_start(ap, target0);
+ int nelm;
+ char **targets = targets2array(target0, ap, &nelm);
+ va_end(ap);
+ ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+ free(targets);
+}
+
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+ GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+ gtk_tree_view_enable_model_drag_source(
+ GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+ GDK_BUTTON1_MASK,
+ t,
+ nelm,
+ GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+ free(t);
+}
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+ va_list ap;
+ va_start(ap, target0);
+ int nelm;
+ char **targets = targets2array(target0, ap, &nelm);
+ va_end(ap);
+ ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+ free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+ GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+ gtk_tree_view_enable_model_drag_dest(
+ GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+ t,
+ nelm,
+ GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+ free(t);
+}
+
+void ui_listview_update(UiList *list, int i) {
+ UiListView *view = list->obj;
+ UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model));
+ g_object_unref(G_OBJECT(model));
+ // TODO: free old model
+}
+
+void ui_listview_destroy(GtkWidget *w, UiListView *v) {
+ gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
+ ui_destroy_boundvar(v->obj->ctx, v->var);
+ // TODO: destroy model?
+ free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+ gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL);
+ ui_destroy_boundvar(v->obj->ctx, v->var);
+ // TODO: destroy model?
+ free(v);
+}
+
+
+void ui_listview_activate_event(
+ GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ UiTreeEventData *event)
+{
+ UiListSelection *selection = ui_listview_selection(
+ gtk_tree_view_get_selection(treeview),
+ event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ event->activate(&e, event->userdata);
+
+ if(selection->count > 0) {
+ free(selection->rows);
+ }
+ free(selection);
+}
+
+void ui_listview_selection_event(
+ GtkTreeSelection *treeselection,
+ UiTreeEventData *event)
+{
+ UiListSelection *selection = ui_listview_selection(treeselection, event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ event->selection(&e, event->userdata);
+
+ if(selection->count > 0) {
+ free(selection->rows);
+ }
+ free(selection);
+}
+
+UiListSelection* ui_listview_selection(
+ GtkTreeSelection *selection,
+ UiTreeEventData *event)
+{
+ GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+
+ UiListSelection *ls = malloc(sizeof(UiListSelection));
+ ls->count = g_list_length(rows);
+ ls->rows = calloc(ls->count, sizeof(int));
+ GList *r = rows;
+ int i = 0;
+ while(r) {
+ GtkTreePath *path = r->data;
+ ls->rows[i] = ui_tree_path_list_index(path);
+ r = r->next;
+ i++;
+ }
+ return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+ int depth = gtk_tree_path_get_depth(path);
+ if(depth == 0) {
+ fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+ return -1;
+ }
+ int *indices = gtk_tree_path_get_indices(path);
+ return indices[depth - 1];
+}
+
+
+/* --------------------------- ComboBox --------------------------- */
+
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+ return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = list;
+ var->type = UI_VAR_SPECIAL;
+ return ui_combobox_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ return ui_combobox_var(obj, var, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = getvalue;
+ UiListModel *listmodel = ui_list_model_new(obj, var, model);
+
+ GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata);
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, combobox, FALSE);
+ return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) {
+ GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
+
+ UiListView *uicbox = malloc(sizeof(UiListView));
+ uicbox->obj = obj;
+ uicbox->widget = combobox;
+ uicbox->var = model->var;
+ uicbox->model = model->model;
+
+ g_signal_connect(
+ combobox,
+ "destroy",
+ G_CALLBACK(ui_combobox_destroy),
+ uicbox);
+
+ // bind var
+ UiList *list = model->var->value;
+ list->update = ui_combobox_modelupdate;
+ list->obj = uicbox;
+
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT(combobox),
+ renderer,
+ "text",
+ 0,
+ NULL);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+
+ // add callback
+ if(f) {
+ UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = udata;
+ event->callback = f;
+ event->value = 0;
+
+ g_signal_connect(
+ combobox,
+ "changed",
+ G_CALLBACK(ui_combobox_change_event),
+ event);
+ }
+
+ return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+ UiEvent event;
+ event.obj = e->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = NULL;
+ event.intval = gtk_combo_box_get_active(widget);
+ e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+ UiListView *view = list->obj;
+ UiListModel *model = ui_list_model_new(view->obj, view->var, view->model);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model));
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+#include "model.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+ UiObject *obj;
+ GtkWidget *widget;
+ UiVar *var;
+ UiModel *model;
+} UiListView;
+
+typedef struct UiTreeEventData {
+ UiObject *obj;
+ ui_callback activate;
+ ui_callback selection;
+ void *userdata;
+} UiTreeEventData;
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
+void ui_listview_update(UiList *list, int i);
+void ui_combobox_destroy(GtkWidget *w, UiListView *v);
+void ui_listview_destroy(GtkWidget *w, UiListView *v);
+
+void ui_listview_activate_event(
+ GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ UiTreeEventData *event);
+void ui_listview_selection_event(
+ GtkTreeSelection *treeselection,
+ UiTreeEventData *event);
+UiListSelection* ui_listview_selection(
+ GtkTreeSelection *selection,
+ UiTreeEventData *event);
+int ui_tree_path_list_index(GtkTreePath *path);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_modelupdate(UiList *list, int i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(MOTIF_OBJPRE)%.o: motif/%.c
+ $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dnd.h"
+
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+ return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+ return NULL;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+
+#include "graphics.h"
+
+#include "container.h"
+
+static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) {
+ UiDrawEvent *drawevent = u;
+ //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c;
+ //XEvent *event = cbs->event;
+ Display *dpy = XtDisplay(widget);
+
+ UiEvent ev;
+ ev.obj = drawevent->obj;
+ ev.window = drawevent->obj->window;
+ ev.document = drawevent->obj->ctx->document;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+
+ XtVaGetValues(
+ widget,
+ XmNwidth,
+ &drawevent->gr.g.width,
+ XmNheight,
+ &drawevent->gr.g.height,
+ NULL);
+
+ XGCValues gcvals;
+ gcvals.foreground = BlackPixelOfScreen(XtScreen(widget));
+ drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals);
+
+ drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata);
+}
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ int n = 0;
+ Arg args[16];
+
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n);
+
+ if(f) {
+ UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = userdata;
+
+ event->gr.display = XtDisplay(drawingarea);
+ event->gr.widget = drawingarea;
+
+ Colormap colormap;
+ XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL);
+ event->gr.colormap = colormap;
+
+ XtAddCallback(
+ drawingarea,
+ XmNexposeCallback,
+ ui_drawingarea_expose,
+ event);
+
+ XtVaSetValues(drawingarea, XmNuserData, event, NULL);
+ }
+
+ XtManageChild(drawingarea);
+ return drawingarea;
+}
+
+static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) {
+ XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
+ XEvent *xevent = cbs->event;
+ UiMouseEventData *event = u;
+
+ if (cbs->reason == XmCR_INPUT) {
+ if (xevent->xany.type == ButtonPress) {
+ UiMouseEvent me;
+ me.x = xevent->xbutton.x;
+ me.y = xevent->xbutton.y;
+ // TODO: configurable double click time
+ me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2;
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &me;
+ e.intval = 0;
+ event->callback(&e, event->userdata);
+
+
+ event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time;
+ }
+ }
+
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+ if(f) {
+ UiMouseEventData *event = malloc(sizeof(UiMouseEventData));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = u;
+ event->last_event = 0;
+
+ XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event);
+ }
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+ XtVaGetValues(
+ drawingarea,
+ XmNwidth,
+ width,
+ XmNheight,
+ height,
+ NULL);
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+ //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True);
+ UiDrawEvent *event;
+ XtVaGetValues(drawingarea, XmNuserData, &event, NULL);
+ ui_drawingarea_expose(drawingarea, event, NULL);
+}
+
+
+/* -------------------- text layout functions -------------------- */
+UiTextLayout* ui_text(UiGraphics *g) {
+ UiTextLayout *text = malloc(sizeof(UiTextLayout));
+ memset(text, 0, sizeof(UiTextLayout));
+ text->text = NULL;
+ text->length = 0;
+ text->widget = ((UiXlibGraphics*)g)->widget;
+ text->fontset = NULL;
+ return text;
+}
+
+static void create_default_fontset(UiTextLayout *layout) {
+ char **missing = NULL;
+ int num_missing = 0;
+ char *def = NULL;
+ Display *dpy = XtDisplay(layout->widget);
+ XFontSet fs = XCreateFontSet(
+ dpy,
+ "-dt-interface system-medium-r-normal-s*utf*:,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni,"
+ "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208",
+ &missing, &num_missing, &def);
+ layout->fontset = fs;
+}
+
+void ui_text_free(UiTextLayout *text) {
+ // TODO
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+ ui_text_setstringl(layout, str, strlen(str));
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+ layout->text = str;
+ layout->length = len;
+ layout->changed = 1;
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+ create_default_fontset(layout);//TODO
+ layout->changed = 1;
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+ if(layout->changed) {
+ XRectangle ext, lext;
+ XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext);
+ layout->width = ext.width;
+ layout->height = ext.height;
+ layout->changed = 0;
+ }
+ *width = layout->width;
+ *height = layout->height;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+ layout->maxwidth = width;
+}
+
+
+/* -------------------- drawing functions -------------------- */
+
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ XColor color;
+ color.flags= DoRed | DoGreen | DoBlue;
+ color.red = red * 257;
+ color.green = green * 257;
+ color.blue = blue * 257;
+ XAllocColor(gr->display, gr->colormap, &color);
+ XSetForeground(gr->display, gr->gc, color.pixel);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ if(fill) {
+ XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+ } else {
+ XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+ }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ int width, height;
+ ui_text_getsize(text, &width, &height);
+ if(text->maxwidth > 0) {
+ XRectangle clip;
+ clip.x = x;
+ clip.y = y;
+ clip.width = text->maxwidth;
+ clip.height = height;
+ XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted);
+ }
+
+ XmbDrawString(
+ gr->display,
+ XtWindow(gr->widget),
+ text->fontset,
+ gr->gc,
+ x,
+ y + height,
+ text->text,
+ text->length);
+
+ XSetClipMask(gr->display, gr->gc, None);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiXlibGraphics {
+ UiGraphics g;
+ Display *display;
+ Widget widget;
+ Colormap colormap;
+ GC gc;
+} UiXlibGraphics;
+
+typedef struct UiDrawEvent {
+ ui_drawfunc callback;
+ UiObject *obj;
+ void *userdata;
+ UiXlibGraphics gr;
+} UiDrawEvent;
+
+typedef struct UiMouseEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ Time last_event;
+} UiMouseEventData;
+
+struct UiTextLayout {
+ char *text;
+ size_t length;
+ Widget widget;
+ XFontSet fontset;
+ int maxwidth;
+ int width;
+ int height;
+ int changed;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRAPHICS_H */
+
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+#include "image.h"
+
+UiIcon* ui_icon(const char *name, int size) {
+ return NULL;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+ return NULL;
+}
+
+void ui_free_icon(UiIcon *icon) {
+
+}
+
+UiImage* ui_icon_image(UiIcon *icon) {
+ return NULL;
+}
+
+UiImage* ui_image(const char *filename) {
+ return NULL;
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+ return NULL;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+ return NULL;
+}
+
+void ui_free_image(UiImage *img) {
+
+}
+
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/*
+ * File: image.h
+ * Author: olaf
+ *
+ * Created on 1. Juli 2018, 19:01
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+ Widget widget;
+ UiVar *list;
+ ui_getvaluefunc getvalue;
+} UiListView;
+
+typedef struct UiListViewEventData {
+ UiEventData event;
+ UiVar *var;
+} UiListViewEventData;
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count);
+void ui_listview_update(UiEvent *event, UiListView *view);
+void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIST_H */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+MOTIF_SRC_DIR = ui/motif/
+MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR)
+
+MOTIFOBJ = toolkit.o
+MOTIFOBJ += stock.o
+MOTIFOBJ += window.o
+MOTIFOBJ += container.o
+MOTIFOBJ += menu.o
+MOTIFOBJ += toolbar.o
+MOTIFOBJ += button.o
+MOTIFOBJ += label.o
+MOTIFOBJ += text.o
+MOTIFOBJ += list.o
+MOTIFOBJ += tree.o
+MOTIFOBJ += graphics.o
+MOTIFOBJ += range.o
+MOTIFOBJ += dnd.o
+MOTIFOBJ += image.o
+
+TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
+TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata);
+double ui_scrollbar_get(UiRange *range);
+void ui_scrollbar_set(UiRange *range, double value);
+void ui_scrollbar_setrange(UiRange *range, double min, double max);
+void ui_scrollbar_setextent(UiRange *range, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STOCK_H
+#define STOCK_H
+
+#include "../ui/stock.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiStockItem {
+ char *label;
+ char *accelerator;
+ char *accelerator_label;
+ // TODO: icon
+} UiStockItem;
+
+void ui_stock_init();
+
+void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon);
+
+UiStockItem* ui_get_stock_item(char *id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STOCK_H */
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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) {
+
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define TOOLKIT_H
+
+#include <inttypes.h>
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Display* ui_get_display();
+
+typedef struct UiEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ int value;
+} UiEventData;
+
+typedef struct UiJob {
+ UiObject *obj;
+ ui_threadfunc job_func;
+ void *job_data;
+ ui_callback finish_callback;
+ void *finish_data;
+} UiJob;
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+void ui_exit_mainloop();
+
+void ui_set_active_window(Widget w);
+Widget ui_get_active_window();
+
+void ui_secondary_event_loop(int *loop);
+void ui_window_dark_theme(Display *dp, Window window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiTreeEventData {
+ UiObject *obj;
+ ui_callback activate;
+ ui_callback selection;
+ void *userdata;
+ UiListSelection *last_selection;
+} UiTreeEventData;
+
+typedef struct UiTableView {
+ Widget widget;
+ UiVar *var;
+ UiModel *model;
+} UiTableView;
+
+void ui_table_update(UiEvent *event, UiTableView *view);
+int ui_add_icon_gadget(Widget container, UiModel *model, void *data);
+char* ui_type_to_string(UiModelType type, void *data, Boolean *free);
+
+void ui_table_action_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel);
+void ui_table_select_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel);
+
+UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs);
+
+Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ENTRY_H
+#define UI_ENTRY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i);
+UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d);
+UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r);
+
+UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname);
+UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname);
+UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname);
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
+void ui_spinner_setdigits(UIWIDGET spinner, int digits);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ENTRY_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_GRAPHICS_H
+#define UI_GRAPHICS_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGraphics UiGraphics;
+typedef struct UiTextLayout UiTextLayout;
+
+typedef void(*ui_drawfunc)(UiEvent*, UiGraphics*, void*);
+
+struct UiGraphics {
+ int width;
+ int height;
+};
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata);
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u);
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height);
+void ui_drawingarea_redraw(UIWIDGET drawingarea);
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g);
+void ui_text_free(UiTextLayout *text);
+void ui_text_setstring(UiTextLayout *layout, char *str);
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len);
+void ui_text_setfont(UiTextLayout *layout, char *font, int size);
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height);
+void ui_text_setwidth(UiTextLayout *layout, int width);
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue);
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2);
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill);
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_GRAPHICS_H */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_RANGE_H
+#define UI_RANGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_RANGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_STOCK_H
+#define UI_STOCK_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// motif stock ids
+#if UI_MOTIF || UI_COCOA || UI_QT4 || UI_QT5
+
+#define UI_STOCK_NEW "ui.stock.New"
+#define UI_STOCK_OPEN "ui.stock.Open"
+#define UI_STOCK_SAVE "ui.stock.Save"
+#define UI_STOCK_SAVE_AS "ui.stock.SaveAs"
+#define UI_STOCK_REVERT_TO_SAVED "ui.stock.RevertToSaved"
+#define UI_STOCK_GO_BACK "ui.stock.GoBack"
+#define UI_STOCK_GO_FORWARD "ui.stock.GoForward"
+#define UI_STOCK_ADD "ui.stock.Add"
+#define UI_STOCK_CLOSE "ui.stock.Close"
+
+#define UI_STOCK_UNDO "ui.stock.Undo"
+#define UI_STOCK_REDO "ui.stock.Redo"
+#define UI_STOCK_CUT "ui.stock.Cut"
+#define UI_STOCK_COPY "ui.stock.Copy"
+#define UI_STOCK_PASTE "ui.stock.Paste"
+#define UI_STOCK_DELETE "ui.stock.Delete"
+
+#endif
+
+#if UI_GTK2 || UI_GTK3
+
+#define UI_STOCK_NEW GTK_STOCK_NEW
+#define UI_STOCK_OPEN GTK_STOCK_OPEN
+#define UI_STOCK_SAVE GTK_STOCK_SAVE
+#define UI_STOCK_SAVE_AS GTK_STOCK_SAVE_AS
+#define UI_STOCK_REVERT_TO_SAVED GTK_STOCK_REVERT_TO_SAVED
+#define UI_STOCK_UNDO GTK_STOCK_UNDO
+#define UI_STOCK_REDO GTK_STOCK_REDO
+#define UI_STOCK_GO_BACK GTK_STOCK_GO_BACK
+#define UI_STOCK_GO_FORWARD GTK_STOCK_GO_FORWARD
+#define UI_STOCK_ADD GTK_STOCK_ADD
+#define UI_STOCK_CLOSE GTK_STOCK_CLOSE
+
+#define UI_STOCK_UNDO GTK_STOCK_UNDO
+#define UI_STOCK_REDO GTK_STOCK_REDO
+#define UI_STOCK_CUT GTK_STOCK_CUT
+#define UI_STOCK_COPY GTK_STOCK_COPY
+#define UI_STOCK_PASTE GTK_STOCK_PASTE
+#define UI_STOCK_DELETE GTK_STOCK_DELETE
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_STOCK_H */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_H
+#define UI_H
+
+#include "toolkit.h"
+#include "container.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "window.h"
+#include "stock.h"
+#include "button.h"
+#include "text.h"
+#include "properties.h"
+#include "tree.h"
+#include "graphics.h"
+#include "entry.h"
+#include "range.h"
+#include "image.h"
+#include "display.h"
+#include "dnd.h"
+
+#endif /* UI_H */
+
--- /dev/null
+/*
+ * 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 */
+