From: Olaf Wintermann Date: Sat, 5 Jun 2021 07:39:33 +0000 (+0200) Subject: add existing code (build system, libs, initial mizucp code) X-Git-Url: https://develop.uap-core.de/gitweb/mizunara.git/commitdiff_plain/6de1fe1dd8731d4d952657181f871f9723bbb8b2 add existing code (build system, libs, initial mizucp code) --- 6de1fe1dd8731d4d952657181f871f9723bbb8b2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a5d528 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +config.mk diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7162bba --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +all: config.mk + $(MAKE) -f make/Makefile.mk + +config.mk: + ./configure + +clean: FORCE + rm -fR build/* + +FORCE: diff --git a/configure b/configure new file mode 100755 index 0000000..44f8e8a --- /dev/null +++ b/configure @@ -0,0 +1,728 @@ +#!/bin/sh + + +PREFIX=/usr +EPREFIX=$PREFIX + +BINDIR= +SBINDIR= +LIBDIR= +LIBEXECDIR= +DATADIR= +SYSCONFDIR= +SHAREDSTATEDIR= +LOCALSTATEDIR= +INCLUDEDIR= +INFODIR= +MANDIR= + +OS=`uname -s` +OS_VERSION=`uname -r` + +TEMP_DIR=".tmp-`uname -n`" +mkdir -p $TEMP_DIR +if [ $? -ne 0 ]; then + echo "Cannot create tmp dir" + echo "Abort" +fi +touch $TEMP_DIR/options +touch $TEMP_DIR/features + +# features + +# help text +printhelp() +{ + echo "Usage: $0 [OPTIONS]..." + cat << __EOF__ +Installation directories: + --prefix=PREFIX path prefix for architecture-independent files + [/usr] + --exec-prefix=EPREFIX path prefix for architecture-dependent files + [PREFIX] + + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR system configuration files [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --mandir=DIR man documentation [DATAROOTDIR/man] + +Options: + --toolkit=(gtk3|motif) + +__EOF__ +} + +# +# parse arguments +# +for ARG in $@ +do + case "$ARG" in + "--prefix="*) PREFIX=${ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${ARG#--includedir=} ;; + "--infodir="*) INFODIR=${ARG#--infodir=} ;; + "--mandir"*) MANDIR=${ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;; + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac +done + +# set dir variables +if [ -z "$BINDIR" ]; then + BINDIR=$EPREFIX/bin +fi +if [ -z "$SBINDIR" ]; then + SBINDIR=$EPREFIX/sbin +fi +if [ -z "$LIBDIR" ]; then + LIBDIR=$EPREFIX/lib +fi +if [ -z "$LIBEXEC" ]; then + LIBEXECDIR=$EPREFIX/libexec +fi +if [ -z "$DATADIR" ]; then + DATADIR=$PREFIX/share +fi +if [ -z "$SYSCONFDIR" ]; then + SYSCONFDIR=$PREFIX/etc +fi +if [ -z "$SHAREDSTATEDIR" ]; then + SHAREDSTATEDIR=$PREFIX/com +fi +if [ -z "$LOCALSTATEDIR" ]; then + LOCALSTATEDIR=$PREFIX/var +fi +if [ -z "$INCLUDEDIR" ]; then + INCLUDEDIR=$PREFIX/include +fi +if [ -z "$INFODIR" ]; then + INFODIR=$PREFIX/info +fi +if [ -z "$MANDIR" ]; then + MANDIR=$PREFIX/man +fi + +which pkg-config > /dev/null +if [ $? -eq 0 ]; then + PKG_CONFIG=pkg-config +else + PKG_CONFIG=false +fi + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +printf "detect platform... " +if [ $OS = SunOS ]; then + PLATFORM="solaris sunos unix svr4" +fi +if [ $OS = Linux ]; then + PLATFORM="linux unix" +fi +if [ $OS = FreeBSD ]; then + PLATFORM="freebsd bsd unix" +fi +if [ $OS = Darwin ]; then + PLATFORM="macos osx bsd unix" +fi +echo $OS | grep "MINGW" > /dev/null +if [ $? -eq 0 ]; then + PLATFORM="windows mingw" +fi + +if [ -z "$PLATFORM" ]; then + PLATFORM="unix" +fi + +for p in $PLATFORM +do + PLATFORM_NAME=$p + break +done +echo $PLATFORM_NAME + +isplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 0 + fi + done + return 1 +} +isnotplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 1 + fi + done + return 0 +} + +# generate config.mk and config.h +cat > $TEMP_DIR/config.mk << __EOF__ +# +# config.mk generated by configure +# + +# general vars + +PREFIX=$PREFIX +EPREFIX=$EPREFIX + +BINDIR=$BINDIR +SBINDIR=$SBINDIR +LIBDIR=$LIBDIR +LIBEXECDIR=$LIBEXECDIR +DATADIR=$DATADIR +SYSCONFDIR=$SYSCONFDIR +SHAREDSTATEDIR=$SHAREDSTATEDIR +LOCALSTATEDIR=$LOCALSTATEDIR +INCLUDEDIR=$INCLUDEDIR +INFODIR=$INFODIR +MANDIR=$MANDIR + +__EOF__ + +echo > $TEMP_DIR/make.mk + +ENV_CFLAGS=$CFLAGS +ENV_LDFLAGS=$LDFLAGS +ENV_CXXFLAGS=$CXXFLAGS + +# Toolchain detection +# this will insert make vars to config.mk +. make/toolchain.sh + +# add user specified flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${ENV_CFLAGS}" ]; then + echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_CXXFLAGS}" ]; then + echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_LDFLAGS}" ]; then + echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# DEPENDENCIES +# + +dependency_curl() +{ + printf "checking for curl... " + # dependency curl platform="windows" + while true + do + if isnotplatform "windows"; then + break + fi + CFLAGS="$CFLAGS -I/mingw/include" + LDFLAGS="$LDFLAGS -lcurl" + echo yes + return 0 + done + + # dependency curl platform="macos" + while true + do + if isnotplatform "macos"; then + break + fi + curl-config --cflags > /dev/null + if [ $? -eq 0 ]; then + CFLAGS="$CFLAGS `curl-config --cflags`" + else + break + fi + curl-config --ldflags > /dev/null + if [ $? -eq 0 ]; then + LDFLAGS="$LDFLAGS `curl-config --ldflags`" + else + break + fi + echo yes + return 0 + done + + # dependency curl + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + $PKG_CONFIG libcurl + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libcurl`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libcurl`" + echo yes + return 0 + done + + # dependency curl + while true + do + curl-config --cflags > /dev/null + if [ $? -eq 0 ]; then + CFLAGS="$CFLAGS `curl-config --cflags`" + else + break + fi + curl-config --ldflags > /dev/null + if [ $? -eq 0 ]; then + LDFLAGS="$LDFLAGS `curl-config --ldflags`" + else + break + fi + which curl-config > /dev/null + if [ $? -ne 0 ]; then + break + fi + echo yes + return 0 + done + + echo no + return 1 +} +dependency_gtk3() +{ + printf "checking for gtk3... " + # dependency gtk3 + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + $PKG_CONFIG gtk+-3.0 + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gtk+-3.0`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs gtk+-3.0`" + CFLAGS="$CFLAGS -DUI_GTK3" + LDFLAGS="$LDFLAGS -lpthread" + echo yes + return 0 + done + + echo no + return 1 +} +dependency_openssl() +{ + printf "checking for openssl... " + # dependency openssl platform="windows" + while true + do + if isnotplatform "windows"; then + break + fi + LDFLAGS="$LDFLAGS -lssl -lcrypto" + echo yes + return 0 + done + + # dependency openssl platform="macos" + while true + do + if isnotplatform "macos"; then + break + fi + LDFLAGS="$LDFLAGS -framework CoreFoundation" + echo yes + return 0 + done + + # dependency openssl platform="bsd" + while true + do + if isnotplatform "bsd"; then + break + fi + if isplatform "macos"; then + break + fi + LDFLAGS="$LDFLAGS -lssl -lcrypto" + echo yes + return 0 + done + + # dependency openssl + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + $PKG_CONFIG openssl + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags openssl`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs openssl`" + echo yes + return 0 + done + + echo no + return 1 +} +dependency_motif() +{ + printf "checking for motif... " + # dependency motif + while true + do + CFLAGS="$CFLAGS -DUI_MOTIF" + LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread" + echo yes + return 0 + done + + echo no + return 1 +} +dependency_libxml2() +{ + printf "checking for libxml2... " + # dependency libxml2 platform="windows" + while true + do + if isnotplatform "windows"; then + break + fi + xml2-config --cflags > /dev/null + if [ $? -eq 0 ]; then + CFLAGS="$CFLAGS `xml2-config --cflags`" + else + break + fi + xml2-config --libs > /dev/null + if [ $? -eq 0 ]; then + LDFLAGS="$LDFLAGS `xml2-config --libs`" + else + break + fi + echo yes + return 0 + done + + # dependency libxml2 platform="macos" + while true + do + if isnotplatform "macos"; then + break + fi + xml2-config --cflags > /dev/null + if [ $? -eq 0 ]; then + CFLAGS="$CFLAGS `xml2-config --cflags`" + else + break + fi + xml2-config --libs > /dev/null + if [ $? -eq 0 ]; then + LDFLAGS="$LDFLAGS `xml2-config --libs`" + else + break + fi + echo yes + return 0 + done + + # dependency libxml2 + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + $PKG_CONFIG libxml-2.0 + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libxml-2.0`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libxml-2.0`" + echo yes + return 0 + done + + # dependency libxml2 + while true + do + xml2-config --cflags > /dev/null + if [ $? -eq 0 ]; then + CFLAGS="$CFLAGS `xml2-config --cflags`" + else + break + fi + xml2-config --libs > /dev/null + if [ $? -eq 0 ]; then + LDFLAGS="$LDFLAGS `xml2-config --libs`" + else + break + fi + echo yes + return 0 + done + + echo no + return 1 +} + +DEPENDENCIES_FAILED= +ERROR=0 +# general dependencies +CFLAGS= +LDFLAGS= +while true +do + if isnotplatform "unix"; then + break + fi + if isplatform "macos"; then + break + fi + while true + do + + cat >> $TEMP_DIR/make.mk << __EOF__ +OBJ_EXT = o +LIB_EXT = a +PACKAGE_SCRIPT = package_unix.sh + +__EOF__ + + break + done + + break +done +while true +do + while true + do + + LDFLAGS="$LDFLAGS -lpthread" + + break + done + + break +done +while true +do + if isnotplatform "bsd"; then + break + fi + if isplatform "macos"; then + break + fi + while true + do + + CFLAGS="$CFLAGS -I/usr/local/include" + LDFLAGS="$LDFLAGS -L/usr/local/lib" + + break + done + + break +done + +# add general dependency flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# OPTION VALUES +# +checkopt_toolkit_gtk3() +{ + VERR=0 + dependency_gtk3 + if [ $? -ne 0 ]; then + VERR=1 + fi + if [ $VERR -ne 0 ]; then + return 1 + fi + cat >> $TEMP_DIR/make.mk << __EOF__ +TOOLKIT = gtk +GTKOBJ = draw_cairo.o + +__EOF__ + return 0 +} +checkopt_toolkit_motif() +{ + VERR=0 + dependency_motif + if [ $? -ne 0 ]; then + VERR=1 + fi + if [ $VERR -ne 0 ]; then + return 1 + fi + cat >> $TEMP_DIR/make.mk << __EOF__ +TOOLKIT = motif + +__EOF__ + return 0 +} + +# +# TARGETS +# +CFLAGS= +CXXFLAGS= +LDFLAGS= + +# Target: tk +CFLAGS= +LDFLAGS= +CXXFLAGS= + + +# Features + +# Option: --toolkit +if [ -z $OPT_TOOLKIT ]; then + SAVED_ERROR=$ERROR + SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED + ERROR=0 + while true + do + checkopt_toolkit_motif + if [ $? -eq 0 ]; then + echo " toolkit: motif" >> $TEMP_DIR/options + ERROR=0 + break + fi + checkopt_toolkit_gtk3 + if [ $? -eq 0 ]; then + echo " toolkit: gtk3" >> $TEMP_DIR/options + ERROR=0 + break + fi + break + done + if [ $ERROR -ne 0 ]; then + SAVED_ERROR=1 + fi + ERROR=$SAVED_ERROR + DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED= +else + if false; then + false + elif [ $OPT_TOOLKIT = "gtk3" ]; then + echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options + checkopt_toolkit_gtk3 + if [ $? -ne 0 ]; then + ERROR=1 + fi + elif [ $OPT_TOOLKIT = "motif" ]; then + echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options + checkopt_toolkit_motif + if [ $? -ne 0 ]; then + ERROR=1 + fi + fi +fi + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "TK_CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "TK_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "TK_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# Target: dav +CFLAGS= +LDFLAGS= +CXXFLAGS= + +dependency_curl +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED curl " + ERROR=1 +fi +dependency_libxml2 +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libxml2 " + ERROR=1 +fi +dependency_openssl +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED openssl " + ERROR=1 +fi + +# Features + + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "DAV_CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "DAV_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "DAV_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +if [ $ERROR -ne 0 ]; then + echo + echo "Error: Unresolved dependencies" + echo $DEPENDENCIES_FAILED + rm -Rf $TEMP_DIR + exit 1 +fi + +echo "configure finished" +echo +echo "Build Config:" +echo " PREFIX: $PREFIX" +echo " TOOLCHAIN: $TOOLCHAIN_NAME" +echo "Options:" +cat $TEMP_DIR/options +echo +cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk +rm -Rf $TEMP_DIR + + diff --git a/libidav/Makefile b/libidav/Makefile new file mode 100644 index 0000000..659d0c5 --- /dev/null +++ b/libidav/Makefile @@ -0,0 +1,53 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2019 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT=../ +include ../config.mk + +# list of source files +SRC = webdav.c +SRC += session.c +SRC += resource.c +SRC += methods.c +SRC += utils.c +SRC += davqlparser.c +SRC += davqlexec.c +SRC += crypto.c +SRC += xml.c +SRC += versioning.c + +OBJ = $(SRC:%.c=../build/libidav/%.$(OBJ_EXT)) + +all: ../build/ucx ../build/lib/libidav.$(LIB_EXT) + +../build/lib/libidav.$(LIB_EXT): $(OBJ) + $(AR) $(ARFLAGS) $(AOFLAGS)../build/lib/libidav.$(LIB_EXT) $(OBJ) + +../build/libidav/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(DAV_CFLAGS) -c -I../ucx -o $@ $< + diff --git a/libidav/crypto.c b/libidav/crypto.c new file mode 100644 index 0000000..e9d25c3 --- /dev/null +++ b/libidav/crypto.c @@ -0,0 +1,1526 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include "utils.h" + +#include "crypto.h" + +/* -------------------- OpenSSL Crypto Functions -------------------- */ +#ifdef DAV_USE_OPENSSL + +#if OPENSSL_VERSION_NUMBER < 0x10000000L + +static EVP_CIPHER_CTX* create_evp_cipher_ctx() { + EVP_CIPHER_CTX *ctx = malloc(sizeof(EVP_CIPHER_CTX)); + EVP_CIPHER_CTX_init(ctx); + return ctx; +} + +static void free_evp_cipher_ctx(EVP_CIPHER_CTX *ctx) { + EVP_CIPHER_CTX_cleanup(ctx); + free(ctx); +} + +#define EVP_CIPHER_CTX_new() create_evp_cipher_ctx() +#define EVP_CIPHER_CTX_free(ctx) free_evp_cipher_ctx(ctx) + +#endif + +int dav_rand_bytes(unsigned char *buf, size_t len) { + return !RAND_bytes(buf, len); +} + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) { + AESDecrypter *dec = calloc(1, sizeof(AESDecrypter)); + SHA256_Init(&dec->sha256); + dec->stream = stream; + dec->write = write_func; + dec->key = key; + dec->init = 0; + dec->ivpos = 0; + + return dec; +} + +void aes_decrypter_init(AESDecrypter *dec) { + //EVP_CIPHER_CTX_init(&dec->ctx); + dec->ctx = EVP_CIPHER_CTX_new(); + dec->init = 1; + if(dec->key->type == DAV_KEY_AES128) { + EVP_DecryptInit_ex( + dec->ctx, + EVP_aes_128_cbc(), + NULL, + dec->key->data, + dec->ivtmp); + } else if(dec->key->type == DAV_KEY_AES256) { + EVP_DecryptInit_ex( + dec->ctx, + EVP_aes_256_cbc(), + NULL, + dec->key->data, + dec->ivtmp); + } else { + fprintf(stderr, "unknown key type\n"); + exit(-1); + } +} + +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { + int len = s*n; + if(!dec->init) { + size_t n = 16 - dec->ivpos; + size_t cp = n > len ? len : n; + memcpy(dec->ivtmp + dec->ivpos, buf, cp); + dec->ivpos += cp; + if(dec->ivpos >= 16) { + aes_decrypter_init(dec); + } + if(len == cp) { + return len; + } else { + buf = (char*)buf + cp; + len -= cp; + } + } + + int outlen = len + 16; + unsigned char *out = malloc(outlen); + EVP_DecryptUpdate(dec->ctx, out, &outlen, buf, len); + ssize_t wlen = dec->write(out, 1, outlen, dec->stream); + SHA256_Update(&dec->sha256, out, wlen); + free(out); + return (s*n) / s; +} + +void aes_decrypter_shutdown(AESDecrypter *dec) { + if(dec->init) { + void *out = malloc(128); + int len = 0; + EVP_DecryptFinal_ex(dec->ctx, out, &len); + dec->write(out, 1, len, dec->stream); + SHA256_Update(&dec->sha256, out, len); + free(out); + //EVP_CIPHER_CTX_cleanup(&dec->ctx); + EVP_CIPHER_CTX_free(dec->ctx); + } +} + +void aes_decrypter_close(AESDecrypter *dec) { + free(dec); +} + + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) { + unsigned char *iv = malloc(16); + if(!RAND_bytes(iv, 16)) { + free(iv); + return NULL; + } + + AESEncrypter *enc = malloc(sizeof(AESEncrypter)); + SHA256_Init(&enc->sha256); + enc->stream = stream; + enc->read = read_func; + enc->seek = seek_func; + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + enc->end = 0; + enc->iv = iv; + enc->ivlen = 16; + + //EVP_CIPHER_CTX_init(&enc->ctx); + enc->ctx = EVP_CIPHER_CTX_new(); + if(key->type == DAV_KEY_AES128) { + EVP_EncryptInit_ex(enc->ctx, EVP_aes_128_cbc(), NULL, key->data, enc->iv); + } else if(key->type == DAV_KEY_AES256) { + EVP_EncryptInit_ex(enc->ctx, EVP_aes_256_cbc(), NULL, key->data, enc->iv); + } else { + fprintf(stderr, "unknown key type\n"); + exit(-1); + } + return enc; +} + +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { + size_t len = s*n; + if(enc->tmp) { + size_t tmp_diff = enc->tmplen - enc->tmpoff; + size_t cp_len = tmp_diff > len ? len : tmp_diff; + memcpy(buf, enc->tmp + enc->tmpoff, cp_len); + enc->tmpoff += cp_len; + if(enc->tmpoff >= enc->tmplen) { + free(enc->tmp); + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + } + return cp_len / s; + } + + if(enc->end) { + return 0; + } + + void *in = malloc(len); + size_t in_len = enc->read(in, 1, len, enc->stream); + + SHA256_Update(&enc->sha256, in, in_len); + + unsigned char *out = NULL; + int outlen = 0; + size_t ivl = enc->ivlen; + if(in_len != 0) { + outlen = len + 32; + out = malloc(outlen + ivl); + if(ivl > 0) { + memcpy(out, enc->iv, ivl); + } + EVP_EncryptUpdate(enc->ctx, out + ivl, &outlen, in, in_len); + if(in_len != len) { + int newoutlen = 16; + EVP_EncryptFinal_ex(enc->ctx, out + ivl + outlen, &newoutlen); + outlen += newoutlen; + enc->end = 1; + } + } else { + out = malloc(16); + EVP_EncryptFinal_ex(enc->ctx, out, &outlen); + enc->end = 1; + } + enc->tmp = (char*)out; + enc->tmplen = outlen + ivl; + enc->tmpoff = 0; + + if(enc->ivlen > 0) { + enc->ivlen = 0; + } + + free(in); + + return aes_read(buf, s, n, enc); +} + +void aes_encrypter_close(AESEncrypter *enc) { + if(enc->tmp) { + free(enc->tmp); + } + if(enc->iv) { + free(enc->iv); + } + //EVP_CIPHER_CTX_cleanup(&enc->ctx); + EVP_CIPHER_CTX_free(enc->ctx); + free(enc); +} + +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) { + if(origin != SEEK_SET || offset != 0 || !enc->seek) { + return CURL_SEEKFUNC_CANTSEEK; + } + + enc->ivlen = 16; + if(enc->seek(enc->stream, 0, SEEK_SET) != 0) { + return CURL_SEEKFUNC_FAIL; + } + return CURL_SEEKFUNC_OK; +} + + +char* aes_encrypt(const char *in, size_t len, DavKey *key) { + unsigned char iv[16]; + if(!RAND_bytes(iv, 16)) { + return NULL; + } + + //EVP_CIPHER_CTX ctx; + //EVP_CIPHER_CTX_init(&ctx); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if(key->type == DAV_KEY_AES128) { + EVP_EncryptInit_ex( + ctx, + EVP_aes_128_cbc(), + NULL, + (unsigned char*)key->data, + iv); + } else if(key->type == DAV_KEY_AES256) { + EVP_EncryptInit_ex( + ctx, + EVP_aes_256_cbc(), + NULL, + (unsigned char*)key->data, + iv); + } else { + //EVP_CIPHER_CTX_cleanup(&ctx); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + //int len = strlen(in); + int buflen = len + 64; + unsigned char *buf = calloc(1, buflen); + memcpy(buf, iv, 16); + + int l = buflen - 16; + EVP_EncryptUpdate(ctx, buf + 16, &l, (unsigned char*)in, len); + + int f = 0; + EVP_EncryptFinal_ex(ctx, buf + 16 + l, &f); + char *out = util_base64encode((char*)buf, 16 + l + f); + free(buf); + EVP_CIPHER_CTX_free(ctx); + //EVP_CIPHER_CTX_cleanup(&ctx); + + return out; +} + +char* aes_decrypt(const char *in, size_t *length, DavKey *key) { + int len; + unsigned char *buf = (unsigned char*)util_base64decode_len(in, &len); + + //EVP_CIPHER_CTX ctx; + //EVP_CIPHER_CTX_init(&ctx); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if(key->type == DAV_KEY_AES128) { + EVP_DecryptInit_ex( + ctx, + EVP_aes_128_cbc(), + NULL, + key->data, + buf); + } else if(key->type == DAV_KEY_AES256) { + EVP_DecryptInit_ex( + ctx, + EVP_aes_256_cbc(), + NULL, + key->data, + buf); + } else { + //EVP_CIPHER_CTX_cleanup(&ctx); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + unsigned char *out = malloc(len + 1); + int outlen = len; + unsigned char *in_buf = buf + 16; + int inlen = len - 16; + int f = 0; + + EVP_DecryptUpdate(ctx, out, &outlen, in_buf, inlen); + EVP_DecryptFinal_ex(ctx, out + outlen, &f); + out[outlen + f] = '\0'; + free(buf); + //EVP_CIPHER_CTX_cleanup(&ctx); + EVP_CIPHER_CTX_free(ctx); + + *length = outlen + f; + return (char*)out; +} + + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf){ + SHA256_Final((unsigned char*)buf, sha256); +} + +char* dav_create_hash(const char *data, size_t len) { + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + DAV_SHA_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, data, len); + SHA256_Final(hash, &ctx); + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} + +DAV_SHA_CTX* dav_hash_init(void) { + DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX)); + SHA256_Init(ctx); + return ctx; +} + +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) { + SHA256_Update(ctx, data, len); +} + +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) { + SHA256_Final(buf, ctx); + free(ctx); +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static int crypto_pw2key_error = 0; +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) { + if(!crypto_pw2key_error) { + fprintf(stderr, "Error: password key derivation not supported on this platform: openssl to old\n"); + crypto_pw2key_error = 1; + } + return 0; +} + +#else +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) { + if(!password) { + return NULL; + } + size_t len = strlen(password); + if(len == 0) { + return NULL; + } + + // setup key data and length + unsigned char keydata[32]; + int keylen = 32; + switch(enc) { + case DAV_KEY_AES128: keylen = 16; break; + case DAV_KEY_AES256: keylen = 32; break; + default: return NULL; + } + + // generate key + switch(pwfunc) { + case DAV_PWFUNC_PBKDF2_SHA256: { + PKCS5_PBKDF2_HMAC( + password, + len, + salt, + saltlen, + DAV_CRYPTO_ITERATION_COUNT, + EVP_sha256(), + keylen, + keydata); + break; + } + case DAV_PWFUNC_PBKDF2_SHA512: { + PKCS5_PBKDF2_HMAC( + password, + len, + salt, + saltlen, + DAV_CRYPTO_ITERATION_COUNT, + EVP_sha512(), + keylen, + keydata); + break; + } + default: return NULL; + } + + // create DavKey with generated data + DavKey *key = malloc(sizeof(DavKey)); + key->data = malloc(keylen); + key->length = keylen; + key->name = NULL; + key->type = enc; + memcpy(key->data, keydata, keylen); + return key; +} +#endif + +#endif + + +/* -------------------- Apple Crypto Functions -------------------- */ +#ifdef DAV_CRYPTO_COMMON_CRYPTO + +#define RANDOM_BUFFER_LENGTH 256 +static char randbuf[RANDOM_BUFFER_LENGTH]; +static int rbufpos = RANDOM_BUFFER_LENGTH; + +int dav_rand_bytes(unsigned char *buf, size_t len) { + if(len + rbufpos > RANDOM_BUFFER_LENGTH) { + int devr = open("/dev/urandom", O_RDONLY); + if(devr == -1) { + return 1; + } + + if(read(devr, randbuf, RANDOM_BUFFER_LENGTH) < RANDOM_BUFFER_LENGTH) { + close(devr); + return 1; + } + + rbufpos = 0; + if(len > RANDOM_BUFFER_LENGTH) { + int err = 0; + if(read(devr, buf, len) < len) { + err = 1; + } + close(devr); + return err; + } + + close(devr); + } + + char *r = randbuf; + memcpy(buf, r + rbufpos, len); + rbufpos += len; + + return 0; +} + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) { + AESDecrypter *dec = calloc(1, sizeof(AESDecrypter)); + CC_SHA256_Init(&dec->sha256); + dec->stream = stream; + dec->write = write_func; + dec->key = key; + dec->init = 0; + dec->ivpos = 0; + + return dec; +} + + +void aes_decrypter_init(AESDecrypter *dec) { + //EVP_CIPHER_CTX_init(&dec->ctx); + dec->init = 1; + + CCCryptorRef cryptor; + CCCryptorStatus status; + if(dec->key->type == DAV_KEY_AES128) { + status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor); + } else if(dec->key->type == DAV_KEY_AES256) { + status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor); + } else { + fprintf(stderr, "unknown key type\n"); + exit(-1); + } + dec->ctx = cryptor; +} + +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { + int len = s*n; + if(!dec->init) { + size_t n = 16 - dec->ivpos; + size_t cp = n > len ? len : n; + memcpy(dec->ivtmp + dec->ivpos, buf, cp); + dec->ivpos += cp; + if(dec->ivpos >= 16) { + aes_decrypter_init(dec); + } + if(len == cp) { + return len; + } else { + buf = (char*)buf + cp; + len -= cp; + } + } + + int outlen = len + 16; + unsigned char *out = malloc(outlen); + + CCCryptorStatus status; + size_t avail = outlen; + size_t moved = 0; + status = CCCryptorUpdate(dec->ctx, buf, len, out, avail, &moved); + + ssize_t wlen = dec->write(out, 1, moved, dec->stream); + CC_SHA256_Update(&dec->sha256, out, wlen); + free(out); + return (s*n) / s; +} + +void aes_decrypter_shutdown(AESDecrypter *dec) { + if(dec->init) { + void *out = malloc(128); + size_t len = 0; + //EVP_DecryptFinal_ex(dec->ctx, out, &len); + CCCryptorFinal(dec->ctx, out, 128, &len); + + + dec->write(out, 1, len, dec->stream); + CC_SHA256_Update(&dec->sha256, out, len); + free(out); + //EVP_CIPHER_CTX_cleanup(&dec->ctx); + //EVP_CIPHER_CTX_free(dec->ctx); + } +} + +void aes_decrypter_close(AESDecrypter *dec) { + +} + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) { + unsigned char *iv = malloc(16); + if(dav_rand_bytes(iv, 16)) { + return NULL; + } + + CCCryptorRef cryptor; + CCCryptorStatus status; + if(key->type == DAV_KEY_AES128) { + status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor); + } else if(key->type == DAV_KEY_AES256) { + status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor); + } else { + free(iv); + return NULL; + } + + AESEncrypter *enc = malloc(sizeof(AESEncrypter)); + enc->ctx = cryptor; + CC_SHA256_Init(&enc->sha256); + enc->stream = stream; + enc->read = read_func; + enc->seek = seek_func; + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + enc->end = 0; + enc->iv = iv; + enc->ivlen = 16; + + return enc; +} + +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { + size_t len = s*n; + if(enc->tmp) { + size_t tmp_diff = enc->tmplen - enc->tmpoff; + size_t cp_len = tmp_diff > len ? len : tmp_diff; + memcpy(buf, enc->tmp + enc->tmpoff, cp_len); + enc->tmpoff += cp_len; + if(enc->tmpoff >= enc->tmplen) { + free(enc->tmp); + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + } + return cp_len / s; + } + + if(enc->end) { + return 0; + } + + void *in = malloc(len); + size_t in_len = enc->read(in, 1, len, enc->stream); + + CC_SHA256_Update(&enc->sha256, in, in_len); + + unsigned char *out = NULL; + size_t outlen = 0; + size_t ivl = enc->ivlen; + if(in_len != 0) { + outlen = len + 32; + out = malloc(outlen + ivl); + if(ivl > 0) { + memcpy(out, enc->iv, ivl); + } + + CCCryptorStatus status; + size_t avail = outlen; + status = CCCryptorUpdate(enc->ctx, in, in_len, out + ivl, avail, &outlen); + if(in_len != len) { + size_t newoutlen = 16; + status = CCCryptorFinal(enc->ctx, out + ivl + outlen, 16, &newoutlen); + outlen += newoutlen; + enc->end = 1; + } + } else { + out = malloc(32); + CCCryptorStatus status; + size_t avail = outlen; + status = CCCryptorFinal(enc->ctx, out, 32, &outlen); + enc->end = 1; + } + enc->tmp = (char*)out; + enc->tmplen = outlen + ivl; + enc->tmpoff = 0; + + if(enc->ivlen > 0) { + enc->ivlen = 0; + } + + free(in); + + return aes_read(buf, s, n, enc); +} + +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) { + if(origin != SEEK_SET || offset != 0 || !enc->seek) { + return CURL_SEEKFUNC_CANTSEEK; + } + + enc->ivlen = 16; + if(enc->seek(enc->stream, 0, SEEK_SET) != 0) { + return CURL_SEEKFUNC_FAIL; + } + return CURL_SEEKFUNC_OK; +} + +void aes_encrypter_close(AESEncrypter *enc) { + if(enc->tmp) { + free(enc->tmp); + } + if(enc->iv) { + free(enc->iv); + } + // TODO: cleanup cryptor + free(enc); +} + +char* aes_encrypt(const char *in, size_t len, DavKey *key) { + unsigned char iv[16]; + if(dav_rand_bytes(iv, 16)) { + return NULL; + } + + CCCryptorRef cryptor; + CCCryptorStatus status; + if(key->type == DAV_KEY_AES128) { + status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor); + } else if(key->type == DAV_KEY_AES256) { + status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor); + } else { + return NULL; + } + + if(status != kCCSuccess) { + return NULL; + } + + int buflen = len + 64; + char *buf = calloc(1, buflen); + memcpy(buf, iv, 16); + + int pos = 16; + size_t avail = buflen - 16; + size_t moved; + char *out = buf + 16; + + status = CCCryptorUpdate(cryptor, in, + len, out, avail, + &moved); + if(status != kCCSuccess) { + free(buf); + return NULL; + } + + pos += moved; + avail -= moved; + out += moved; + + status = CCCryptorFinal(cryptor, out, avail, &moved); + if(status != kCCSuccess) { + free(buf); + return NULL; + } + + pos += moved; + + char *b64enc = util_base64encode(buf, pos); + free(buf); + + return b64enc; +} + +char* aes_decrypt(const char *in, size_t *len, DavKey *key) { + int inlen; + unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen); + + CCCryptorRef cryptor; + CCCryptorStatus status; + if(key->type == DAV_KEY_AES128) { + status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor); + } else if(key->type == DAV_KEY_AES256) { + status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor); + } else { + free(buf); + return NULL; + } + + if(status != kCCSuccess) { + free(buf); + return NULL; + } + + char *out = malloc(inlen + 1); + size_t outavail = inlen; + size_t outlen = 0; + + unsigned char *inbuf = buf + 16; + inlen -= 16; + + size_t moved = 0; + status = CCCryptorUpdate(cryptor, inbuf, inlen, out, outavail, &moved); + if(status != kCCSuccess) { + free(buf); + free(out); + // TODO cryptor + return NULL; + } + + outlen += moved; + outavail -= moved; + + status = CCCryptorFinal(cryptor, out + outlen, outavail, &moved); + if(status != kCCSuccess) { + free(buf); + free(out); + // TODO cryptor + return NULL; + } + + outlen += moved; + out[outlen] = 0; + + *len = outlen; + return out; +} + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) { + CC_SHA256_Final(buf, sha256); +} + +char* dav_create_hash(const char *data, size_t len) { + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + CC_SHA256((const unsigned char*)data, len, hash); + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} + +DAV_SHA_CTX* dav_hash_init(void) { + DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX)); + CC_SHA256_Init(ctx); + return ctx; +} + +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) { + CC_SHA256_Update(ctx, data, len); +} + +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) { + CC_SHA256_Final(buf, ctx); + free(ctx); +} + +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) { + if(!password) { + return NULL; + } + size_t len = strlen(password); + if(len == 0) { + return NULL; + } + + // setup key data and length + unsigned char keydata[32]; + int keylen = 32; + switch(enc) { + case DAV_KEY_AES128: keylen = 16; break; + case DAV_KEY_AES256: keylen = 32; break; + default: return NULL; + } + + // generate key + switch(pwfunc) { + case DAV_PWFUNC_PBKDF2_SHA256: { + int result = CCKeyDerivationPBKDF( + kCCPBKDF2, + password, + len, + salt, + saltlen, + kCCPRFHmacAlgSHA256, + DAV_CRYPTO_ITERATION_COUNT, + keydata, + keylen); + if(result) { + return NULL; + } + break; + } + case DAV_PWFUNC_PBKDF2_SHA512: { + int result = CCKeyDerivationPBKDF( + kCCPBKDF2, + password, + len, + salt, + saltlen, + kCCPRFHmacAlgSHA512, + DAV_CRYPTO_ITERATION_COUNT, + keydata, + keylen); + if(result) { + return NULL; + } + break; + } + default: return NULL; + } + + // create DavKey with generated data + DavKey *key = malloc(sizeof(DavKey)); + key->data = malloc(keylen); + key->length = keylen; + key->name = NULL; + key->type = enc; + memcpy(key->data, keydata, keylen); + return key; +} + +#endif + +/* -------------------- Windows Crypto Functions -------------------- */ +#ifdef DAV_CRYPTO_CNG + +static void cng_cleanup(BCRYPT_ALG_HANDLE hAesAlg, BCRYPT_KEY_HANDLE hKey, BCRYPT_HASH_HANDLE hHash, void *pbObject) { + if(hAesAlg) { + BCryptCloseAlgorithmProvider(hAesAlg,0); + } + if(hKey) { + BCryptDestroyKey(hKey); + } + if(hHash) { + BCryptDestroyHash(hHash); + } + if(pbObject) { + free(pbObject); + } +} + +static int cng_init_key(BCRYPT_ALG_HANDLE *alg, BCRYPT_KEY_HANDLE *key, void **keyobj, DavKey *aesKey) { + BCRYPT_ALG_HANDLE hAesAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + + void *pbKeyObject = NULL; + ULONG keyObjectLength = 0; + + ULONG result = 0; + + // check DavKey and get AES key length + if(!aesKey) { + return 1; + } + + ULONG aesKeyLength = 0; + if(aesKey->type == DAV_KEY_AES128) { + aesKeyLength = 16; + } else if(aesKey->type == DAV_KEY_AES256) { + aesKeyLength = 32; + } + if(aesKeyLength > aesKey->length || !aesKey->data) { + // invalid DavKey + return 1; + } + + // initialize BCrypt stuff + if(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0)) { + fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n"); + return 1; + } + + if(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjectLength, sizeof(DWORD), &result, 0)) { + fprintf(stderr, "Error: BCrypt: Cannot get BCRYPT_OBJECT_LENGTH\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + if(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0)) { + fprintf(stderr, "Error: BCrypt: Cannot set CBC mode\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + pbKeyObject = calloc(1, keyObjectLength); + if(!pbKeyObject) { + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + // init key + if(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, keyObjectLength, aesKey->data, aesKeyLength, 0)) { + fprintf(stderr, "Error: BCrypt: Cannot set key\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + *alg = hAesAlg; + *key = hKey; + *keyobj = pbKeyObject; + + return 0; +} + +static int cng_hash_init(WinBCryptSHACTX *ctx) { + if(BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)) { + fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n"); + return 1; + } + + ULONG hashObjectLen; + ULONG result; + if(BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjectLen, sizeof(DWORD), &result, 0)) { + cng_cleanup(ctx->hAlg, NULL, NULL, NULL); + return 1; + } + + ctx->pbHashObject = calloc(1, hashObjectLen); + + if(BCryptCreateHash(ctx->hAlg, &ctx->hHash, ctx->pbHashObject, hashObjectLen, NULL, 0, 0)) { + cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject); + return 1; + } + + return 0; +} + + +int dav_rand_bytes(unsigned char *buf, size_t len) { + if(BCryptGenRandom(NULL, (unsigned char*)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) { + return 1; + } + return 0; +} + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) { + AESDecrypter *dec = calloc(1, sizeof(AESDecrypter)); + if(!dec) { + return NULL; + } + if(cng_hash_init(&dec->sha256)) { + free(dec); + return NULL; + } + + dec->stream = stream; + dec->write = write_func; + dec->key = key; + dec->init = 0; + dec->ivpos = 0; + + return dec; +} + +static void aes_decrypter_init(AESDecrypter *dec) { + if(cng_init_key(&dec->ctx.hAlg, &dec->ctx.hKey, &dec->ctx.pbKeyObject, dec->key)) { + fprintf(stderr, "Error: cng_init_key failed\n"); + exit(-1); + } + // copy iv + memcpy(dec->ctx.pbIV, dec->ivtmp, 16); +} + +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { + int len = s*n; + if(!dec->init) { + dec->init = 1; + + size_t n = 16 - dec->ivpos; + size_t cp = n > len ? len : n; + memcpy(dec->ivtmp + dec->ivpos, buf, cp); + dec->ivpos += cp; + if(dec->ivpos >= 16) { + aes_decrypter_init(dec); + } + if(len == cp) { + return len; + } else { + buf = (char*)buf + cp; + len -= cp; + } + } + + // the cipher text must be a multiply of 16 + // remaining bytes are stored in ctx.buf and must be added to cibuf + // the next time + size_t cbufalloc = len + 64; + ULONG clen = 0; + char *cbuf = malloc(cbufalloc); + + // add previous remaining bytes + if(dec->ctx.buflen > 0) { + memcpy(cbuf, dec->ctx.buf, dec->ctx.buflen); + clen = dec->ctx.buflen; + } + // add current bytes + memcpy(cbuf + clen, buf, len); + clen += len; + + // check if the message fits the blocksize + int remaining = clen % 16; + if(remaining == 0) { + // decrypt last block next time, or in aes_decrypter_shutdown + // this makes sure, that shutdown always decrypts the last block + // with BCRYPT_BLOCK_PADDING flag + remaining = 16; + } + + // add remaining bytes to ctx.buf for the next aes_write run + clen -= remaining; + memcpy(dec->ctx.buf, cbuf + clen, remaining); + dec->ctx.buflen = remaining; + + // ready to decrypt the message + ULONG outlen = clen + 32; + unsigned char *out = malloc(outlen); + + // decrypt + if(clen > 0) { + ULONG enc_len = 0; + ULONG status = BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &enc_len, 0); + if(status > 0) { + fprintf(stderr, "Error: BCryptDecrypt failed: 0x%X\n", status); + free(out); + free(cbuf); + return 0; + } + outlen = enc_len; + } + + // write decrypted data to the output stream and update the hash + dec->write(out, 1, outlen, dec->stream); + BCryptHashData(dec->sha256.hHash, out, outlen, 0); + + free(out); + free(cbuf); + + return (s*n) / s; +} + +void aes_decrypter_shutdown(AESDecrypter *dec) { + if(dec->init && dec->ctx.buflen > 0) { + ULONG outlen = 64; + char out[64]; + if(BCryptDecrypt(dec->ctx.hKey, dec->ctx.buf, dec->ctx.buflen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptDecrypt failed\n"); + return; + } + dec->write(out, 1, outlen, dec->stream); + BCryptHashData(dec->sha256.hHash, out, outlen, 0); + } +} + +void aes_decrypter_close(AESDecrypter *dec) { + cng_cleanup(dec->ctx.hAlg, dec->ctx.hKey, NULL, dec->ctx.pbKeyObject); + cng_cleanup(dec->sha256.hAlg, NULL, dec->sha256.hHash, dec->sha256.pbHashObject); + free(dec); +} + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) { + unsigned char *iv = malloc(16); + if(dav_rand_bytes(iv, 16)) { + free(iv); + return NULL; + } + + AESEncrypter *enc = calloc(1, sizeof(AESEncrypter)); + if(cng_hash_init(&enc->sha256)) { + free(iv); + free(enc); + return NULL; + } + + enc->stream = stream; + enc->read = read_func; + enc->seek = seek_func; + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + enc->end = 0; + enc->iv = iv; + enc->ivlen = 0; + + if(cng_init_key(&enc->ctx.hAlg, &enc->ctx.hKey, &enc->ctx.pbKeyObject, key)) { + fprintf(stderr, "Error: cng_init_key failed\n"); + exit(-1); + } + + enc->ctx.buflen = 0; + memcpy(enc->ctx.pbIV, iv, 16); + + return enc; +} + +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { + size_t len = s*n; + size_t nread = 0; + + if(enc->tmp) { + // the temp buffer contains bytes that are already encrypted, but + // the last aes_read had not enough read buffer space + + // in case we have a tmp buf, we just return this + size_t tmp_diff = enc->tmplen - enc->tmpoff; + size_t cp_len = tmp_diff > len ? len : tmp_diff; + memcpy(buf, enc->tmp + enc->tmpoff, cp_len); + enc->tmpoff += cp_len; + if(enc->tmpoff >= enc->tmplen) { + free(enc->tmp); + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + } + return cp_len / s; + } + + if(enc->ivlen < 16) { + size_t copy_iv_len = 16 - enc->ivlen; + copy_iv_len = len > copy_iv_len ? copy_iv_len : len; + + memcpy(buf, enc->iv, copy_iv_len); + buf += copy_iv_len; + len -= copy_iv_len; + nread = copy_iv_len; + + enc->ivlen += copy_iv_len; + + if(len == 0) { + return copy_iv_len / s; + } + } + + if(enc->end) { + return 0; + } + + size_t remaining = len % 16; + len -= remaining; + + if(len > 256) { + len -= 16; // optimization for avoiding tmp buffer usage + } + + size_t inalloc = len; + ULONG inlen = 0; + unsigned char *in = malloc(inalloc); + + // fill the input buffer + while(inlen < inalloc) { + size_t r = enc->read(in + inlen, 1, inalloc - inlen, enc->stream); + if(r == 0) { + enc->end = 1; + break; + } + inlen += r; + } + + if(inlen == 0) { + return nread / s; + } + + // hash read data + BCryptHashData(enc->sha256.hHash, in, inlen, 0); + + // create output buffer + ULONG outalloc = inlen + 16; + ULONG outlen = 0; + char *out = malloc(outalloc); + + // encrypt + int flags = 0; + if(inlen % 16 != 0) { + enc->end = 1; + } + if(enc->end) { + flags = BCRYPT_BLOCK_PADDING; + } + if(BCryptEncrypt(enc->ctx.hKey, in, inlen, NULL, enc->ctx.pbIV, 16, out, outalloc, &outlen, flags)) { + fprintf(stderr, "Error: BCryptEncrypt failed\n"); + } + + // check if the output fits in buf, if not, save the remaining bytes in tmp + if(outlen > len) { + size_t tmplen = outlen - len; + char *tmp = malloc(tmplen); + memcpy(tmp, out+len, tmplen); + + enc->tmp = tmp; + enc->tmplen = tmplen; + enc->tmpoff = 0; + + outlen = len; + } + + // fill read buffer and return + memcpy(buf, out, outlen); + nread += outlen; + + free(in); + free(out); + + return nread / s; +} + +void aes_encrypter_close(AESEncrypter *enc) { + enc->end = 1; +} + +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) { + if(origin != SEEK_SET || offset != 0 || !enc->seek) { + return CURL_SEEKFUNC_CANTSEEK; + } + + enc->ivlen = 0; + memcpy(enc->ctx.pbIV, enc->iv, 16); + if(enc->seek(enc->stream, 0, SEEK_SET) != 0) { + return CURL_SEEKFUNC_FAIL; + } + return CURL_SEEKFUNC_OK; +} + +char* aes_encrypt(const char *in, size_t len, DavKey *key) { + // create random IV + char iv[16]; + if(dav_rand_bytes(iv, 16)) { + return NULL; + } + + // initialize bcrypt stuff + BCRYPT_ALG_HANDLE hAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + void *pbKeyObject = NULL; + if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) { + return NULL; + } + + // create output buffer + ULONG outlen = len + 128; + char *out = malloc(outlen); + + // the output must start with the IV + memcpy(out, iv, 16); + char *encbuf = out + 16; + ULONG enclen = outlen - 16; + ULONG encoutlen = 0; + + // encrypt + if(BCryptEncrypt(hKey, (PUCHAR)in, len, NULL, (PUCHAR)iv, 16, encbuf, enclen, &encoutlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptEncrypt failed\n"); + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + return NULL; + } + + outlen = encoutlen + 16; // length of encrypted data + 16 bytes IV + + // base64 encode + char *outstr = util_base64encode(out, outlen); + + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + + return outstr; +} + +char* aes_decrypt(const char *in, size_t *len, DavKey *key) { + BCRYPT_ALG_HANDLE hAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + void *pbKeyObject = NULL; + if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) { + return NULL; + } + + int inlen; + unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen); + if(inlen < 16 || !buf) { + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + if(buf) { + free(buf); + } + return NULL; + } + + // encrypted data starts with IV + char iv[16]; + memcpy(iv, buf, 16); + + // decrypt data + char *data = buf + 16; // encrypted data starts after IV + size_t datalen = inlen - 16; + + // create output buffer + ULONG outlen = inlen; + char *out = malloc(outlen + 1); + + // decrypt + if(BCryptDecrypt(hKey, data, datalen, NULL, iv, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) { + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + free(buf); + return NULL; + } + + // decrypt finished, return + out[outlen] = 0; + *len = (size_t)outlen; + return out; +} + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) { + BCryptFinishHash(sha256->hHash, buf, DAV_SHA256_DIGEST_LENGTH, 0); +} + + +char* dav_create_hash(const char *data, size_t len) { + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + DAV_SHA_CTX *ctx = dav_hash_init(); + if(ctx) { + dav_hash_update(ctx, data, len); + dav_hash_final(ctx, hash); + } + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} + +DAV_SHA_CTX* dav_hash_init(void) { + DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX)); + if(!ctx) { + return NULL; + } + if(cng_hash_init(ctx)) { + free(ctx); + return NULL; + } + return ctx; +} + +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) { + BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0); +} + +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) { + BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0); + + // cleanup + cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject); + free(ctx); +} + +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) { + if(!password) { + return NULL; + } + size_t len = strlen(password); + if(len == 0) { + return NULL; + } + + // setup key data and length + unsigned char keydata[128]; + int keylen = 32; + switch(enc) { + case DAV_KEY_AES128: keylen = 16; break; + case DAV_KEY_AES256: keylen = 32; break; + default: return NULL; + } + + LPCWSTR algid; + switch(pwfunc) { + case DAV_PWFUNC_PBKDF2_SHA256: algid = BCRYPT_SHA256_ALGORITHM; break; + case DAV_PWFUNC_PBKDF2_SHA512: algid = BCRYPT_SHA512_ALGORITHM; break; + default: return NULL; + } + + // open algorithm provider + BCRYPT_ALG_HANDLE hAlg; + ULONG status = BCryptOpenAlgorithmProvider(&hAlg, algid, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG); + if(status > 0) { + fprintf(stderr, "Error: dav_pw2key: BCryptOpenAlgorithmProvider failed: 0x%X\n", (unsigned int)status); + return NULL; + } + + // derive key + status = BCryptDeriveKeyPBKDF2( + hAlg, + (PUCHAR)password, + len, + (PUCHAR)salt, + saltlen, + DAV_CRYPTO_ITERATION_COUNT, + keydata, + 128, + 0); + + BCryptCloseAlgorithmProvider(hAlg,0); + + if(status) { + fprintf(stderr, "Error: dav_pw2key: BCryptDeriveKeyPBKDF2 failed: 0x%X\n", (unsigned int)status); + return NULL; + } + + // create DavKey with generated data + DavKey *key = malloc(sizeof(DavKey)); + key->data = malloc(keylen); + key->length = keylen; + key->name = NULL; + key->type = enc; + memcpy(key->data, keydata, keylen); + return key; +} +#endif + + + +UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) { + UcxBuffer *encbuf = ucx_buffer_new( + NULL, + in->size+16, + UCX_BUFFER_AUTOEXTEND); + + AESEncrypter *enc = aes_encrypter_new( + key, + in, + (dav_read_func)ucx_buffer_read, + NULL); + if(!enc) { + ucx_buffer_free(encbuf); + return NULL; + } + + char buf[1024]; + size_t r; + while((r = aes_read(buf, 1, 1024, enc)) > 0) { + ucx_buffer_write(buf, 1, r, encbuf); + } + aes_encrypter_close(enc); + + encbuf->pos = 0; + return encbuf; +} + +UcxBuffer* aes_decrypt_buffer(UcxBuffer *in, DavKey *key) { + UcxBuffer *decbuf = ucx_buffer_new( + NULL, + in->size, + UCX_BUFFER_AUTOEXTEND); + AESDecrypter *dec = aes_decrypter_new( + key, + decbuf, + (dav_write_func)ucx_buffer_write); + + aes_write(in->space, 1, in->size, dec); + aes_decrypter_shutdown(dec); + aes_decrypter_close(dec); + decbuf->pos = 0; + return decbuf; +} diff --git a/libidav/crypto.h b/libidav/crypto.h new file mode 100644 index 0000000..7ea6597 --- /dev/null +++ b/libidav/crypto.h @@ -0,0 +1,166 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAV_CRYPTO_H +#define DAV_CRYPTO_H + +#include "webdav.h" +#include + +#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 +#include + +#elif defined(_WIN32) + +#define DAV_CRYPTO_CNG + +#include +#include + +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 +#include + +#if defined(__sun) && defined(__SunOS_5_10) +#include +#define SHA256_Init SHA256Init +#define SHA256_Update SHA256Update +#define SHA256_Final SHA256Final +#else +#include +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define DAV_PWFUNC_PBKDF2_SHA256 0 +#define DAV_PWFUNC_PBKDF2_SHA512 1 + +#define DAV_CRYPTO_ITERATION_COUNT 4000 + +typedef struct { + DAV_AES_CTX ctx; + DAV_SHA_CTX sha256; + void *stream; + dav_write_func write; + DavKey *key; + int init; + unsigned char ivtmp[16]; + size_t ivpos; +} AESDecrypter; + +typedef struct { + DAV_AES_CTX ctx; + DAV_SHA_CTX sha256; + void *iv; + size_t ivlen; + void *stream; + dav_read_func read; + dav_seek_func seek; + char *tmp; + size_t tmplen; + size_t tmpoff; + int end; +} AESEncrypter; + +typedef struct DavHashContext DavHashContext; + +int dav_rand_bytes(unsigned char *buf, size_t len); + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func); +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec); +void aes_decrypter_shutdown(AESDecrypter *dec); +void aes_decrypter_close(AESDecrypter *dec); + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func); +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc); +void aes_encrypter_close(AESEncrypter *enc); +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin); + +char* aes_encrypt(const char *in, size_t len, DavKey *key); +char* aes_decrypt(const char *in, size_t *len, DavKey *key); + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf); + +char* dav_create_hash(const char *data, size_t len); + +DAV_SHA_CTX* dav_hash_init(void); +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len); +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf); + +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc); + +UcxBuffer* aes_encrypt_buffer(UcxBuffer *buf, DavKey *key); +UcxBuffer* aes_decrypt_buffer(UcxBuffer *buf, DavKey *key); + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_CRYPTO_H */ + diff --git a/libidav/davqlexec.c b/libidav/davqlexec.c new file mode 100644 index 0000000..9eb6ec5 --- /dev/null +++ b/libidav/davqlexec.c @@ -0,0 +1,1466 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#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;ispace, 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;iname); + obj.data.string = res->name; + break; + } + case DAVQL_RES_PATH: { + obj.type = 1; + obj.length = strlen(res->path); + obj.data.string = res->path; + break; + } + case DAVQL_RES_HREF: { + obj.type = 1; + obj.length = strlen(res->href); + obj.data.string = res->href; + break; + } + case DAVQL_RES_CONTENTLENGTH: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->contentlength; + break; + } + case DAVQL_RES_CONTENTTYPE: { + obj.type = 1; + obj.length = strlen(res->contenttype); + obj.data.string = res->contenttype; + break; + } + case DAVQL_RES_CREATIONDATE: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->creationdate; + break; + } + case DAVQL_RES_LASTMODIFIED: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->lastmodified; + break; + } + case DAVQL_RES_ISCOLLECTION: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->iscollection; + break; + } + } + DAVQL_PUSH(obj); + break; + } + case DAVQL_CMD_PROP_IDENTIFIER: { + //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name); + //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name); + DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name); + if(dav_xml_isstring(value)) { + obj.type = 1; + obj.length = (uint32_t)value->contentlength; + obj.data.string = value->content; + } else { + obj.type = 2; + obj.length = 0; + obj.data.node = value; + } + DAVQL_PUSH(obj); + break; + } + //case DAVQL_CMD_OP_UNARY_ADD: { + // printf("uadd\n"); + // break; + //} + case DAVQL_CMD_OP_UNARY_SUB: { + //printf("usub\n"); + obj = DAVQL_POP(); + if(obj.type == 0) { + obj.data.integer = -obj.data.integer; + DAVQL_PUSH(obj); + } else { + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_UNARY_NEG: { + //printf("uneg\n"); + obj = DAVQL_POP(); + if(obj.type == 0) { + obj.data.integer = obj.data.integer == 0 ? 1 : 0; + DAVQL_PUSH(obj); + } else { + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_ADD: { + //printf("add\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer + obj2.data.integer); + } else { + // TODO: string concat + } + break; + } + case DAVQL_CMD_OP_BINARY_SUB: { + //printf("sub\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer - obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_MUL: { + //printf("mul\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer * obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_DIV: { + //printf("div\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer / obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_AND: { + //printf("and\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer & obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_OR: { + //printf("or\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer | obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_XOR: { + //printf("xor\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer ^ obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_LOGICAL_NOT: { + //printf("not\n"); + break; + } + case DAVQL_CMD_OP_LOGICAL_AND: { + //printf("land\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(v1 && v2); + break; + } + case DAVQL_CMD_OP_LOGICAL_OR_L: { + //printf("or_l %d\n", cmd.data.integer); + DavQLStackObj obj1 = stack[stpos]; + if((obj1.type == 0 && obj1.data.integer) || (obj1.type == 1 && obj1.data.string)) { + stpos--; + DAVQL_PUSH_INT(1); + i += cmd.data.integer; // jump, skip right subtree of 'or' + } + break; + } + case DAVQL_CMD_OP_LOGICAL_OR: { + //printf("or\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(v1 || v2); + break; + } + case DAVQL_CMD_OP_LOGICAL_XOR: { + //printf("lxor\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(!v1 != !v2); + break; + } + case DAVQL_CMD_OP_EQ: { + //printf("eq\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer == obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_NEQ: { + //printf("neq\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer != obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LT: { + //printf("lt\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer < obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_GT: { + //printf("gt\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer > obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LE: { + //printf("le\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer <= obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_GE: { + //printf("ge\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer >= obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LIKE: { + //printf("like\n"); + break; + } + case DAVQL_CMD_OP_UNLIKE: { + //printf("unlike\n"); + break; + } + case DAVQL_CMD_CALL: { + //printf("call %x\n", cmd.data.func); + break; + } + } + } + + if(stpos == 1) { + *result = stack[0]; + } else { + ret = -1; + } + free(stack); + + return ret; +} diff --git a/libidav/davqlexec.h b/libidav/davqlexec.h new file mode 100644 index 0000000..11a6258 --- /dev/null +++ b/libidav/davqlexec.h @@ -0,0 +1,186 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef DAVQLEXEC_H +#define DAVQLEXEC_H + +#include +#include "davqlparser.h" +#include "webdav.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavQLCmd DavQLCmd; +typedef struct DavQLStackObj DavQLStackObj; +typedef struct DavQLRes DavQLRes; + +typedef struct DavQLArg DavQLArg; +typedef struct DavQLArgList DavQLArgList; + +typedef void*(*davql_func)(); // TODO: interface? + +struct DavQLArg { + int type; + union DavQLArgValue{ + int d; + unsigned int u; + char *s; + time_t t; + } value; + DavQLArg *next; +}; + +struct DavQLArgList { + DavQLArg *first; + DavQLArg *current; +}; + +typedef enum { + DAVQL_OK = 0, + DAVQL_UNSUPPORTED_FORMATCHAR, + DAVQL_UNKNOWN_FORMATCHAR +} davqlerror_t; + +typedef enum { + DAVQL_CMD_INT = 0, + DAVQL_CMD_STRING, + DAVQL_CMD_TIMESTAMP, + DAVQL_CMD_RES_IDENTIFIER, + DAVQL_CMD_PROP_IDENTIFIER, + //DAVQL_CMD_OP_UNARY_ADD, + DAVQL_CMD_OP_UNARY_SUB, + DAVQL_CMD_OP_UNARY_NEG, + DAVQL_CMD_OP_BINARY_ADD, + DAVQL_CMD_OP_BINARY_SUB, + DAVQL_CMD_OP_BINARY_MUL, + DAVQL_CMD_OP_BINARY_DIV, + DAVQL_CMD_OP_BINARY_AND, + DAVQL_CMD_OP_BINARY_OR, + DAVQL_CMD_OP_BINARY_XOR, + DAVQL_CMD_OP_LOGICAL_NOT, + DAVQL_CMD_OP_LOGICAL_AND, + DAVQL_CMD_OP_LOGICAL_OR_L, + DAVQL_CMD_OP_LOGICAL_OR, + DAVQL_CMD_OP_LOGICAL_XOR, + DAVQL_CMD_OP_EQ, + DAVQL_CMD_OP_NEQ, + DAVQL_CMD_OP_LT, + DAVQL_CMD_OP_GT, + DAVQL_CMD_OP_LE, + DAVQL_CMD_OP_GE, + DAVQL_CMD_OP_LIKE, + DAVQL_CMD_OP_UNLIKE, + DAVQL_CMD_CALL +} davqlcmdtype_t; + +typedef enum { + DAVQL_RES_NAME = 0, + DAVQL_RES_PATH, + DAVQL_RES_HREF, + DAVQL_RES_CONTENTLENGTH, + DAVQL_RES_CONTENTTYPE, + DAVQL_RES_CREATIONDATE, + DAVQL_RES_LASTMODIFIED, + DAVQL_RES_ISCOLLECTION +} davqlresprop_t; + +struct DavQLCmd { + davqlcmdtype_t type; + union DavQLCmdData { + int64_t integer; + sstr_t string; + time_t timestamp; + davqlresprop_t resprop; + DavPropName property; + davql_func func; + } data; +}; + +struct DavQLStackObj { + int32_t type; // 0: int, 1: string, 2: xml + uint32_t length; + union DavQLStackData { + int64_t integer; + char *string; + DavXmlNode *node; + } data; +}; + +struct DavQLRes { + DavResource *resource; + int depth; +}; + +typedef struct DavCompiledField { + char *ns; + char *name; + UcxBuffer *code; +} DavCompiledField; + +typedef struct DavOrderCriterion { + int type; // 0: resprop, 1: property + union DavQLColumn { + davqlresprop_t resprop; + UcxKey property; + } column; + _Bool descending; +} DavOrderCriterion; + +DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap); +void dav_ql_free_arglist(DavQLArgList *args); + +int dav_ql_getarg_int(DavQLArgList *args); +unsigned int dav_ql_getarg_uint(DavQLArgList *args); +char* dav_ql_getarg_str(DavQLArgList *args); +time_t dav_ql_getarg_time(DavQLArgList *args); + +DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...); +DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap); + +UcxBuffer* dav_path_string(sstr_t src, DavQLArgList *args, davqlerror_t *error); +sstr_t dav_format_string(UcxAllocator *a, sstr_t fstr, DavQLArgList *ap, davqlerror_t *error); + +DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap); + +int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop); + +UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap); + +int dav_exec_expr(UcxBuffer *bcode, DavResource *res, DavQLStackObj *result); + + + +#ifdef __cplusplus +} +#endif + +#endif /* DAVQLEXEC_H */ + diff --git a/libidav/davqlparser.c b/libidav/davqlparser.c new file mode 100644 index 0000000..38f5694 --- /dev/null +++ b/libidav/davqlparser.c @@ -0,0 +1,1817 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "davqlparser.h" +#include +#include +#include +#include + +#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 ') " _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;iargs = ucx_list_append( + stmt->args, + (void*)(intptr_t)c); + } + placeholder = 0; + } else if (c == '%') { + placeholder = 1; + } + } +} + +static int dav_parse_literal(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + expr->srctext = token_sstr(token); + if (token_is(token, DAVQL_TOKEN_NUMBER)) { + expr->type = DAVQL_NUMBER; + } else if (token_is(token, DAVQL_TOKEN_STRING)) { + expr->type = DAVQL_STRING; + // check for format specifiers and add args + dav_add_fmt_args(stmt, expr->srctext); + } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) { + expr->type = DAVQL_TIMESTAMP; + } else if (token_is(token, DAVQL_TOKEN_FMTSPEC) + && expr->srctext.length == 2) { + switch (expr->srctext.ptr[1]) { + case 'd': expr->type = DAVQL_NUMBER; break; + case 's': expr->type = DAVQL_STRING; break; + case 't': expr->type = DAVQL_TIMESTAMP; break; + default: + dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC, + _error_invalid_fmtspec, stmt, token); + return 0; + } + // add fmtspec type to query arg list + stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)expr->srctext.ptr[1]); + } else { + return 0; + } + + return 1; +} + +// forward declaration +static int dav_parse_expression(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr); + +static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + expr->srctext.ptr = token_sstr(token).ptr; + expr->srctext.length = 0; + expr->left = expr->right = NULL; // in case we fail, we want them to be sane + + int total_consumed = 0; + + // RULE: Expression, {",", Expression}; + DavQLExpression *arglist = expr; + DavQLExpression arg; + char *lastchar = expr->srctext.ptr; + int consumed; + do { + memset(&arg, 0, sizeof(DavQLExpression)); + consumed = dav_parse_expression(stmt, token, &arg); + if (consumed) { + lastchar = arg.srctext.ptr + arg.srctext.length; + total_consumed += consumed; + token = ucx_list_get(token, consumed); + // look ahead for a comma + if (token_is(token, DAVQL_TOKEN_COMMA)) { + total_consumed++; + token = token->next; + /* we have more arguments, so put the current argument to the + * left subtree and create a new node to the right + */ + dqlsec_malloc(stmt, arglist->left, DavQLExpression); + memcpy(arglist->left, &arg, sizeof(DavQLExpression)); + arglist->srctext.ptr = arg.srctext.ptr; + arglist->op = DAVQL_ARGLIST; + arglist->type = DAVQL_FUNCCALL; + dqlsec_mallocz(stmt, arglist->right, DavQLExpression); + arglist = arglist->right; + } else { + // this was the last argument, so write it to the current node + memcpy(arglist, &arg, sizeof(DavQLExpression)); + consumed = 0; + } + } + } while (consumed && !stmt->errorcode); + + // recover source text + arglist = expr; + while (arglist && arglist->type == DAVQL_FUNCCALL) { + arglist->srctext.length = lastchar - arglist->srctext.ptr; + arglist = arglist->right; + } + + return total_consumed; +} + +static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + // RULE: Identifier, "(", ArgumentList, ")"; + if (token_is(token, DAVQL_TOKEN_IDENTIFIER) && + token_is(token->next, DAVQL_TOKEN_OPENP)) { + + expr->type = DAVQL_FUNCCALL; + expr->op = DAVQL_CALL; + + dqlsec_mallocz(stmt, expr->left, DavQLExpression); + expr->left->type = DAVQL_IDENTIFIER; + expr->left->srctext = token_sstr(token); + expr->right = NULL; + + token = token->next->next; + + DavQLExpression arg; + int argtokens = dav_parse_arglist(stmt, token, &arg); + if (stmt->errorcode) { + // if an error occurred while parsing the arglist, return now + return 2; + } + if (argtokens) { + token = ucx_list_get(token, argtokens); + dqlsec_malloc(stmt, expr->right, DavQLExpression); + memcpy(expr->right, &arg, sizeof(DavQLExpression)); + } else { + // arg list may be empty + expr->right = NULL; + } + + if (token_is(token, DAVQL_TOKEN_CLOSEP)) { + return 3 + argtokens; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par, + stmt, token); + return 2; // it MUST be a function call, but it is invalid + } + } else { + return 0; + } +} + +static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + UcxList *firsttoken = token; // save for srctext recovery + + DavQLExpression* atom = expr; + int total_consumed = 0; + + // optional unary operator + if (token_is(token, DAVQL_TOKEN_OPERATOR)) { + char *op = strchr("+-~", token_sstr(token).ptr[0]); + if (op) { + expr->type = DAVQL_UNARY; + switch (*op) { + case '+': expr->op = DAVQL_ADD; break; + case '-': expr->op = DAVQL_SUB; break; + case '~': expr->op = DAVQL_NEG; break; + } + dqlsec_mallocz(stmt, expr->left, DavQLExpression); + atom = expr->left; + total_consumed++; + token = token->next; + } else { + dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP, + _error_invalid_unary_op, stmt, token); + return 0; + } + } + + // RULE: (ParExpression | AtomicExpression) + if (token_is(token, DAVQL_TOKEN_OPENP)) { + token = token->next; total_consumed++; + // RULE: "(", Expression, ")" + int consumed = dav_parse_expression(stmt, token, atom); + if (stmt->errorcode) { + return 0; + } + if (!consumed) { + dav_error_in_context(DAVQL_ERROR_INVALID_EXPR, + _error_invalid_expr, stmt, token); + return 0; + } + token = ucx_list_get(token, consumed); + total_consumed += consumed; + if (token_is(token, DAVQL_TOKEN_CLOSEP)) { + token = token->next; total_consumed++; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_PAR, + _error_missing_par, stmt, token); + return 0; + } + } else { + // RULE: FunctionCall + int consumed = dav_parse_funccall(stmt, token, atom); + if (consumed) { + total_consumed += consumed; + } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) { + // RULE: Identifier + total_consumed++; + atom->type = DAVQL_IDENTIFIER; + atom->srctext = token_sstr(token); + } else { + // RULE: Literal + total_consumed += dav_parse_literal(stmt, token, atom); + } + } + + // recover source text + expr->srctext.ptr = token_sstr(firsttoken).ptr; + if (total_consumed > 0) { + sstr_t lasttoken = + token_sstr(ucx_list_get(firsttoken, total_consumed-1)); + expr->srctext.length = + lasttoken.ptr - expr->srctext.ptr + lasttoken.length; + } else { + // the expression should not be used anyway, but we want to be safe + expr->srctext.length = 0; + } + + + return total_consumed; +} + +static int dav_parse_bitexpr(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + return dav_parse_binary_expr(stmt, token, expr, + dav_parse_unary_expr, + "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR}, + dav_parse_bitexpr); +} + +static int dav_parse_multexpr(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + return dav_parse_binary_expr(stmt, token, expr, + dav_parse_bitexpr, + "*/", (int[]){DAVQL_MUL, DAVQL_DIV}, + dav_parse_multexpr); +} + +static int dav_parse_expression(DavQLStatement* stmt, UcxList* token, + DavQLExpression* expr) { + + return dav_parse_binary_expr(stmt, token, expr, + dav_parse_multexpr, + "+-", (int[]){DAVQL_ADD, DAVQL_SUB}, + dav_parse_expression); +} + +static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token, + DavQLField *field) { + int total_consumed = 0, consumed; + + // RULE: Expression, " as ", Identifier; + DavQLExpression *expr; + dqlsec_mallocz(stmt, expr, DavQLExpression); + consumed = dav_parse_expression(stmt, token, expr); + if (stmt->errorcode) { + dav_free_expression(expr); + return 0; + } + if (expr->type == DAVQL_UNDEFINED_TYPE) { + dav_free_expression(expr); + dav_error_in_context(DAVQL_ERROR_INVALID_EXPR, + _error_invalid_expr, stmt, token); + return 0; + } + + token = ucx_list_get(token, consumed); + total_consumed += consumed; + + if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) { + token = token->next; total_consumed++; + } else { + dav_free_expression(expr); + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_as, stmt, token); + return 0; + } + + if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) { + field->name = token_sstr(token); + field->expr = expr; + return total_consumed + 1; + } else { + dav_free_expression(expr); + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_identifier, stmt, token); + return 0; + } +} + +static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { + + // RULE: "-" + if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) { + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + dqlsec_list_append_or_free(stmt, stmt->fields, field); + dqlsec_mallocz(stmt, field->expr, DavQLExpression); + field->expr->type = DAVQL_IDENTIFIER; + field->expr->srctext = field->name = token_sstr(token); + return 1; + } + + // RULE: "*", {",", NamedExpression} + if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) { + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + dqlsec_list_append_or_free(stmt, stmt->fields, field); + dqlsec_mallocz(stmt, field->expr, DavQLExpression); + field->expr->type = DAVQL_IDENTIFIER; + field->expr->srctext = field->name = token_sstr(token); + + int total_consumed = 0; + int consumed = 1; + + do { + token = ucx_list_get(token, consumed); + total_consumed += consumed; + + if (token_is(token, DAVQL_TOKEN_COMMA)) { + total_consumed++; token = token->next; + DavQLField localfield; + consumed = dav_parse_named_field(stmt, token, &localfield); + if (!stmt->errorcode && consumed) { + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + memcpy(field, &localfield, sizeof(DavQLField)); + dqlsec_list_append_or_free(stmt, stmt->fields, field); + } + } else { + consumed = 0; + } + } while (consumed > 0); + + return total_consumed; + } + + // RULE: FieldExpression, {",", FieldExpression} + { + int total_consumed = 0, consumed; + do { + // RULE: NamedField | Identifier + DavQLField localfield; + consumed = dav_parse_named_field(stmt, token, &localfield); + if (consumed) { + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + memcpy(field, &localfield, sizeof(DavQLField)); + dqlsec_list_append_or_free(stmt, stmt->fields, field); + token = ucx_list_get(token, consumed); + total_consumed += consumed; + } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER) + // look ahead, if the field is JUST the identifier + && (token_is(token->next, DAVQL_TOKEN_COMMA) || + tokenvalue_is(token->next, "from"))) { + + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + dqlsec_mallocz(stmt, field->expr, DavQLExpression); + field->expr->type = DAVQL_IDENTIFIER; + field->expr->srctext = field->name = token_sstr(token); + dqlsec_list_append_or_free(stmt, stmt->fields, field); + + consumed = 1; + total_consumed++; + token = token->next; + + // we found a valid solution, so erase any errors + stmt->errorcode = 0; + if (stmt->errormessage) { + free(stmt->errormessage); + stmt->errormessage = NULL; + } + } else { + // dav_parse_named_field has already thrown a good error + consumed = 0; + } + + // field has been parsed, now try to get a comma + if (consumed) { + consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0; + if (consumed) { + token = token->next; + total_consumed++; + } + } + } while (consumed); + + return total_consumed; + } +} + +// forward declaration +static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, + DavQLExpression *expr); + +static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token, + DavQLExpression *expr) { + + expr->type = DAVQL_LOGICAL; + expr->srctext = token_sstr(token); + + int total_consumed = 0; + + DavQLExpression bexpr; + memset(&bexpr, 0, sizeof(DavQLExpression)); + total_consumed = dav_parse_expression(stmt, token, &bexpr); + if (!total_consumed || stmt->errorcode) { + return 0; + } + token = ucx_list_get(token, total_consumed); + + UcxList* optok = token; + // RULE: Expression, (" like " | " unlike "), String + if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok, + "like") || tokenvalue_is(optok, "unlike"))) { + + total_consumed++; + token = token->next; + if (token_is(token, DAVQL_TOKEN_STRING)) { + expr->op = tokenvalue_is(optok, "like") ? + DAVQL_LIKE : DAVQL_UNLIKE; + dqlsec_malloc(stmt, expr->left, DavQLExpression); + memcpy(expr->left, &bexpr, sizeof(DavQLExpression)); + dqlsec_mallocz(stmt, expr->right, DavQLExpression); + expr->right->type = DAVQL_STRING; + expr->right->srctext = token_sstr(token); + expr->srctext.length = expr->right->srctext.ptr - + expr->srctext.ptr + expr->right->srctext.length; + + // fmt args + dav_add_fmt_args(stmt, expr->right->srctext); + + return total_consumed + 1; + } else { + dav_error_in_context(DAVQL_ERROR_INVALID_STRING, + _error_invalid_string, stmt, token); + return 0; + } + } + // RULE: Expression, Comparison, Expression + else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && ( + tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") || + tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) { + + total_consumed++; + token = token->next; + + if (tokenvalue_is(optok, "=")) { + expr->op = DAVQL_EQ; + } else { + if (tokenvalue_is(token, "=")) { + if (tokenvalue_is(optok, "!")) { + expr->op = DAVQL_NEQ; + } else if (tokenvalue_is(optok, "<")) { + expr->op = DAVQL_LE; + } else if (tokenvalue_is(optok, ">")) { + expr->op = DAVQL_GE; + } + total_consumed++; + token = token->next; + } else { + if (tokenvalue_is(optok, "<")) { + expr->op = DAVQL_LT; + } else if (tokenvalue_is(optok, ">")) { + expr->op = DAVQL_GT; + } + } + } + + DavQLExpression rexpr; + memset(&rexpr, 0, sizeof(DavQLExpression)); + int consumed = dav_parse_expression(stmt, token, &rexpr); + if (stmt->errorcode) { + return 0; + } + if (!consumed) { + dav_error_in_context( + DAVQL_ERROR_MISSING_EXPR, _error_missing_expr, + stmt, token); + return 0; + } + + total_consumed += consumed; + dqlsec_malloc(stmt, expr->left, DavQLExpression); + memcpy(expr->left, &bexpr, sizeof(DavQLExpression)); + dqlsec_malloc(stmt, expr->right, DavQLExpression); + memcpy(expr->right, &rexpr, sizeof(DavQLExpression)); + + expr->srctext.length = expr->right->srctext.ptr - + expr->srctext.ptr + expr->right->srctext.length; + + return total_consumed; + } + // RULE: FunctionCall | Identifier; + else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) { + memcpy(expr, &bexpr, sizeof(DavQLExpression)); + + return total_consumed; + } else { + return 0; + } +} + +static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token, + DavQLExpression *expr) { + + // RULE: "not ", LogicalExpression + if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) { + expr->type = DAVQL_LOGICAL; + expr->op = DAVQL_NOT; + dqlsec_mallocz(stmt, expr->left, DavQLExpression); + expr->srctext = token_sstr(token); + + token = token->next; + int consumed = dav_parse_bool_expr(stmt, token, expr->left); + if (stmt->errorcode) { + return 0; + } + if (consumed) { + sstr_t lasttok = token_sstr(ucx_list_get(token, consumed-1)); + expr->srctext.length = + lasttok.ptr - expr->srctext.ptr + lasttok.length; + return consumed + 1; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, + _error_missing_expr, stmt, token); + return 0; + } + } + // RULE: "(", LogicalExpression, ")" + else if (token_is(token, DAVQL_TOKEN_OPENP)) { + int consumed = dav_parse_logical_expr(stmt, token->next, expr); + if (consumed) { + token = ucx_list_get(token->next, consumed); + + if (token_is(token, DAVQL_TOKEN_CLOSEP)) { + token = token->next; + return consumed + 2; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par, + stmt, token); + return 0; + } + } else { + // don't handle errors here, we can also try a boolean primary + stmt->errorcode = 0; + if (stmt->errormessage) { + free(stmt->errormessage); + } + } + } + + // RULE: BooleanPrimary + return dav_parse_bool_prim(stmt, token, expr); +} + +static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, + DavQLExpression *expr) { + + UcxList *firsttoken = token; + int total_consumed = 0; + + // RULE: BooleanLiteral, [LogicalOperator, LogicalExpression]; + DavQLExpression left, right; + memset(&left, 0, sizeof(DavQLExpression)); + int consumed = dav_parse_bool_expr(stmt, token, &left); + if (stmt->errorcode) { + return 0; + } + if (!consumed) { + dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, + _error_missing_expr, stmt, token); + return 0; + } + total_consumed += consumed; + token = ucx_list_get(token, consumed); + + if (token_is(token, DAVQL_TOKEN_OPERATOR)) { + expr->type = DAVQL_LOGICAL; + + davqloperator_t op = DAVQL_NOOP; + if (tokenvalue_is(token, "and")) { + op = DAVQL_LAND; + } else if (tokenvalue_is(token, "or")) { + op = DAVQL_LOR; + } else if (tokenvalue_is(token, "xor")) { + op = DAVQL_LXOR; + } + + if (op == DAVQL_NOOP) { + dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP, + _error_invalid_logical_op, stmt, token); + return 0; + } else { + expr->op = op; + total_consumed++; + token = token->next; + + memset(&right, 0, sizeof(DavQLExpression)); + consumed = dav_parse_logical_expr(stmt, token, &right); + if (stmt->errorcode) { + return 0; + } + if (!consumed) { + dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, + _error_missing_expr, stmt, token); + return 0; + } + total_consumed += consumed; + token = ucx_list_get(token, consumed); + + dqlsec_malloc(stmt, expr->left, DavQLExpression); + memcpy(expr->left, &left, sizeof(DavQLExpression)); + dqlsec_malloc(stmt, expr->right, DavQLExpression); + memcpy(expr->right, &right, sizeof(DavQLExpression)); + } + } else { + memcpy(expr, &left, sizeof(DavQLExpression)); + } + + // set type and recover source text + if (total_consumed > 0) { + expr->srctext.ptr = token_sstr(firsttoken).ptr; + sstr_t lasttok = token_sstr(ucx_list_get(firsttoken, total_consumed-1)); + expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length; + } + + return total_consumed; +} + +static int dav_parse_where_clause(DavQLStatement *stmt, UcxList *token) { + dqlsec_mallocz(stmt, stmt->where, DavQLExpression); + + return dav_parse_logical_expr(stmt, token, stmt->where); +} + +static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) { + + int total_consumed = 0; + + // RULE: "depth", "=", (Number | "infinity") + if (tokenvalue_is(token, "depth")) { + token = token->next; total_consumed++; + if (tokenvalue_is(token, "=")) { + token = token->next; total_consumed++; + if (tokenvalue_is(token, "infinity")) { + stmt->depth = DAV_DEPTH_INFINITY; + token = token->next; total_consumed++; + } else { + DavQLExpression *depthexpr; + dqlsec_mallocz(stmt, depthexpr, DavQLExpression); + + int consumed = dav_parse_expression(stmt, token, depthexpr); + + if (consumed) { + if (depthexpr->type == DAVQL_NUMBER) { + if (depthexpr->srctext.ptr[0] == '%') { + stmt->depth = DAV_DEPTH_PLACEHOLDER; + } else { + sstr_t depthstr = depthexpr->srctext; + char *conv = malloc(depthstr.length+1); + if (!conv) { + dav_free_expression(depthexpr); + stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; + return 0; + } + char *chk; + memcpy(conv, depthstr.ptr, depthstr.length); + conv[depthstr.length] = '\0'; + stmt->depth = strtol(conv, &chk, 10); + if (*chk || stmt->depth < -1) { + dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH, + _error_invalid_depth, stmt, token); + } + free(conv); + } + total_consumed += consumed; + } else { + dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH, + _error_invalid_depth, stmt, token); + } + } + + dav_free_expression(depthexpr); + } + } + } + + return total_consumed; +} + +static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token, + DavQLOrderCriterion *crit) { + + // RULE: (Identifier | Number), [" asc"|" desc"]; + DavQLExpression expr; + memset(&expr, 0, sizeof(DavQLExpression)); + int consumed = dav_parse_expression(stmt, token, &expr); + if (stmt->errorcode || !consumed) { + return 0; + } + + if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) { + dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION, + _error_invalid_order_criterion, stmt, token); + return 0; + } + + dqlsec_malloc(stmt, crit->column, DavQLExpression); + memcpy(crit->column, &expr, sizeof(DavQLExpression)); + + token = ucx_list_get(token, consumed); + if (token_is(token, DAVQL_TOKEN_KEYWORD) && ( + tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) { + + crit->descending = tokenvalue_is(token, "desc"); + + return consumed+1; + } else { + crit->descending = 0; + return consumed; + } +} + +static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) { + + int total_consumed = 0, consumed; + + DavQLOrderCriterion crit; + + // RULE: OrderByCriterion, {",", OrderByCriterion}; + do { + consumed = dav_parse_order_crit(stmt, token, &crit); + if (stmt->errorcode) { + return 0; + } + if (!consumed) { + dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr, + stmt, token); + return 0; + } + token = ucx_list_get(token, consumed); + total_consumed += consumed; + + DavQLOrderCriterion *criterion; + dqlsec_malloc(stmt, criterion, DavQLOrderCriterion); + memcpy(criterion, &crit, sizeof(DavQLOrderCriterion)); + dqlsec_list_append_or_free(stmt, stmt->orderby, criterion); + + if (token_is(token, DAVQL_TOKEN_COMMA)) { + total_consumed++; + token = token->next; + } else { + consumed = 0; + } + } while (consumed); + + return total_consumed; +} + + +static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) { + + // RULE: Assignment, {",", Assignment} + int total_consumed = 0, consumed; + do { + // RULE: Identifier, "=", Expression + if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) { + + // Identifier + DavQLField *field; + dqlsec_malloc(stmt, field, DavQLField); + field->name = token_sstr(token); + total_consumed++; + token = token->next; + + // "=" + if (!token_is(token, DAVQL_TOKEN_OPERATOR) + || !tokenvalue_is(token, "=")) { + dav_free_field(field); + + dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN, + _error_missing_assign, stmt, token); + return total_consumed; + } + total_consumed++; + token = token->next; + + // Expression + dqlsec_mallocz(stmt, field->expr, DavQLExpression); + consumed = dav_parse_expression(stmt, token, field->expr); + if (stmt->errorcode) { + dav_free_field(field); + return total_consumed; + } + token = ucx_list_get(token, consumed); + total_consumed += consumed; + + // Add assignment to list and check if there's another one + dqlsec_list_append_or_free(stmt, stmt->fields, field); + consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0; + if (consumed) { + token = token->next; + total_consumed++; + } + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_identifier, stmt, token); + return total_consumed; + } + } while (consumed); + + return total_consumed; +} + +static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) { + if (token_is(tokens, DAVQL_TOKEN_STRING)) { + stmt->path = token_sstr(tokens); + tokens = tokens->next; + return 1; + } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR) + && tokenvalue_is(tokens, "/")) { + stmt->path = token_sstr(tokens); + tokens = tokens->next; + int consumed = 1; + while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) && + !token_is(tokens, DAVQL_TOKEN_END)) { + sstr_t toksstr = token_sstr(tokens); + stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length; + tokens = tokens->next; + consumed++; + } + return consumed; + } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) && + tokenvalue_is(tokens, "%s")) { + stmt->path = token_sstr(tokens); + tokens = tokens->next; + stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)'s'); + return 1; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_path, stmt, tokens); + return 0; + } +} + +/** + * Parser of a select statement. + * @param stmt the statement object that shall contain the syntax tree + * @param tokens the token list + */ +static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { + stmt->type = DAVQL_SELECT; + + // Consume field list + tokens = ucx_list_get(tokens, dav_parse_fieldlist(stmt, tokens)); + if (stmt->errorcode) { + return; + } + + // Consume FROM keyword + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "from")) { + tokens = tokens->next; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_from, stmt, tokens); + return; + } + + // Consume path + tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens)); + if (stmt->errorcode) { + return; + } + //dav_add_fmt_args(stmt, stmt->path); // add possible path args + + // Consume with clause (if any) + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "with")) { + tokens = tokens->next; + tokens = ucx_list_get(tokens, + dav_parse_with_clause(stmt, tokens)); + } + if (stmt->errorcode) { + return; + } + + // Consume where clause (if any) + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "where")) { + tokens = tokens->next; + tokens = ucx_list_get(tokens, + dav_parse_where_clause(stmt, tokens)); + } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "anywhere")) { + // useless, but the user may want to explicitly express his intent + tokens = tokens->next; + stmt->where = NULL; + } + if (stmt->errorcode) { + return; + } + + // Consume order by clause (if any) + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "order")) { + tokens = tokens->next; + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "by")) { + tokens = tokens->next; + tokens = ucx_list_get(tokens, + dav_parse_orderby_clause(stmt, tokens)); + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_by, stmt, tokens); + return; + } + } + if (stmt->errorcode) { + return; + } + + + if (tokens) { + if (token_is(tokens, DAVQL_TOKEN_INVALID)) { + dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN, + _error_invalid_token, stmt, tokens); + } else if (!token_is(tokens, DAVQL_TOKEN_END)) { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _error_unexpected_token, stmt, tokens); + } + } +} + +static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { + stmt->type = DAVQL_SET; + + // Consume assignments + tokens = ucx_list_get(tokens, dav_parse_assignments(stmt, tokens)); + if (stmt->errorcode) { + return; + } + + // Consume AT keyword + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "at")) { + tokens = tokens->next; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_at, stmt, tokens); + return; + } + + // Consume path + tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens)); + if (stmt->errorcode) { + return; + } + + // Consume with clause (if any) + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "with")) { + tokens = tokens->next; + tokens = ucx_list_get(tokens, + dav_parse_with_clause(stmt, tokens)); + } + if (stmt->errorcode) { + return; + } + + // Consume mandatory where clause (or anywhere keyword) + if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "where")) { + tokens = tokens->next; + tokens = ucx_list_get(tokens, + dav_parse_where_clause(stmt, tokens)); + } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD) + && tokenvalue_is(tokens, "anywhere")) { + // no-op, but we want the user to be explicit about this + tokens = tokens->next; + stmt->where = NULL; + } else { + dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, + _error_missing_where, stmt, tokens); + return; + } +} + +DavQLStatement* dav_parse_statement(sstr_t srctext) { + DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement)); + + // if we can't even get enough memory for the statement object or an error + // message, we can simply die without returning anything + if (!stmt) { + return NULL; + } + char *oommsg = strdup(_error_out_of_memory); + if (!oommsg) { + free(stmt); + return NULL; + } + + // default values + stmt->type = -1; + stmt->depth = 1; + + // save trimmed source text + stmt->srctext = sstrtrim(srctext); + + if (stmt->srctext.length) { + // tokenization + UcxList* tokens = dav_parse_tokenize(stmt->srctext); + + if (tokens) { + // use first token to determine query type + + if (tokenvalue_is(tokens, "select")) { + dav_parse_select_statement(stmt, tokens->next); + } else if (tokenvalue_is(tokens, "set")) { + dav_parse_set_statement(stmt, tokens->next); + } else { + stmt->type = DAVQL_ERROR; + stmt->errorcode = DAVQL_ERROR_INVALID; + stmt->errormessage = strdup(_error_invalid); + } + + // free token data + UCX_FOREACH(token, tokens) { + free(token->data); + } + ucx_list_free(tokens); + } else { + stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; + } + } else { + stmt->type = DAVQL_ERROR; + stmt->errorcode = DAVQL_ERROR_INVALID; + stmt->errormessage = strdup(_error_invalid); + } + + if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) { + stmt->type = DAVQL_ERROR; + stmt->errormessage = oommsg; + } else { + free(oommsg); + } + + return stmt; +} + +void dav_free_statement(DavQLStatement *stmt) { + UCX_FOREACH(expr, stmt->fields) { + dav_free_field(expr->data); + } + ucx_list_free(stmt->fields); + + if (stmt->where) { + dav_free_expression(stmt->where); + } + if (stmt->errormessage) { + free(stmt->errormessage); + } + UCX_FOREACH(crit, stmt->orderby) { + dav_free_order_criterion(crit->data); + } + ucx_list_free(stmt->orderby); + ucx_list_free(stmt->args); + free(stmt); +} diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h new file mode 100644 index 0000000..fa52910 --- /dev/null +++ b/libidav/davqlparser.h @@ -0,0 +1,371 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAVQLPARSER_H +#define DAVQLPARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#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. + * NULL for literals or identifiers. + */ + DavQLExpression *left; + /** + * Right operand. + * NULL 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. + *
    + *
  • SELECT: the identifier or an alias name
  • + *
  • SET: the identifier
  • + *
+ */ + sstr_t name; + /** + * The field expression. + *
    + *
  • SELECT: the queried property (identifier) or an expression
  • + *
  • SET: the expression for the value to be set
  • + *
+ */ + DavQLExpression *expr; +} DavQLField; + +/** + * Query statement object. + * Contains the binary information about the parsed query. + * + * The grammar for a DavQLStatement is: + * + *
+ * 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"];
+ * 
+ * 
+ * + * 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. + * + * SELECT: + *
+ * SelectStatement = "select ", FieldExpressions,
+ * " from ", Path,
+ * [" with ", WithClause],
+ * [(" where ", LogicalExpression) | " anywhere"],
+ * [" order by ", OrderByClause];
+  * 
+ * + * SET: + *
+ * SetStatement = "set ",Assignments,
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
+ * 
+ * + */ +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. + * NULL, if there is no where clause. + */ + DavQLExpression* where; + /** + * The list of DavQLOrderCriterions. + * This is NULL for SET queries and may be NULL + * if the result doesn't need to be sorted. + */ + UcxList* orderby; + /** + * The recursion depth for the statement. + * Defaults to 1. + * Magic numbers are DAV_DEPTH_INFINITY for infinity and + * DAV_DEPTH_PLACEHOLDER for a placeholder. + */ + int depth; + /** + * A list of all required arguments + */ + UcxList* args; +} DavQLStatement; + +/** Infinity recursion depth for a DavQLStatement. */ +#define DAV_DEPTH_INFINITY -1 + +/** Depth needs to be specified at runtime. */ +#define DAV_DEPTH_PLACEHOLDER -2 + +/** Unexpected token. */ +#define DAVQL_ERROR_UNEXPECTED_TOKEN 1 + +/** A token has been found, for which no token class is applicable. */ +#define DAVQL_ERROR_INVALID_TOKEN 2 + +/** A token that has been expected was not found. */ +#define DAVQL_ERROR_MISSING_TOKEN 11 + +/** An expression has been expected, but was not found. */ +#define DAVQL_ERROR_MISSING_EXPR 12 + +/** A closed parenthesis ')' is missing. */ +#define DAVQL_ERROR_MISSING_PAR 13 + +/** An assignment operator '=' is missing. */ +#define DAVQL_ERROR_MISSING_ASSIGN 14 + +/** The type of the expression could not be determined. */ +#define DAVQL_ERROR_INVALID_EXPR 21 + +/** An operator has been found for an unary expression, but it is invalid. */ +#define DAVQL_ERROR_INVALID_UNARY_OP 22 + +/** An operator has been found for a logical expression, but it is invalid. */ +#define DAVQL_ERROR_INVALID_LOGICAL_OP 23 + +/** Invalid format specifier. */ +#define DAVQL_ERROR_INVALID_FMTSPEC 24 + +/** A string has been expected. */ +#define DAVQL_ERROR_INVALID_STRING 25 + +/** The order criterion is invalid (must be an identifier or field index). */ +#define DAVQL_ERROR_INVALID_ORDER_CRITERION 26 + +/** The depth is invalid. */ +#define DAVQL_ERROR_INVALID_DEPTH 101 + +/** Nothing about the statement seems legit. */ +#define DAVQL_ERROR_INVALID -1 + +/** A call to malloc or calloc failed. */ +#define DAVQL_ERROR_OUT_OF_MEMORY -2 + +/** + * Starts an interactive debugger for a DavQLStatement. + * + * @param stmt the statement to debug + */ +void dav_debug_statement(DavQLStatement *stmt); + +/** + * Parses a statement. + * @param stmt the sstr_t containing the statement + * @return a DavQLStatement object + */ +DavQLStatement* dav_parse_statement(sstr_t stmt); + +/** + * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement. + */ +#define dav_parse_cstr_statement(stmt) dav_parse_statement(S(stmt)) + +/** + * Frees a DavQLStatement. + * @param stmt the statement object to free + */ +void dav_free_statement(DavQLStatement *stmt); + +#ifdef __cplusplus +} +#endif + +#endif /* DAVQLPARSER_H */ + diff --git a/libidav/methods.c b/libidav/methods.c new file mode 100644 index 0000000..e54da5d --- /dev/null +++ b/libidav/methods.c @@ -0,0 +1,1350 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "utils.h" +#include "methods.h" +#include "crypto.h" +#include "session.h" +#include "xml.h" + +#include + +#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;ipos = 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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = SC("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = SC("\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + // write root element and namespaces + ucx_bprintf(buf, "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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\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, "\n\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + // properties + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + // end + s = S("\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + // write root element and namespaces + s = SC("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + if(data->set) { + s = SC("\n\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("name); + ucx_buffer_write(s.ptr, 1, s.length, buf); + s = SC(">\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + } + s = SC("\n\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + } + if(data->remove) { + s = SC("\n\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("\n\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + } + + s = SC("\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + if(DAV_ENCRYPT_NAME(sn)) { + s = S(""); + 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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + } + + s = S(""); + ucx_buffer_write(s.ptr, 1, s.length, buf); + ucx_buffer_puts(buf, key->name); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + if(hash) { + s = S(""); + ucx_buffer_write(s.ptr, 1, s.length, buf); + ucx_buffer_puts(buf, hash); + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + } + + s = S("\n\n\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("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n" + "\n" + "\n" + "http://davutils.org/libidav/\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + s = S("\n"); + ucx_buffer_write(s.ptr, 1, s.length, buf); + + return buf; +} + +int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock) { + lock->locktoken = NULL; + lock->timeout = NULL; + + xmlDoc *doc = xmlReadMemory(response->space, response->size, NULL, NULL, 0); + if(!doc) { + sn->error = DAV_ERROR; + return -1; + } + + char *timeout = NULL; + char *locktoken = NULL; + + int ret = -1; + xmlNode *xml_root = xmlDocGetRootElement(doc); + DavBool lockdiscovery = 0; + if(xml_root) { + xmlNode *node = xml_root->children; + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "lockdiscovery")) { + node = node->children; + lockdiscovery = 1; + continue; + } + + if(xstreq(node->name, "activelock")) { + node = node->children; + continue; + } + + if(lockdiscovery) { + if(xstreq(node->name, "timeout")) { + timeout = util_xml_get_text(node); + } else if(xstreq(node->name, "locktoken")) { + xmlNode *n = node->children; + while(n) { + if(xstreq(n->name, "href")) { + locktoken = util_xml_get_text(n); + break; + } + n = n->next; + } + } + } + } + node = node->next; + } + } + + if(timeout && locktoken) { + lock->timeout = strdup(timeout); + lock->locktoken = strdup(locktoken); + ret = 0; + } + + xmlFreeDoc(doc); + return ret; +} + +CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK"); + curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); + request->pos = 0; + + // clear headers + struct curl_slist *headers = NULL; + + if(timeout != 0) { + sstr_t thdr; + if(timeout < 0) { + thdr = ucx_sprintf("%s", "Timeout: Infinite"); + } else { + thdr = ucx_sprintf("Timeout: Second-%u", (unsigned int)timeout); + } + headers = curl_slist_append(headers, thdr.ptr); + free(thdr.ptr); + } + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); + curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek); + curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); + + CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL); + + if(headers) { + curl_slist_free_all(headers); + } + + return ret; +} + +CURLcode do_unlock_request(DavSession *sn, char *locktoken) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "UNLOCK"); + curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); + + // set lock-token header + sstr_t ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken); + struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + CURLcode ret = dav_session_curl_perform(sn, NULL); + curl_slist_free_all(headers); + free(ltheader.ptr); + + return ret; +} + +CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method); + curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); + + // set lock-token header + sstr_t ltheader; + struct curl_slist *headers = NULL; + if(locktoken) { + ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken); + headers = curl_slist_append(NULL, ltheader.ptr); + } + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + CURLcode ret = dav_session_curl_perform(sn, NULL); + if(locktoken) { + curl_slist_free_all(headers); + free(ltheader.ptr); + } + + return ret; +} + + +CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT"); + + curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); + curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek); + curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: text/xml"); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + request->pos = 0; + response->size = response->pos = 0; + CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL); + + curl_slist_free_all(headers); + + return ret; +} diff --git a/libidav/methods.h b/libidav/methods.h new file mode 100644 index 0000000..660e738 --- /dev/null +++ b/libidav/methods.h @@ -0,0 +1,134 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef METHODS_H +#define METHODS_H + +#include "webdav.h" +#include "resource.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PropfindParser PropfindParser; +typedef struct ResponseTag ResponseTag; +typedef struct LockDiscovery LockDiscovery; + +struct PropfindParser { + xmlDoc *document; + xmlNode *current; +}; + +struct ResponseTag { + char *href; + int iscollection; + UcxList *properties; + char *crypto_name; + char *crypto_key; +}; + +struct LockDiscovery { + char *timeout; + char *locktoken; +}; + +int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin); + +CURLcode do_propfind_request( + DavSession *sn, + UcxBuffer *request, + UcxBuffer *response); + +CURLcode do_proppatch_request( + DavSession *sn, + char *lock, + UcxBuffer *request, + UcxBuffer *response); + +CURLcode do_put_request( + DavSession *sn, + char *lock, + DavBool create, + void *data, + dav_read_func read_func, + dav_seek_func seek_func, + size_t length); + +UcxBuffer* create_allprop_propfind_request(void); +UcxBuffer* create_cryptoprop_propfind_request(void); +UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt); +UcxBuffer* create_basic_propfind_request(void); + +PropfindParser* create_propfind_parser(UcxBuffer *response, char *url); +void destroy_propfind_parser(PropfindParser *parser); +int get_propfind_response(PropfindParser *parser, ResponseTag *result); +void cleanup_response(ResponseTag *result); + +int hrefeq(DavSession *sn, char *href1, char *href2); +DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path); +void add_properties(DavResource *res, ResponseTag *response); + +DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response); +int parse_response_tag(DavResource *resource, xmlNode *node); +void set_davprops(DavResource *res); + +/* + * parses the content of a resourcetype element + * returns 1 if the resourcetype is a collection, 0 otherwise + */ +int parse_resource_type(xmlNode *node); + +UcxBuffer* create_proppatch_request(DavResourceData *data); +UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash); + +CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response); + +CURLcode do_mkcol_request(DavSession *sn, char *lock); + +CURLcode do_head_request(DavSession *sn); + +CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override); + +UcxBuffer* create_lock_request(void); +int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock); +CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout); +CURLcode do_unlock_request(DavSession *sn, char *locktoken); + +CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken); + +CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response); + +#ifdef __cplusplus +} +#endif + +#endif /* METHODS_H */ + diff --git a/libidav/resource.c b/libidav/resource.c new file mode 100644 index 0000000..8853e44 --- /dev/null +++ b/libidav/resource.c @@ -0,0 +1,1557 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#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 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;iname = 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, "\n"); + ucx_buffer_puts(content, "\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, ""); + + ucx_map_free_content(nsmap, (ucx_destructor)free); + ucx_map_free(nsmap); + + // encrypt xml document + char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key); + ucx_buffer_free(content); + + DavXmlNode *ret = NULL; + if(crypto_prop_content) { + ret = dav_text_node(sn, crypto_prop_content); + free(crypto_prop_content); + } + return ret; +} + +UcxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) { + if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) { + return NULL; + } + + return parse_crypto_prop_str(sn, key, node->content); +} + +UcxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) { + size_t len = 0; + char *dec_str = aes_decrypt(content, &len, key); + + xmlDoc *doc = xmlReadMemory(dec_str, len, NULL, NULL, 0); + free(dec_str); + if(!doc) { + return NULL; + } + + int err = 0; + xmlNode *xml_root = xmlDocGetRootElement(doc); + if(xml_root) { + if( + !xml_root->ns || + !xstreq(xml_root->name, "prop") || + !xstreq(xml_root->ns->href, "DAV:")) + { + err = 1; + } + } else { + err = 1; + } + + if(err) { + xmlFreeDoc(doc); + return NULL; + } + + // ready to get the properties + UcxMap *map = ucx_map_new(32); + xmlNode *n = xml_root->children; + while(n) { + if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) { + DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty)); + property->name = dav_session_strdup(sn, (const char*)n->name); + property->ns = dav_session_malloc(sn, sizeof(DavNamespace)); + property->ns->name = dav_session_strdup(sn, (const char*)n->ns->href); + property->ns->prefix = n->ns->prefix ? + dav_session_strdup(sn, (const char*)n->ns->prefix) : NULL; + property->value = n->children ? dav_convert_xml(sn, n->children) : NULL; + + sstr_t key = dav_property_key(property->ns->name, property->name); + ucx_map_sstr_put(map, key, property); + free(key.ptr); + } + n = n->next; + } + + xmlFreeDoc(doc); + if(map->count == 0) { + ucx_map_free(map); + return NULL; + } + return map; +} diff --git a/libidav/resource.h b/libidav/resource.h new file mode 100644 index 0000000..5cce0b7 --- /dev/null +++ b/libidav/resource.h @@ -0,0 +1,105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RESOURCE_H +#define RESOURCE_H + +#include "webdav.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavResourceData DavResourceData; + +struct DavResourceData { + UcxMap *properties; + UcxList *set; + UcxList *remove; + UcxList *crypto_set; + UcxList *crypto_remove; + + /* + * properties encapsulated in a crypto-prop property or NULL + */ + UcxMap *crypto_properties; + + /* + * char* or stream + */ + void *content; + /* + * if NULL, content is a char* + */ + read_func read; + /* + * curl seek func + */ + dav_seek_func seek; + /* + * content length + */ + size_t length; + + /* + * sha256 content hash + */ + char hash[32]; +}; + +DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href); + +void resource_free_properties(DavSession *sn, UcxMap *properties); + +void resource_set_href(DavResource *res, sstr_t href); + +void resource_set_info(DavResource *res, char *href_str); +DavResourceData* resource_data_new(DavSession *sn); +void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val); +void resource_set_crypto_properties(DavResource *res, UcxMap *cprops); +DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name); +DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name); +DavXmlNode* resource_get_property_k(DavResource *res, UcxKey key); +DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key); +void resource_add_child(DavResource *parent, DavResource *child); +void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr); +int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash); + +sstr_t dav_property_key_a(UcxAllocator *a, const char *ns, const char *name); + +DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties); +UcxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node); +UcxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content); + +#ifdef __cplusplus +} +#endif + +#endif /* RESOURCE_H */ + diff --git a/libidav/session.c b/libidav/session.c new file mode 100644 index 0000000..9f57fa4 --- /dev/null +++ b/libidav/session.c @@ -0,0 +1,595 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include + +#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 0) { + //printf("elm: %.*s\n", elm.length, elm.ptr); + DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr); + + // if necessary add a path separator + if(pbuf->space[pbuf->pos-1] != '/') { + if(href->space[href->pos-1] != '/') { + ucx_buffer_putc(href, '/'); + } + ucx_buffer_putc(pbuf, '/'); + } + // add last path/href to the cache + sstr_t pp = sstrn(pbuf->space, pbuf->size); + sstr_t hh = sstrn(href->space, href->size); + dav_session_cache_path(sn, pp, hh); + + ucx_buffer_write(elm.ptr, 1, elm.length, pbuf); + if(child) { + // href is already URL encoded, so don't encode again + ucx_buffer_puts(href, util_resource_name(child->href)); + res = child; + } else if(DAV_ENCRYPT_NAME(sn)) { + char *random_name = util_random_str(); + ucx_buffer_puts(href, random_name); + free(random_name); + } else { + // path is not URL encoded, so we have to do this here + scstr_t resname = scstr(util_resource_name(path)); + // the name of collections ends with + // a trailing slash, which MUST NOT be encoded + if(resname.ptr[resname.length-1] == '/') { + char *esc = curl_easy_escape(sn->handle, + resname.ptr, resname.length-1); + ucx_buffer_write(esc, 1, strlen(esc), href); + ucx_buffer_putc(href, '/'); + curl_free(esc); + } else { + char *esc = curl_easy_escape(sn->handle, + resname.ptr, resname.length); + ucx_buffer_write(esc, 1, strlen(esc), href); + curl_free(esc); + } + } + } + + // cleanup + free(elm.ptr); + } + free(elms); + + // if necessary add a path separator + if(p.ptr[p.length-1] == '/') { + if(href->space[href->pos-1] != '/') { + ucx_buffer_putc(href, '/'); + } + ucx_buffer_putc(pbuf, '/'); + } + // add the final path to the cache + sstr_t pp = sstrn(pbuf->space, pbuf->size); + sstr_t hh = sstrn(href->space, href->size); + dav_session_cache_path(sn, pp, hh); + + sstr_t href_str = sstrdup_a( + sn->mp->allocator, + sstrn(href->space, + href->size)); + + // cleanup + dav_resource_free_all(root); + ucx_buffer_free(rqbuf); + ucx_buffer_free(pbuf); + ucx_buffer_free(href); + + return href_str.ptr; + } else { + return dav_session_create_plain_href(sn, path); + } +} + +DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name) { + if(res && !dav_propfind(sn, res, rqbuf)) { + DavResource *child = res->children; + while(child) { + if(!strcmp(child->name, name)) { + return child; + } + child = child->next; + } + } + return NULL; +} + +void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href) { + char *elm = ucx_map_sstr_get(sn->pathcache, path); + if(!elm) { + href = sstrdup_a(sn->mp->allocator, href); + ucx_map_sstr_put(sn->pathcache, path, href.ptr); + } +} + + +DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout) { + DavLock *lock = dav_session_malloc(sn, sizeof(DavLock)); + lock->path = NULL; + lock->token = dav_session_strdup(sn, token); + + // TODO: timeout + + return lock; +} + +void dav_destroy_lock(DavSession *sn, DavLock *lock) { + dav_session_free(sn, lock->token); + if(lock->path) { + dav_session_free(sn, lock->path); + } + dav_session_free(sn, lock); +} + +int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock) { + DavLockManager *locks = sn->locks; + if(ucx_map_cstr_get(locks->resource_locks, path)) { + return -1; + } + + ucx_map_cstr_put(locks->resource_locks, path, lock); + return 0; +} + +static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) { + UcxList *next = elm->next; + if(next) { + next->prev = newelm; + newelm->next = next; + } + newelm->prev = elm; + elm->next = newelm; +} + +int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock) { + DavLockManager *locks = sn->locks; + if(!locks->collection_locks) { + locks->collection_locks = ucx_list_append_a( + sn->mp->allocator, + NULL, + lock); + lock->path = dav_session_strdup(sn, path); + return 0; + } + + UcxList *elm = locks->collection_locks; + for(;;) { + DavLock *l = elm->data; + int cmp = strcmp(path, l->path); + if(cmp > 0) { + UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock); + lock->path = dav_session_strdup(sn, path); + insert_lock(sn, elm, newelm); + } else if(cmp == 0) { + return -1; + } + + if(elm->next) { + elm = elm->next; + } else { + UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock); + lock->path = dav_session_strdup(sn, path); + ucx_list_concat(elm, newelm); + break; + } + } + + return 0; +} + +DavLock* dav_get_lock(DavSession *sn, char *path) { + DavLockManager *locks = sn->locks; + + DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path); + if(lock) { + return lock; + } + + sstr_t p = sstr(path); + UCX_FOREACH(elm, locks->collection_locks) { + DavLock *cl = elm->data; + int cmd = strcmp(path, cl->path); + if(cmd == 0) { + return cl; + } else if(sstrprefix(p, sstr(cl->path))) { + return cl; + } else if(cmd > 0) { + break; + } + } + + return NULL; +} + +void dav_remove_lock(DavSession *sn, char *path, DavLock *lock) { + DavLockManager *locks = sn->locks; + + if(ucx_map_cstr_remove(locks->resource_locks, path)) { + return; + } + + UcxList *rm = NULL; + UCX_FOREACH(elm, locks->collection_locks) { + DavLock *cl = elm->data; + if(cl == lock) { + rm = elm; + break; + } + } + + if(rm) { + locks->collection_locks = ucx_list_remove_a( + sn->mp->allocator, + locks->collection_locks, + rm); + } +} diff --git a/libidav/session.h b/libidav/session.h new file mode 100644 index 0000000..2ea2b76 --- /dev/null +++ b/libidav/session.h @@ -0,0 +1,120 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAV_SESSION_H +#define DAV_SESSION_H + +#include +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// initial size of the session mempool +#define DAV_SESSION_MEMPOOL_SIZE 1024 +// initial size of the path cache map +#define DAV_PATH_CACHE_SIZE 32 + +#define DAV_ENCRYPT_NAME(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_NAME) == DAV_SESSION_ENCRYPT_NAME) + +#define DAV_DECRYPT_NAME(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_NAME) == DAV_SESSION_DECRYPT_NAME) + +#define DAV_ENCRYPT_CONTENT(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_CONTENT) == DAV_SESSION_ENCRYPT_CONTENT) + +#define DAV_DECRYPT_CONTENT(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_CONTENT) == DAV_SESSION_DECRYPT_CONTENT) + +#define DAV_IS_ENCRYPTED(sn) \ + (DAV_ENCRYPT_NAME(sn) || DAV_ENCRYPT_CONTENT(sn)) + +#define DAV_CRYPTO(sn) \ + (DAV_ENCRYPT_NAME(sn) || DAV_DECRYPT_NAME(sn) || \ + DAV_ENCRYPT_CONTENT(sn) || DAV_DECRYPT_CONTENT(sn)) + +#define DAV_ENCRYPT_PROPERTIES(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_PROPERTIES) == DAV_SESSION_ENCRYPT_PROPERTIES) + +#define DAV_DECRYPT_PROPERTIES(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_PROPERTIES) == DAV_SESSION_DECRYPT_PROPERTIES) + +/* +typedef struct DavPathCacheElement { + char *name; + char *encrypted_name; + int exists; +} DavPathCacheElement; +*/ + +typedef struct DavLock { + char *path; + char *token; + +} DavLock; + +typedef struct DavLockManager { + UcxMap *resource_locks; + UcxList *collection_locks; +} DavLockManager; + +CURLcode dav_session_curl_perform(DavSession *sn, long *status); +CURLcode dav_session_curl_perform_buf(DavSession *sn, UcxBuffer *request, UcxBuffer *response, long *status); + +int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); +int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); + +void dav_session_set_error(DavSession *sn, CURLcode c, int status); +void dav_session_set_errstr(DavSession *sn, const char *str); + +char* dav_session_create_plain_href(DavSession *sn, char *path); + +char* dav_session_get_href(DavSession *sn, char *path); + +DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name); + +void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href); + + +DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout); +void dav_destroy_lock(DavSession *sn, DavLock *lock); + +int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock); +int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock); + +DavLock* dav_get_lock(DavSession *sn, char *path); +void dav_remove_lock(DavSession *sn, char *path, DavLock *lock); + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_SESSION_H */ + diff --git a/libidav/utils.c b/libidav/utils.c new file mode 100644 index 0000000..6ca83bd --- /dev/null +++ b/libidav/utils.c @@ -0,0 +1,1161 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define getpasswordchar() getch() +#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\') +#define PATH_SEPARATOR '\\' +#else +#include +#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 +#include +#include +#include +#include +*/ + +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 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;ihandle, 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;iflags = 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 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 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> 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> 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;ispace; + ucx_buffer_free(buf); + return str; +} + +void util_remove_trailing_pathseparator(char *path) { + size_t len = strlen(path); + if(len < 2) { + return; + } + + if(path[len-1] == '/') { + path[len-1] = '\0'; + } +} + +char* util_file_hash(const char *path) { + FILE *in = fopen(path, "r"); + if(!in) { + return NULL; + } + + DAV_SHA_CTX *sha = dav_hash_init(); + char *buf = malloc(16384); + + size_t r; + while((r = fread(buf, 1, 16384, in)) > 0) { + dav_hash_update(sha, buf, r); + } + + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + dav_hash_final(sha, hash); + free(buf); + fclose(in); + + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} diff --git a/libidav/utils.h b/libidav/utils.h new file mode 100644 index 0000000..f29138f --- /dev/null +++ b/libidav/utils.h @@ -0,0 +1,130 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_H +#define UTILS_H + +#ifdef _WIN32 +#include +#include +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include + +#include +#include "webdav.h" + +#ifndef S_IRWXG +/* if one is not defined, the others are probably also not defined */ +#define S_IRWXG 070 +#define S_IRGRP 040 +#define S_IWGRP 020 +#define S_IXGRP 010 +#define S_IRWXO 07 +#define S_IROTH 04 +#define S_IWOTH 02 +#define S_IXOTH 01 +#endif /* S_IRWXG */ + +#ifdef __cplusplus +extern "C" { +#endif + +time_t util_parse_creationdate(char *str); +time_t util_parse_lastmodified(char *str); + +int util_mkdir(char *path, mode_t mode); + +char* util_url_base(char *url); +char* util_url_base_s(sstr_t url); +char* util_url_path(char *url); +char* util_url_decode(DavSession *sn, char *url); +char* util_resource_name(char *url); +char* util_concat_path(const char *url_base, const char *path); +char* util_get_url(DavSession *sn, const char *href); +void util_set_url(DavSession *sn, const char *href); + +/* + * returns true if path1 and path2 are equal or if path2 is a child of path1 + */ +int util_path_isrelated(const char *path1, const char *path2); + +int util_path_isabsolut(const char *path); + +char* util_path_normalize(const char *path); +char* util_create_relative_path(const char *abspath, const char *base); + +void util_capture_header(CURL *handle, UcxMap* map); + +char* util_path_to_url(DavSession *sn, char *path); +char* util_parent_path(const char *path); + +char* util_size_str(DavBool iscollection, uint64_t contentlength); +char* util_date_str(time_t tm); + +int util_getboolean(const char *v); +int util_strtouint(const char *str, uint64_t *value); +int util_strtoint(const char *str, int64_t *value); +int util_szstrtouint(const char *str, uint64_t *value); + +int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result); + +char* util_xml_get_text(const xmlNode *elm); + +char* util_base64decode(const char *in); +char* util_base64decode_len(const char *in, int *outlen); +char* util_base64encode(const char *in, size_t len); + +char* util_encrypt_str(DavSession *sn, char *str, char *key); +char* util_encrypt_str_k(DavSession *sn, char *str, DavKey *key); +char* util_decrypt_str(DavSession *sn, char *str, char *key); +char* util_decrypt_str_k(DavSession *sn, char *str, DavKey *key); + +char* util_random_str(); + +sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub); + +sstr_t util_readline(FILE *stream); +char* util_password_input(char *prompt); + +char* util_hexstr(const unsigned char *data, size_t len); + +void util_remove_trailing_pathseparator(char *path); + +char* util_file_hash(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif /* UTILS_H */ + diff --git a/libidav/versioning.c b/libidav/versioning.c new file mode 100644 index 0000000..197f3b6 --- /dev/null +++ b/libidav/versioning.c @@ -0,0 +1,173 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "versioning.h" + +#include "methods.h" +#include "utils.h" +#include "session.h" + +static int basic_deltav_op(DavResource *res, char *method) { + DavSession *sn = res->session; + CURL *handle = sn->handle; + util_set_url(sn, dav_resource_get_href(res)); + + DavLock *lock = dav_get_lock(res->session, res->path); + char *locktoken = lock ? lock->token : NULL; + + CURLcode ret = do_simple_request(sn, method, locktoken); + long status = 0; + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); + if(!(ret == CURLE_OK && (status >= 200 && status < 300))) { + dav_session_set_error(sn, ret, status); + return 1; + } + return 0; +} + +int dav_versioncontrol(DavResource *res) { + return basic_deltav_op(res, "VERSION-CONTROL"); +} + +int dav_checkout(DavResource *res) { + return basic_deltav_op(res, "CHECKOUT"); +} + +int dav_checkin(DavResource *res) { + return basic_deltav_op(res, "CHECKIN"); +} + +int dav_uncheckout(DavResource *res) { + return basic_deltav_op(res, "UNCHECKOUT"); +} + +DavResource* dav_versiontree(DavResource *res, char *properties) { + DavSession *sn = res->session; + util_set_url(sn, dav_resource_get_href(res)); + + UcxList *proplist = NULL; + if(properties) { + proplist = parse_properties_string(sn->context, sstr(properties)); + } + + // check if the list already contains a D:version-name property + int add_vname = 1; + UCX_FOREACH(elm, proplist) { + DavProperty *p = elm->data; + if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) { + add_vname = 0; + break; + } + } + if(add_vname) { + // we need at least the D:version-name prop + DavProperty *p = malloc(sizeof(DavProperty)); + p->ns = dav_get_namespace(sn->context, "D"); + p->name = strdup("version-name"); + p->value = NULL; + proplist = ucx_list_prepend(proplist, p); + } + + // create a version-tree request, which is almost the same as propfind + UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1); + UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + + // do the request + CURLcode ret = do_report_request(sn, rqbuf, rpbuf); + long status = 0; + curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status); + int error = 0; + DavResource *versions = NULL; + if(ret == CURLE_OK && status == 207) { + sn->error = DAV_OK; + + // parse multistatus response + PropfindParser *parser = create_propfind_parser(rpbuf, NULL); + if(parser) { + DavResource *list_end = NULL; + + ResponseTag response; + int r; + + // we don't want name decryption for version resources + int snflags = sn->flags; + sn->flags = 0; + while((r = get_propfind_response(parser, &response)) != 0) { + if(r == -1) { + res->session->error = DAV_ERROR; + error = 1; + break; + } + DavResource *v = response2resource(sn, &response, NULL); + // add version to list + if(!versions) { + versions = v; + } else { + list_end->next = v; + } + list_end = v; + + cleanup_response(&response); + } + sn->flags = snflags; + + destroy_propfind_parser(parser); + } else { + sn->error = DAV_ERROR; + error = 1; + } + } else { + dav_session_set_error(sn, ret, status); + error = 1; + } + + // cleanup + while(proplist) { + DavProperty *p = proplist->data; + free(p->name); + free(p); + UcxList *next = proplist->next; + free(proplist); + proplist = next; + } + if(error && versions) { + DavResource *cur = versions; + while(cur) { + DavResource *next = cur->next; + dav_resource_free(cur); + cur = next; + } + versions = NULL; + } + + return versions; +} diff --git a/libidav/versioning.h b/libidav/versioning.h new file mode 100644 index 0000000..1cf2649 --- /dev/null +++ b/libidav/versioning.h @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef VERSIONING_H +#define VERSIONING_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif /* VERSIONING_H */ + diff --git a/libidav/webdav.c b/libidav/webdav.c new file mode 100644 index 0000000..ed8ceb2 --- /dev/null +++ b/libidav/webdav.c @@ -0,0 +1,409 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#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 0) { + sstr_t nspre = sstrsubsl(s, 0, nsname.ptr - s.ptr); + nsname.ptr++; + nsname.length--; + + DavProperty *dp = malloc(sizeof(DavProperty)); + sstr_t pre = sstrtrim(nspre); + dp->ns = dav_get_namespace_s(context, pre); + dp->name = sstrdup(nsname).ptr; + if(dp->ns && dp->name) { + proplist = ucx_list_append(proplist, dp); + } else { + free(dp->name); + free(dp); + } + } + free(s.ptr); + } + free(props); + return proplist; +} + +DavResource* dav_query(DavSession *sn, char *query, ...) { + DavQLStatement *stmt = dav_parse_statement(sstr(query)); + if(!stmt) { + sn->error = DAV_ERROR; + return NULL; + } + if(stmt->errorcode != 0) { + sn->error = DAV_QL_ERROR; + dav_free_statement(stmt); + return NULL; + } + + va_list ap; + va_start(ap, query); + DavResult result = dav_statement_execv(sn, stmt, ap); + va_end(ap); + + dav_free_statement(stmt); + return result.result; +} + + + + diff --git a/libidav/webdav.h b/libidav/webdav.h new file mode 100644 index 0000000..eb9a891 --- /dev/null +++ b/libidav/webdav.h @@ -0,0 +1,372 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBDAV_H +#define WEBDAV_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char DavBool; +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +typedef struct DavContext DavContext; +typedef struct DavProxy DavProxy; +typedef struct DavSession DavSession; +typedef struct DavResource DavResource; +typedef struct DavResult DavResult; +typedef struct DavNamespace DavNamespace; +typedef struct DavProperty DavProperty; +typedef struct DavPropName DavPropName; +typedef struct DavKey DavKey; +typedef struct DavNSInfo DavNSInfo; +typedef struct DavXmlNode DavXmlNode; +typedef struct DavXmlAttr DavXmlAttr; + +typedef size_t(*dav_read_func)(void*, size_t, size_t, void*); +typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*); +typedef int(*dav_seek_func)(const void *, long, int); + +typedef int(*dav_auth_func)(DavSession *, void *); +typedef void(*dav_progress_func)(DavResource *, int64_t, int64_t, void *); + +enum DavError { + DAV_OK = 0, + DAV_ERROR, + DAV_NOT_FOUND, + DAV_UNAUTHORIZED, + DAV_FORBIDDEN, + DAV_METHOD_NOT_ALLOWED, + DAV_CONFLICT, + DAV_LOCKED, + DAV_UNSUPPORTED_PROTOCOL, + DAV_COULDNT_RESOLVE_PROXY, + DAV_COULDNT_RESOLVE_HOST, + DAV_COULDNT_CONNECT, + DAV_TIMEOUT, + DAV_SSL_ERROR, + DAV_QL_ERROR, + DAV_CONTENT_VERIFICATION_ERROR, + DAV_PRECONDITION_FAILED, + DAV_REQUEST_ENTITY_TOO_LARGE, + DAV_REQUEST_URL_TOO_LONG, + DAV_PROXY_AUTH_REQUIRED, + DAV_NET_AUTH_REQUIRED +}; + +typedef enum DavError DavError; + +enum DavXmlNodeType { + DAV_XML_NONE = 0, + DAV_XML_ELEMENT, + DAV_XML_TEXT +}; + +typedef enum DavXmlNodeType DavXmlNodeType; + +#define DAV_SESSION_ENCRYPT_CONTENT 0x0001 +#define DAV_SESSION_ENCRYPT_NAME 0x0002 +#define DAV_SESSION_ENCRYPT_PROPERTIES 0x0004 +#define DAV_SESSION_DECRYPT_CONTENT 0x0008 +#define DAV_SESSION_DECRYPT_NAME 0x0010 +#define DAV_SESSION_DECRYPT_PROPERTIES 0x0020 +#define DAV_SESSION_STORE_HASH 0x0040 + +#define DAV_SESSION_CONTENT_ENCRYPTION 0x0009 +#define DAV_SESSION_FULL_ENCRYPTION 0x003f + + +#define DAV_NS "http://davutils.org/" +#define DAV_PROPS_NS "http://davutils.org/props/" + +struct DavNamespace { + char *prefix; + char *name; +}; + +struct DavResource { + DavSession *session; + DavResource *prev; + DavResource *next; + DavResource *parent; + DavResource *children; + char *name; + char *path; + char *href; + uint64_t contentlength; + char *contenttype; + time_t creationdate; + time_t lastmodified; + void *data; + int iscollection; + int exists; +}; + +struct DavSession { + DavContext *context; + CURL *handle; + char *base_url; + UcxMempool *mp; + UcxMap *pathcache; + DavKey *key; + void *locks; + uint32_t flags; + DavError error; + char *errorstr; + + int(*auth_prompt)(DavSession *sn, void *userdata); + void *authprompt_userdata; + + void(*get_progress)(DavResource *res, int64_t total, int64_t now, void *userdata); + void(*put_progress)(DavResource *res, int64_t total, int64_t now, void *userdata); + void *progress_userdata; +}; + +struct DavContext { + UcxMap *namespaces; + UcxMap *namespaceinfo; + UcxMap *keys; + UcxList *sessions; + DavProxy *http_proxy; + DavProxy *https_proxy; +}; + +struct DavProxy { + char *url; + char *username; + char *password; + char *no_proxy; +}; + +struct DavProperty { + DavNamespace *ns; + char *name; + DavXmlNode *value; +}; + +struct DavPropName { + char *ns; + char *name; +}; + +struct DavResult { + DavResource *result; + int status; +}; + +#define DAV_KEY_AES128 0 +#define DAV_KEY_AES256 1 + +struct DavKey { + char *name; + int type; + void *data; + size_t length; +}; + +struct DavNSInfo { + char *prefix; + DavBool encrypt; +}; + +struct DavXmlNode { + DavXmlNodeType type; + + char *namespace; + char *name; + + DavXmlNode *prev; + DavXmlNode *next; + DavXmlNode *children; + DavXmlNode *parent; + + DavXmlAttr *attributes; + + char *content; + size_t contentlength; +}; + +struct DavXmlAttr { + char *name; + char *value; + DavXmlAttr *next; +}; + +DavContext* dav_context_new(void); +void dav_context_destroy(DavContext *ctx); + +void dav_context_add_key(DavContext *context, DavKey *key); +DavKey* dav_context_get_key(DavContext *context, char *name); + +int dav_add_namespace(DavContext *context, const char *prefix, const char *ns); +DavNamespace* dav_get_namespace(DavContext *context, const char *prefix); + +int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt); +int dav_namespace_is_encrypted(DavContext *context, const char *ns); + +DavSession* dav_session_new(DavContext *context, char *base_url); +DavSession* dav_session_new_auth( + DavContext *context, + char *base_url, + char *user, + char *password); +void dav_session_set_auth(DavSession *sn, char *user, char *password); +void dav_session_set_baseurl(DavSession *sn, char *base_url); +void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags); + +void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata); +void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata); + +void dav_session_destroy(DavSession *sn); + +void* dav_session_malloc(DavSession *sn, size_t size); +void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size); +void* dav_session_realloc(DavSession *sn, void *ptr, size_t size); +void dav_session_free(DavSession *sn, void *ptr); +char* dav_session_strdup(DavSession *sn, const char *str); + +void dav_set_effective_href(DavSession *sn, DavResource *resource); +DavResource* dav_get(DavSession *sn, char *path, char *properties); + +UcxList* parse_properties_string(DavContext *context, sstr_t str); + +DavResource* dav_query(DavSession *sn, char *query, ...); + +sstr_t dav_property_key(const char *ns, const char *name); +void dav_get_property_namespace_str( + DavContext *ctx, + char *prefixed_name, + char **ns, + char **name); +DavNamespace* dav_get_property_namespace( + DavContext *ctx, + char *prefixed_name, + char **name); + +/* ------------------------ resource functions ------------------------ */ + +DavResource* dav_resource_new(DavSession *sn, char *path); +DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *name); +DavResource* dav_resource_new_href(DavSession *sn, char *href); + +void dav_resource_free(DavResource *res); +void dav_resource_free_all(DavResource *res); + +char* dav_resource_get_href(DavResource *resource); + +DavResource* dav_create_child(DavResource *parent, char *name); +int dav_delete(DavResource *res); +int dav_create(DavResource *res); +int dav_exists(DavResource *res); + +int dav_copy(DavResource *res, char *newpath); +int dav_move(DavResource *res, char *newpath); +int dav_copy_o(DavResource *res, char *newpath, DavBool override); +int dav_move_o(DavResource *res, char *newpath, DavBool override); +int dav_copyto(DavResource *res, char *url, DavBool override); +int dav_moveto(DavResource *res, char *url, DavBool override); + +int dav_lock(DavResource *res); +int dav_lock_t(DavResource *res, time_t timeout); +int dav_unlock(DavResource *res); + +DavXmlNode* dav_get_property(DavResource *res, char *name); +DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name); +DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name); +char* dav_get_string_property(DavResource *res, char *name); +char* dav_get_string_property_ns(DavResource *res, char *ns, char *name); +void dav_set_string_property(DavResource *res, char *name, char *value); +void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value); +void dav_set_property(DavResource *res, char *name, DavXmlNode *value); +void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value); +void dav_remove_property(DavResource *res, char *name); +void dav_remove_property_ns(DavResource *res, char *ns, char *name); +void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value); +void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value); +void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name); + +DavPropName* dav_get_property_names(DavResource *res, size_t *count); + +void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func); +void dav_set_content_data(DavResource *res, char *content, size_t length); +void dav_set_content_length(DavResource *res, size_t length); + +int dav_load(DavResource *res); +int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop); +int dav_store(DavResource *res); +int dav_get_content(DavResource *res, void *stream, dav_write_func write_func); + +// private +int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf); + + +/* --------------------------- DeltaV ---------------------------- */ + +int dav_versioncontrol(DavResource *res); +int dav_checkout(DavResource *res); +int dav_checkin(DavResource *res); +int dav_uncheckout(DavResource *res); +DavResource* dav_versiontree(DavResource *res, char *properties); + +/* ------------------------ xml functions ------------------------ */ +char* dav_xml_getstring(DavXmlNode *node); +DavBool dav_xml_isstring(DavXmlNode *node); +DavXmlNode* dav_xml_nextelm(DavXmlNode *node); +DavXmlNode* dav_text_node(DavSession *sn, char *text); + +DavXmlNode* dav_copy_node(DavXmlNode *node); + +DavXmlNode* dav_xml_createnode(const char *ns, const char *name); +DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text); +DavXmlNode* dav_xml_createtextnode(const char *text); +void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child); +void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value); +char* dav_xml_get_attr(DavXmlNode *node, const char *name); + +DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* WEBDAV_H */ + diff --git a/libidav/xml.c b/libidav/xml.c new file mode 100644 index 0000000..082cfba --- /dev/null +++ b/libidav/xml.c @@ -0,0 +1,393 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include + +#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("", 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, "", prefix, node->name); + } else { + ucx_fprintf(stream, writef, "", node->name); + } + } + } else if(node->type == DAV_XML_TEXT) { + writef(node->content, 1, node->contentlength, stream); + } + + node = node->next; + } +} + +/* ------------------------- public API ------------------------- */ + +char* dav_xml_getstring(DavXmlNode *node) { + if(node && node->type == DAV_XML_TEXT) { + return node->content; + } else { + return NULL; + } +} + +DavBool dav_xml_isstring(DavXmlNode *node) { + if(node && node->type == DAV_XML_TEXT && !node->next) { + return TRUE; + } else { + return FALSE; + } +} + +DavXmlNode* dav_xml_nextelm(DavXmlNode *node) { + node = node->next; + while(node) { + if(node->type == DAV_XML_ELEMENT) { + return node; + } + node = node->next; + } + return NULL; +} + +DavXmlNode* dav_text_node(DavSession *sn, char *text) { + UcxMempool *mp = sn->mp; + DavXmlNode *newxn = ucx_mempool_calloc(mp, 1, sizeof(DavXmlNode)); + newxn->type = DAV_XML_TEXT; + sstr_t content = sstrdup_a(mp->allocator, sstr(text)); + newxn->content = content.ptr; + newxn->contentlength = content.length; + return newxn; +} + + +DavXmlAttr* dav_copy_xml_attr(DavXmlAttr *attr) { + if(!attr) { + return NULL; + } + DavXmlAttr *newattr = NULL; + DavXmlAttr *prev = NULL; + while(attr) { + DavXmlAttr *n = calloc(1, sizeof(DavXmlAttr)); + n->name = strdup(attr->name); + n->value = strdup(attr->value); + if(prev) { + prev->next = n; + } else { + newattr = n; + } + prev = n; + attr = attr->next; + } + return newattr; +} + +DavXmlNode* dav_copy_node(DavXmlNode *node) { + DavXmlNode *ret = NULL; + DavXmlNode *prev = NULL; + while(node) { + DavXmlNode *copy = calloc(1, sizeof(DavXmlNode)); + copy->type = node->type; + if(node->type == DAV_XML_ELEMENT) { + copy->namespace = strdup(node->namespace); + copy->name = strdup(node->name); + copy->children = dav_copy_node(node->children); + copy->attributes = dav_copy_xml_attr(node->attributes); + } else { + copy->contentlength = node->contentlength; + copy->content = malloc(node->contentlength+1); + memcpy(copy->content, node->content, node->contentlength); + copy->content[copy->contentlength] = 0; + } + if(!ret) { + ret = copy; + } + if(prev) { + prev->next = copy; + copy->prev = prev; + } + prev = copy; + node = node->next; + } + return ret; +} + + +DavXmlNode* dav_xml_createnode(const char *ns, const char *name) { + DavXmlNode *node = calloc(1, sizeof(DavXmlNode)); + node->type = DAV_XML_ELEMENT; + node->namespace = strdup(ns); + node->name = strdup(name); + return node; +} + +DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text) { + DavXmlNode *node = calloc(1, sizeof(DavXmlNode)); + node->type = DAV_XML_ELEMENT; + node->namespace = strdup(ns); + node->name = strdup(name); + + DavXmlNode *textnode = dav_xml_createtextnode(text); + node->children = textnode; + + return node; +} + +DavXmlNode* dav_xml_createtextnode(const char *text) { + DavXmlNode *node = calloc(1, sizeof(DavXmlNode)); + node->type = DAV_XML_TEXT; + sstr_t content = sstrdup(sstr((char*)text)); + node->content = content.ptr; + node->contentlength = content.length; + return node; +} + +void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child) { + DavXmlNode *last_child = NULL; + DavXmlNode *c = node->children; + while(c) { + last_child = c; + c = c->next; + } + if(last_child) { + last_child->next = child; + child->prev = last_child; + } else { + node->children = child; + } +} + +void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value) { + DavXmlAttr *attr = calloc(1, sizeof(DavXmlAttr)); + attr->name = strdup(name); + attr->value = strdup(value); + + if(node->attributes) { + DavXmlAttr *last; + DavXmlAttr *end = node->attributes; + while(end) { + last = end; + end = end->next; + } + last->next = attr; + } else { + node->attributes = attr; + } +} + +char* dav_xml_get_attr(DavXmlNode *node, const char *name) { + DavXmlAttr *attr = node->attributes; + while(attr) { + if(!strcmp(attr->name, name)) { + return attr->value; + } + + attr = attr->next; + } + return NULL; +} + +DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len) { + xmlDoc *doc = xmlReadMemory(str, len, NULL, NULL, 0); + if(!doc) { + return NULL; + } + xmlNode *xml_root = xmlDocGetRootElement(doc); + if(!xml_root) { + xmlFreeDoc(doc); + return NULL; + } + DavXmlNode *x = dav_convert_xml(sn, xml_root); + xmlFreeDoc(doc); + return x; +} diff --git a/libidav/xml.h b/libidav/xml.h new file mode 100644 index 0000000..a78449f --- /dev/null +++ b/libidav/xml.h @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef DAV_XML_H +#define DAV_XML_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node); + +void dav_print_xml(DavXmlNode *node); + +void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode *node); + + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_XML_H */ + diff --git a/make/Makefile.mk b/make/Makefile.mk new file mode 100644 index 0000000..11b8e73 --- /dev/null +++ b/make/Makefile.mk @@ -0,0 +1,62 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2021 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# this makefile is invoked from the build root directory + +BUILD_ROOT = ./ +include config.mk + +BUILD_DIRS = build/bin build/lib +BUILD_DIRS += build/libidav +BUILD_DIRS += build/mizunara +BUILD_DIRS += build/mizucp +BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT) + +all: $(BUILD_DIRS) ucx ui libidav mizucp mizunara + make/$(PACKAGE_SCRIPT) + +$(BUILD_DIRS): + mkdir -p $@ + +ucx: $(BUILD_DIRS) FORCE + cd ucx; $(MAKE) + +ui: $(BUILD_DIRS) FORCE + cd ui; $(MAKE) all + +libidav: $(BUILD_DIRS) FORCE + cd libidav; $(MAKE) all + +mizucp: $(BUILD_DIRS) FORCE + cd mizucp; $(MAKE) + +mizunara: $(BUILD_DIRS) ui FORCE + cd mizunara; $(MAKE) + +FORCE: + diff --git a/make/clang.mk b/make/clang.mk new file mode 100644 index 0000000..93e8096 --- /dev/null +++ b/make/clang.mk @@ -0,0 +1,9 @@ +# +# clang toolchain config +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared diff --git a/make/configure.vm b/make/configure.vm new file mode 100644 index 0000000..80068be --- /dev/null +++ b/make/configure.vm @@ -0,0 +1,615 @@ +#!/bin/sh + +#foreach( $var in $vars ) +#if( $var.exec ) +${var.name}=`${var.value}` +#else +${var.name}=${var.value} +#end +#end + +#if ( ! $project.hasVar("PREFIX") ) +PREFIX=/usr +#end +#if ( ! $project.hasVar("EPREFIX") ) +EPREFIX=$PREFIX +#end + +#if ( ! $project.hasVar("BINDIR") ) +BINDIR= +#end +#if ( ! $project.hasVar("SBINDIR") ) +SBINDIR= +#end +#if ( ! $project.hasVar("LIBDIR") ) +LIBDIR= +#end +#if ( ! $project.hasVar("LIBEXECDIR") ) +LIBEXECDIR= +#end +#if ( ! $project.hasVar("DATADIR") ) +DATADIR= +#end +#if ( ! $project.hasVar("SYSCONFDIR") ) +SYSCONFDIR= +#end +#if ( ! $project.hasVar("SHAREDSTATEDIR") ) +SHAREDSTATEDIR= +#end +#if ( ! $project.hasVar("LOCALSTATEDIR") ) +LOCALSTATEDIR= +#end +#if ( ! $project.hasVar("INCLUDEDIR") ) +INCLUDEDIR= +#end +#if ( ! $project.hasVar("INFODIR") ) +INFODIR= +#end +#if ( ! $project.hasVar("MANDIR") ) +MANDIR= +#end + +OS=`uname -s` +OS_VERSION=`uname -r` + +TEMP_DIR=".tmp-`uname -n`" +mkdir -p $TEMP_DIR +if [ $? -ne 0 ]; then + echo "Cannot create tmp dir" + echo "Abort" +fi +touch $TEMP_DIR/options +touch $TEMP_DIR/features + +# features +#foreach( $feature in $features ) +#if( ${feature.isDefault()} ) +${feature.getVarName()}=on +#end +#end + +# help text +printhelp() +{ + echo "Usage: $0 [OPTIONS]..." + cat << __EOF__ +Installation directories: + --prefix=PREFIX path prefix for architecture-independent files + [/usr] + --exec-prefix=EPREFIX path prefix for architecture-dependent files + [PREFIX] + + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR system configuration files [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --mandir=DIR man documentation [DATAROOTDIR/man] + +#if( $options.size() > 0 ) +Options: +#foreach( $opt in $options ) + --${opt.getArgument()}=${opt.getValuesString()} +#end + +#end +#if( $features.size() > 0 ) +Optional Features: +#foreach( $feature in $features ) +#if( $feature.default ) + --disable-${feature.arg} +#else + --enable-${feature.arg} +#end +#end + +#end +__EOF__ +} + +# +# parse arguments +# +#set( $D = '$' ) +for ARG in $@ +do + case "$ARG" in + "--prefix="*) PREFIX=${D}{ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${D}{ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${D}{ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${D}{ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${D}{ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${D}{ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${D}{ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${D}{ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${D}{ARG#--includedir=} ;; + "--infodir="*) INFODIR=${D}{ARG#--infodir=} ;; + "--mandir"*) MANDIR=${D}{ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + #foreach( $opt in $options ) + "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;; + #end + #foreach( $feature in $features ) + "--enable-${feature.arg}") ${feature.getVarName()}=on ;; + "--disable-${feature.arg}") unset ${feature.getVarName()} ;; + #end + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac +done + +# set dir variables +if [ -z "$BINDIR" ]; then + BINDIR=$EPREFIX/bin +fi +if [ -z "$SBINDIR" ]; then + SBINDIR=$EPREFIX/sbin +fi +if [ -z "$LIBDIR" ]; then + LIBDIR=$EPREFIX/lib +fi +if [ -z "$LIBEXEC" ]; then + LIBEXECDIR=$EPREFIX/libexec +fi +if [ -z "$DATADIR" ]; then + DATADIR=$PREFIX/share +fi +if [ -z "$SYSCONFDIR" ]; then + SYSCONFDIR=$PREFIX/etc +fi +if [ -z "$SHAREDSTATEDIR" ]; then + SHAREDSTATEDIR=$PREFIX/com +fi +if [ -z "$LOCALSTATEDIR" ]; then + LOCALSTATEDIR=$PREFIX/var +fi +if [ -z "$INCLUDEDIR" ]; then + INCLUDEDIR=$PREFIX/include +fi +if [ -z "$INFODIR" ]; then + INFODIR=$PREFIX/info +fi +if [ -z "$MANDIR" ]; then + MANDIR=$PREFIX/man +fi + +which pkg-config > /dev/null +if [ $? -eq 0 ]; then + PKG_CONFIG=pkg-config +else + PKG_CONFIG=false +fi + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +printf "detect platform... " +if [ $OS = SunOS ]; then + PLATFORM="solaris sunos unix svr4" +fi +if [ $OS = Linux ]; then + PLATFORM="linux unix" +fi +if [ $OS = FreeBSD ]; then + PLATFORM="freebsd bsd unix" +fi +if [ $OS = Darwin ]; then + PLATFORM="macos osx bsd unix" +fi +echo $OS | grep "MINGW" > /dev/null +if [ $? -eq 0 ]; then + PLATFORM="windows mingw" +fi + +if [ -z "$PLATFORM" ]; then + PLATFORM="unix" +fi + +for p in $PLATFORM +do + PLATFORM_NAME=$p + break +done +echo $PLATFORM_NAME + +isplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 0 + fi + done + return 1 +} +isnotplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 1 + fi + done + return 0 +} + +# generate config.mk and config.h +cat > $TEMP_DIR/config.mk << __EOF__ +# +# config.mk generated by configure +# + +# general vars +#foreach( $var in $vars ) +${var.name}=$${var.name} +#end + +#if ( ! $project.hasVar("PREFIX") ) +PREFIX=$PREFIX +#end +#if ( ! $project.hasVar("EPREFIX") ) +EPREFIX=$EPREFIX +#end + +#if ( ! $project.hasVar("BINDIR") ) +BINDIR=$BINDIR +#end +#if ( ! $project.hasVar("SBINDIR") ) +SBINDIR=$SBINDIR +#end +#if ( ! $project.hasVar("LIBDIR") ) +LIBDIR=$LIBDIR +#end +#if ( ! $project.hasVar("LIBEXECDIR") ) +LIBEXECDIR=$LIBEXECDIR +#end +#if ( ! $project.hasVar("DATADIR") ) +DATADIR=$DATADIR +#end +#if ( ! $project.hasVar("SYSCONFDIR") ) +SYSCONFDIR=$SYSCONFDIR +#end +#if ( ! $project.hasVar("SHAREDSTATEDIR") ) +SHAREDSTATEDIR=$SHAREDSTATEDIR +#end +#if ( ! $project.hasVar("LOCALSTATEDIR") ) +LOCALSTATEDIR=$LOCALSTATEDIR +#end +#if ( ! $project.hasVar("INCLUDEDIR") ) +INCLUDEDIR=$INCLUDEDIR +#end +#if ( ! $project.hasVar("INFODIR") ) +INFODIR=$INFODIR +#end +#if ( ! $project.hasVar("MANDIR") ) +MANDIR=$MANDIR +#end + +__EOF__ + +echo > $TEMP_DIR/make.mk + +ENV_CFLAGS=$CFLAGS +ENV_LDFLAGS=$LDFLAGS +ENV_CXXFLAGS=$CXXFLAGS + +# Toolchain detection +# this will insert make vars to config.mk +. make/toolchain.sh + +# add user specified flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${ENV_CFLAGS}" ]; then + echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_CXXFLAGS}" ]; then + echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_LDFLAGS}" ]; then + echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# DEPENDENCIES +# + +#foreach( $dependency in $namedDependencies ) +dependency_${dependency.name}() +{ + printf "checking for ${dependency.name}... " + #foreach( $sub in $dependency.getSubdependencies() ) + # dependency $sub.name $sub.getPlatformString() + while true + do + #if( $sub.platform ) + if isnotplatform "${sub.platform}"; then + break + fi + #end + #foreach( $not in $sub.getNotList() ) + if isplatform "${not}"; then + break + fi + #end + #if( $sub.pkgconfig.size() > 0 ) + if [ -z "$PKG_CONFIG" ]; then + break + fi + #end + #foreach( $pkg in $sub.pkgconfig ) + $PKG_CONFIG $pkg.getPkgConfigParam() + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`" + #end + #foreach( $flags in $sub.flags ) + #if( $flags.exec ) + $flags.value > /dev/null + if [ $? -eq 0 ]; then + $flags.varName="$$flags.varName `$flags.value`" + else + break + fi + #else + $flags.varName="$$flags.varName $flags.value" + #end + #end + #foreach( $test in $sub.tests ) + $test > /dev/null + if [ $? -ne 0 ]; then + break + fi + #end + #if ( $sub.make.length() > 0 ) + cat >> $TEMP_DIR/make.mk << __EOF__ +# Dependency: $dependency.name +$sub.make +__EOF__ + #end + echo yes + return 0 + done + + #end + echo no + return 1 +} +#end + +DEPENDENCIES_FAILED= +ERROR=0 +#if( $dependencies.size() > 0 ) +# general dependencies +CFLAGS= +LDFLAGS= +#foreach( $dependency in $dependencies ) +while true +do + #if( $dependency.platform ) + if isnotplatform "${dependency.platform}"; then + break + fi + #end + #foreach( $not in $dependency.getNotList() ) + if isplatform "${not}"; then + break + fi + #end + while true + do + #if( $dependency.pkgconfig.size() > 0 ) + if [ -z "$PKG_CONFIG" ]; then + ERROR=1 + break + fi + #end + #foreach( $pkg in $dependency.pkgconfig ) + printf "checking for pkg-config package $pkg.getPkgConfigParam()... " + $PKG_CONFIG $pkg.getPkgConfigParam() + if [ $? -ne 0 ]; then + echo no + ERROR=1 + break + fi + echo yes + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`" + #end + + #foreach( $flags in $dependency.flags ) + #if( $flags.exec ) + $flags.value > /dev/null + if [ $? -ne 0 ]; then + $flags.varName="$$flags.varName `$flags.value`" + else + ERROR=1 + break + fi + #else + $flags.varName="$$flags.varName $flags.value" + #end + #end + #if ( $dependency.make.length() > 0 ) + cat >> $TEMP_DIR/make.mk << __EOF__ +$dependency.make +__EOF__ + #end + + break + done + + break +done +#end + +# add general dependency flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi +#end + +# +# OPTION VALUES +# +#foreach( $opt in $options ) +#foreach( $val in $opt.values ) +${val.func}() +{ + VERR=0 + #foreach( $dep in $val.dependencies ) + dependency_$dep + if [ $? -ne 0 ]; then + VERR=1 + fi + #end + if [ $VERR -ne 0 ]; then + return 1 + fi + #foreach( $def in $val.defines ) + CFLAGS="$CFLAGS ${def.toFlags()}" + #end + #if( $val.hasMake() ) + cat >> $TEMP_DIR/make.mk << __EOF__ +$val.make +__EOF__ + #end + return 0 +} +#end +#end + +# +# TARGETS +# +CFLAGS= +CXXFLAGS= +LDFLAGS= + +#foreach( $target in $targets ) +#if ( $target.name ) +# Target: $target.name +#else +# Target +#end +CFLAGS= +LDFLAGS= +CXXFLAGS= + +#foreach( $dependency in $target.dependencies ) +dependency_$dependency +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 +fi +#end + +# Features +#foreach( $feature in $target.features ) +if [ ! -z "$${feature.getVarName()}" ]; then +#foreach( $dependency in $feature.dependencies ) + # check dependency + dependency_$dependency + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $${feature.getVarName()} != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 + fi + fi +#end +fi +#end + +#foreach( $opt in $target.options ) +# Option: --${opt.argument} +if [ -z ${D}${opt.getVarName()} ]; then + SAVED_ERROR=$ERROR + SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED + ERROR=0 + while true + do + #foreach( $optdef in $opt.defaults ) + #if( $optdef.platform ) + if isplatform "$optdef.platform"; then + #end + $optdef.func + if [ $? -eq 0 ]; then + echo " ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options + ERROR=0 + break + fi + #if( $optdef.platform ) + fi + #end + #end + break + done + if [ $ERROR -ne 0 ]; then + SAVED_ERROR=1 + fi + ERROR=$SAVED_ERROR + DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED= +else + if false; then + false + #foreach( $optval in $opt.values ) + elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then + echo " ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options + $optval.func + if [ $? -ne 0 ]; then + ERROR=1 + fi + #end + fi +fi +#end + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "${target.getCFlags()} += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +#end +if [ $ERROR -ne 0 ]; then + echo + echo "Error: Unresolved dependencies" + echo $DEPENDENCIES_FAILED + rm -Rf $TEMP_DIR + exit 1 +fi + +echo "configure finished" +echo +echo "Build Config:" +echo " PREFIX: $PREFIX" +echo " TOOLCHAIN: $TOOLCHAIN_NAME" +#if ( $options.size() > 0 ) +echo "Options:" +cat $TEMP_DIR/options +#end +echo +cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk +rm -Rf $TEMP_DIR + + diff --git a/make/gcc.mk b/make/gcc.mk new file mode 100644 index 0000000..624bdf1 --- /dev/null +++ b/make/gcc.mk @@ -0,0 +1,10 @@ +# +# gcc toolchain config +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared + diff --git a/make/mingw.mk b/make/mingw.mk new file mode 100644 index 0000000..340102e --- /dev/null +++ b/make/mingw.mk @@ -0,0 +1,46 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm +MSBUILD = MSBuild.exe + +CFLAGS = -std=gnu99 -c -O2 -m64 +COFLAGS = -o +LDFLAGS = +LOFLAGS = -o +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = .exe + +PACKAGE_SCRIPT = package_windows.sh \ No newline at end of file diff --git a/make/osx.mk b/make/osx.mk new file mode 100644 index 0000000..0db5e1c --- /dev/null +++ b/make/osx.mk @@ -0,0 +1,43 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS += -std=gnu99 -g -I/usr/include/libxml2 +LDFLAGS += -lxml2 -lz -lpthread -licucore -lm +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = + +PACKAGE_SCRIPT = package_osx.sh diff --git a/make/package_unix.sh b/make/package_unix.sh new file mode 100755 index 0000000..13f4793 --- /dev/null +++ b/make/package_unix.sh @@ -0,0 +1,2 @@ +#!/bin/sh + diff --git a/make/project.xml b/make/project.xml new file mode 100644 index 0000000..cc20939 --- /dev/null +++ b/make/project.xml @@ -0,0 +1,147 @@ + + + + + gtk+-3.0 + -DUI_GTK3 + -lpthread + + + + -DUI_MOTIF + -lXm -lXt -lX11 -lpthread + + + + -I/mingw/include + -lcurl + + + curl-config --cflags + curl-config --ldflags + + + libcurl + + + which curl-config + curl-config --cflags + curl-config --ldflags + + + + xml2-config --cflags + xml2-config --libs + + + xml2-config --cflags + xml2-config --libs + + + libxml-2.0 + + + xml2-config --cflags + xml2-config --libs + + + + -lssl -lcrypto + + + -framework CoreFoundation + + + -lssl -lcrypto + + + openssl + + + + + OBJ_EXT = o + LIB_EXT = a + PACKAGE_SCRIPT = package_unix.sh + + + + -lpthread + + + + -I/usr/local/include + -L/usr/local/lib + + + + + + + + curl,libxml2,openssl + + + diff --git a/make/suncc.mk b/make/suncc.mk new file mode 100644 index 0000000..a97fe3c --- /dev/null +++ b/make/suncc.mk @@ -0,0 +1,10 @@ +# +# suncc toolchain +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -Kpic +SHLIB_LDFLAGS = -G + diff --git a/make/toolchain.sh b/make/toolchain.sh new file mode 100644 index 0000000..8e9a529 --- /dev/null +++ b/make/toolchain.sh @@ -0,0 +1,181 @@ +#!/bin/sh +# +# toolchain detection +# + +C_COMPILERS="cc gcc clang suncc" +CPP_COMPILERS="CC g++ clang++ sunCC" +unset CC_ARG_CHECKED +unset TOOLCHAIN_DETECTION_ERROR +unset TOOLCHAIN_NAME + +check_c_compiler() +{ + cat > $TEMP_DIR/test.c << __EOF__ +/* test file */ +#include +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 +int main(int argc, char **argv) { +#if defined(__clang__) + std::cout << "clang" << std::endl; +#elif defined(__GNUC__) + std::cout << "gcc" << std::endl; +#elif defined(__sun) + std::cout << "suncc" << std::endl; +#else + std::cout << "unknown" << std::endl; +#endif + return 0; +} +__EOF__ + rm -f $TEMP_DIR/checkcc + $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null + + if [ $? -ne 0 ]; then + return 1 + fi + return 0 +} + +printf "detect C compiler... " + +for COMP in $C_COMPILERS +do + check_c_compiler $COMP + if [ $? -ne 0 ]; then + if [ ! -z "$CC" ]; then + if [ $COMP = $CC ]; then + echo "$CC is not a working C Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi + fi + else + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME + if [ $COMP = "cc" ]; then + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_c_compiler "gcc" + if [ $? -eq 0 ]; then + COMP=gcc + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_c_compiler "clang" + if [ $? -eq 0 ]; then + COMP=clang + USE_TOOLCHAIN="clang" + fi + fi + fi + + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CC=$COMP + echo $COMP + break + fi +done +if [ -z $TOOLCHAIN_CC ]; then + echo "not found" +fi + +printf "detect C++ compiler... " + +for COMP in $CPP_COMPILERS +do + check_cpp_compiler $COMP + if [ $? -ne 0 ]; then + if [ ! -z "$CXX" ]; then + if [ $COMP = $CXX ]; then + echo "$CC is not a working C++ Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi + fi + else + if [ $COMP = "CC" ]; then + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_cpp_compiler "g++" + if [ $? -eq 0 ]; then + COMP=g++ + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_cpp_compiler "clang++" + if [ $? -eq 0 ]; then + COMP=clang++ + USE_TOOLCHAIN="clang" + fi + fi + fi + + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CXX=$COMP + echo $COMP + break + fi +done +if [ -z $TOOLCHAIN_CXX ]; then + echo "not found" +fi + +TOOLCHAIN_LD=$TOOLCHAIN_CC + +if [ -z "$TOOLCHAIN_NAME" ]; then + TOOLCHAIN_DETECTION_ERROR="error" +else + cat >> $TEMP_DIR/config.mk << __EOF__ +# toolchain +__EOF__ + echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk + if [ ! -z "$TOOLCHAIN_CXX" ]; then + echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk + fi + echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk + echo >> $TEMP_DIR/config.mk + + cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk + else + echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk + echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk + fi +fi diff --git a/make/windows.mk b/make/windows.mk new file mode 100644 index 0000000..2f3bc72 --- /dev/null +++ b/make/windows.mk @@ -0,0 +1,42 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS = -std=gnu99 +LDFLAGS = +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = obj +LIB_EXT = lib +APP_EXT = .exe + diff --git a/mizucp/Makefile b/mizucp/Makefile new file mode 100644 index 0000000..a8a1898 --- /dev/null +++ b/mizucp/Makefile @@ -0,0 +1,45 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = .. +include $(BUILD_ROOT)/config.mk + +CFLAGS += -I../ucx -I.. + +SRC = main.c + +OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/mizucp/%.$(OBJ_EXT)) + +all: $(BUILD_ROOT)/build/bin/mizucp + +$(BUILD_ROOT)/build/bin/mizucp: $(OBJ) $(BUILD_ROOT)/build/lib/libidav.a + $(LD) -o $(BUILD_ROOT)/build/bin/mizucp$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -lidav -lucx $(LDFLAGS) $(DAV_LDFLAGS) + +$(BUILD_ROOT)/build/mizucp/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(DAV_CFLAGS) -o $@ -c $< + diff --git a/mizucp/main.c b/mizucp/main.c new file mode 100644 index 0000000..9dc305a --- /dev/null +++ b/mizucp/main.c @@ -0,0 +1,367 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "main.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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;ibuf + 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= CLIENT_UPDATE_INTERVALL) { + for(int i=1;i 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;ipos;i++) { + if(client->buf[i] == '\n') { + sstr_t msg; + msg.ptr = &client->buf[msgstart]; + msg.length = i - msgstart; + msgstart = i+1; + + int msgret = handle_client_msg(client, msg); + if(msgret) return msgret; + } + } + + if(msgstart < client->pos) { + // incomplete message + memmove(client->buf, client->buf + msgstart, client->pos - msgstart); + client->pos -= msgstart; + } else { + client->pos = 0; + } + + return 0; +} + +int handle_client_msg(CtrlClient *client, sstr_t msg) { + printf("msg: %.*s\n", (int)msg.length, msg.ptr); + + if(!sstrcmp(msg, S("abort"))) { + return -1; + } + + return 0; +} + +void client_send_status(CtrlClient *client) { + char *msg = "s 0\n"; + write(client->fd, msg, strlen(msg)); +} diff --git a/mizucp/main.h b/mizucp/main.h new file mode 100644 index 0000000..9192835 --- /dev/null +++ b/mizucp/main.h @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAIN_H +#define MAIN_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UWCP_ENV_HOME "HOME" +#define UWCP_CFG_DIR ".uwfile" +#define UWCP_COPY_DIR "copy" + +#define CLIENT_MSG_BUFSIZE 512 + +typedef char CPBool; + +typedef struct { + char *from; + char *to; + CPBool url; + CPBool pause; + CPBool printsocket; +} CPSettings; + +typedef struct { + int fd; + char buf[CLIENT_MSG_BUFSIZE]; + size_t pos; +} CtrlClient; + +int uwcp_copy(CPSettings *settings); + +int uwcp_srvctrl(CPSettings *settings); + +void client_free(CtrlClient *client); + +int handle_messages(CtrlClient *client); +int handle_client_msg(CtrlClient *client, sstr_t msg); + +void client_send_status(CtrlClient *client); + +#ifdef __cplusplus +} +#endif + +#endif /* MAIN_H */ + diff --git a/mizunara/Makefile b/mizunara/Makefile new file mode 100644 index 0000000..5656808 --- /dev/null +++ b/mizunara/Makefile @@ -0,0 +1,45 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2021 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = .. +include $(BUILD_ROOT)/config.mk + +CFLAGS += -I../ui/ -I../ucx -I.. + +SRC = main.c + +OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/mizunara/%.$(OBJ_EXT)) + +all: $(BUILD_ROOT)/build/bin/mizunara + +$(BUILD_ROOT)/build/bin/mizunara: $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a + $(LD) -o $(BUILD_ROOT)/build/bin/mizunara$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS) + +$(BUILD_ROOT)/build/mizunara/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $< + diff --git a/mizunara/main.c b/mizunara/main.c new file mode 100644 index 0000000..ba20e28 --- /dev/null +++ b/mizunara/main.c @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +void action_menu(UiEvent *event, void *userdata) { + +} + + +void application_startup(UiEvent *event, void *data) { + + UiObject *obj = ui_window("Test", NULL); + + + ui_show(obj); +} + +int main(int argc, char** argv) { + ui_init("app1", argc, argv); + ui_onstartup(application_startup, NULL); + + // menu + ui_menu("File"); + ui_menuitem("Hello", action_menu, NULL); + ui_submenu("Submenu1"); + ui_submenu("Submenu2"); + ui_menuitem("item2", action_menu, NULL); + ui_submenu_end(); + ui_menuitem("item3", action_menu, NULL); + ui_submenu_end(); + ui_menuitem("item4", action_menu, NULL); + + + ui_main(); + + return (EXIT_SUCCESS); +} diff --git a/resource/.DS_Store b/resource/.DS_Store new file mode 100644 index 0000000..2bdfb30 Binary files /dev/null and b/resource/.DS_Store differ diff --git a/resource/locales/de_DE.properties b/resource/locales/de_DE.properties new file mode 100644 index 0000000..7ab93f2 --- /dev/null +++ b/resource/locales/de_DE.properties @@ -0,0 +1 @@ +hello = HALLO WELT! diff --git a/resource/locales/en_EN.properties b/resource/locales/en_EN.properties new file mode 100644 index 0000000..3e033cf --- /dev/null +++ b/resource/locales/en_EN.properties @@ -0,0 +1 @@ +hello = HELLO WORLD! diff --git a/resource/template.app/Contents/Info.plist b/resource/template.app/Contents/Info.plist new file mode 100644 index 0000000..d23a9e1 --- /dev/null +++ b/resource/template.app/Contents/Info.plist @@ -0,0 +1,71 @@ + + + + + BuildMachineOSBuild + 10K549 + CFBundleDevelopmentRegion + de_DE + CFBundleExecutable + mk12 + CFBundleIdentifier + com.yourcompany.toolkit + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + toolkit + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + DTCompiler + + DTPlatformBuild + 10M2518 + DTPlatformVersion + PG + DTSDKBuild + 10M2518 + DTSDKName + macosx10.6 + DTXcode + 0400 + DTXcodeBuild + 10M2518 + LSMinimumSystemVersion + 10.7 + NSMainNibFile + MainMenu + CFBundleDisplayName + + CFBundleGetInfoString + + LSApplicationCategoryType + + CFBundleDocumentTypes + + + LSItemContentTypes + + public.data + + CFBundleTypeIconFile + + CFBundleTypeName + DocumentType + CFBundleTypeRole + Editor + + + + NSPrincipalClass + NSApplication + + diff --git a/resource/template.app/Contents/PkgInfo b/resource/template.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/resource/template.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..dea12de Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings differ diff --git a/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib new file mode 100644 index 0000000..69e866a Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib differ diff --git a/ucx/Makefile b/ucx/Makefile new file mode 100644 index 0000000..2369197 --- /dev/null +++ b/ucx/Makefile @@ -0,0 +1,62 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2013 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../ +include ../config.mk + +# list of source files +SRC = utils.c +SRC += list.c +SRC += map.c +SRC += avl.c +SRC += properties.c +SRC += mempool.c +SRC += string.c +SRC += test.c +SRC += allocator.c +SRC += logging.c +SRC += buffer.c +SRC += stack.c +SRC += ucx.c +SRC += array.c + +OBJ = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT)) + +UCX_LIB = ../build/lib/libucx.$(LIB_EXT) + +all: ../build/ucx $(UCX_LIB) + +$(UCX_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ) + +../build/ucx: + mkdir -p ../build/ucx + +../build/ucx/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) -o $@ -c $< + diff --git a/ucx/README b/ucx/README new file mode 100644 index 0000000..d0890d0 --- /dev/null +++ b/ucx/README @@ -0,0 +1,4 @@ +UCX is a library for common data structures, algorithms and string functions. + +More informations at: https://develop.uap-core.de/ucx/ + diff --git a/ucx/allocator.c b/ucx/allocator.c new file mode 100644 index 0000000..22a5cd5 --- /dev/null +++ b/ucx/allocator.c @@ -0,0 +1,60 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/allocator.h" + +#include + +static UcxAllocator default_allocator = { + NULL, + ucx_default_malloc, + ucx_default_calloc, + ucx_default_realloc, + ucx_default_free +}; + +UcxAllocator *ucx_default_allocator() { + UcxAllocator *allocator = &default_allocator; + return allocator; +} + +void *ucx_default_malloc(void *ignore, size_t n) { + return malloc(n); +} + +void *ucx_default_calloc(void *ignore, size_t n, size_t size) { + return calloc(n, size); +} + +void *ucx_default_realloc(void *ignore, void *data, size_t n) { + return realloc(data, n); +} + +void ucx_default_free(void *ignore, void *data) { + free(data); +} diff --git a/ucx/array.c b/ucx/array.c new file mode 100644 index 0000000..0592fc6 --- /dev/null +++ b/ucx/array.c @@ -0,0 +1,467 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE /* we want to use qsort_r(), if available */ +#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */ + + +#include "ucx/array.h" +#include "ucx/utils.h" + +#include +#include +#include + +#ifndef UCX_ARRAY_DISABLE_QSORT +#ifdef __GLIBC__ +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8) +#define ucx_array_sort_impl qsort_r +#endif /* glibc version >= 2.8 */ +#elif /* not __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__) +#define ucx_array_sort_impl ucx_qsort_r +#define USE_UCX_QSORT_R +#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun) +#if __STDC_VERSION__ >= 201112L +#define ucx_array_sort_impl qsort_s +#endif +#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */ +#endif /* UCX_ARRAY_DISABLE_QSORT */ + +#ifndef ucx_array_sort_impl +#define ucx_array_sort_impl ucx_mergesort +#endif + +static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) { + size_t required_capacity = array->capacity; + while (reqcap > required_capacity) { + if (required_capacity * 2 < required_capacity) + return 1; + required_capacity <<= 1; + } + if (ucx_array_reserve(array, required_capacity)) { + return 1; + } + return 0; +} + +int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t elmsize, size_t index, void* data) { + + if(!alloc || !capacity || !array) { + errno = EINVAL; + return 1; + } + + size_t newcapacity = *capacity; + while(index >= newcapacity) { + if(ucx_szmul(newcapacity, 2, &newcapacity)) { + errno = EOVERFLOW; + return 1; + } + } + + size_t memlen, offset; + if(ucx_szmul(newcapacity, elmsize, &memlen)) { + errno = EOVERFLOW; + return 1; + } + /* we don't need to check index*elmsize - it is smaller than memlen */ + + + void* newptr = alrealloc(alloc, *array, memlen); + if(newptr == NULL) { + errno = ENOMEM; /* we cannot assume that every allocator sets this */ + return 1; + } + *array = newptr; + *capacity = newcapacity; + + + char* dest = *array; + dest += elmsize*index; + memcpy(dest, data, elmsize); + + return 0; +} + +int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t index, void* data) { + + return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*), + index, &data); +} + +UcxArray* ucx_array_new(size_t capacity, size_t elemsize) { + return ucx_array_new_a(capacity, elemsize, ucx_default_allocator()); +} + +UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + UcxArray* array = almalloc(allocator, sizeof(UcxArray)); + if(array) { + ucx_array_init_a(array, capacity, elemsize, allocator); + } + return array; +} + +void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) { + ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator()); +} + +void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + + array->allocator = allocator; + array->elemsize = elemsize; + array->size = 0; + array->data = alcalloc(allocator, capacity, elemsize); + + if (array->data) { + array->capacity = capacity; + } else { + array->capacity = 0; + } +} + +int ucx_array_clone(UcxArray* dest, UcxArray const* src) { + if (ucx_array_ensurecap(dest, src->capacity)) { + return 1; + } + + dest->elemsize = src->elemsize; + dest->size = src->size; + + if (dest->data) { + memcpy(dest->data, src->data, src->size*src->elemsize); + } + + return 0; +} + +int ucx_array_equals(UcxArray const *array1, UcxArray const *array2, + cmp_func cmpfnc, void* data) { + + if (array1->size != array2->size || array1->elemsize != array2->elemsize) { + return 0; + } else { + if (array1->size == 0) + return 1; + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array1->elemsize; + data = &elemsize; + } + + for (size_t i = 0 ; i < array1->size ; i++) { + int r = cmpfnc( + ucx_array_at(array1, i), + ucx_array_at(array2, i), + data); + if (r != 0) + return 0; + } + return 1; + } +} + +void ucx_array_destroy(UcxArray *array) { + if(array->data) + alfree(array->allocator, array->data); + array->data = NULL; + array->capacity = array->size = 0; +} + +void ucx_array_free(UcxArray *array) { + ucx_array_destroy(array); + alfree(array->allocator, array); +} + +int ucx_array_append_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + void* dest = ucx_array_at(array, array->size); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + if (array->size > 0) { + void *dest = ucx_array_at(array, count); + memmove(dest, array->data, array->elemsize*array->size); + } + + if (data) { + memcpy(array->data, data, array->elemsize*count); + } else { + memset(array->data, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_set_from(UcxArray *array, size_t index, + void *data, size_t count) { + if (ucx_array_ensurecap(array, index + count)) + return 1; + + if (index+count > array->size) { + array->size = index+count; + } + + void *dest = ucx_array_at(array, index); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + + return 0; +} + +int ucx_array_concat(UcxArray *array1, const UcxArray *array2) { + + if (array1->elemsize != array2->elemsize) + return 1; + + size_t capacity = array1->capacity+array2->capacity; + + if (array1->capacity < capacity) { + if (ucx_array_reserve(array1, capacity)) { + return 1; + } + } + + void* dest = ucx_array_at(array1, array1->size); + memcpy(dest, array2->data, array2->size*array2->elemsize); + + array1->size += array2->size; + + return 0; +} + +void *ucx_array_at(UcxArray const *array, size_t index) { + char* memory = array->data; + char* loc = memory + index*array->elemsize; + return loc; +} + +size_t ucx_array_find(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array->elemsize; + data = &elemsize; + } + + if (array->size > 0) { + for (size_t i = 0 ; i < array->size ; i++) { + void* ptr = ucx_array_at(array, i); + if (cmpfnc(ptr, elem, data) == 0) { + return i; + } + } + return array->size; + } else { + return 0; + } +} + +int ucx_array_contains(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + return ucx_array_find(array, elem, cmpfnc, data) != array->size; +} + +static void ucx_mergesort_merge(void *arrdata,size_t elemsize, + cmp_func cmpfnc, void *data, + size_t start, size_t mid, size_t end) { + + char* array = arrdata; + + size_t rightstart = mid + 1; + + if (cmpfnc(array + mid*elemsize, + array + rightstart*elemsize, data) <= 0) { + /* already sorted */ + return; + } + + /* we need memory for one element */ + void *value = malloc(elemsize); + + while (start <= mid && rightstart <= end) { + if (cmpfnc(array + start*elemsize, + array + rightstart*elemsize, data) <= 0) { + start++; + } else { + /* save the value from the right */ + memcpy(value, array + rightstart*elemsize, elemsize); + + /* shift all left elements one element to the right */ + size_t shiftcount = rightstart-start; + void *startptr = array + start*elemsize; + void *dest = array + (start+1)*elemsize; + memmove(dest, startptr, shiftcount*elemsize); + + /* bring the first value from the right to the left */ + memcpy(startptr, value, elemsize); + + start++; + mid++; + rightstart++; + } + } + + /* free the temporary memory */ + free(value); +} + +static void ucx_mergesort_impl(void *arrdata, size_t elemsize, + cmp_func cmpfnc, void *data, size_t l, size_t r) { + if (l < r) { + size_t m = l + (r - l) / 2; + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); + ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r); + } +} + +static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1); +} + +#ifdef USE_UCX_QSORT_R +struct cmpfnc_swapargs_info { + cmp_func func; + void *data; +}; + +static int cmp_func_swap_args(void *data, const void *x, const void *y) { + struct cmpfnc_swapargs_info* info = data; + return info->func(x, y, info->data); +} + +static void ucx_qsort_r(void *array, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + struct cmpfnc_swapargs_info info; + info.func = cmpfnc; + info.data = data; + qsort_r(array, count, elemsize, &info, cmp_func_swap_args); +} +#endif /* USE_UCX_QSORT_R */ + +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) { + ucx_array_sort_impl(array->data, array->size, array->elemsize, + cmpfnc, data); +} + +void ucx_array_remove(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, index+1); + memmove(dest, src, (array->size - index)*array->elemsize); + } +} + +void ucx_array_remove_fast(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, array->size); + memcpy(dest, src, array->elemsize); + } +} + +int ucx_array_shrink(UcxArray* array) { + void* newptr = alrealloc(array->allocator, array->data, + array->size*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = array->size; + return 0; + } else { + return 1; + } +} + +int ucx_array_resize(UcxArray* array, size_t capacity) { + if (array->capacity >= capacity) { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + if (array->size > array->capacity) { + array->size = array->capacity; + } + return 0; + } else { + return 1; + } + } else { + return ucx_array_reserve(array, capacity); + } +} + +int ucx_array_reserve(UcxArray* array, size_t capacity) { + if (array->capacity > capacity) { + return 0; + } else { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + return 0; + } else { + return 1; + } + } +} + +int ucx_array_grow(UcxArray* array, size_t count) { + return ucx_array_reserve(array, array->size+count); +} diff --git a/ucx/avl.c b/ucx/avl.c new file mode 100644 index 0000000..7639b56 --- /dev/null +++ b/ucx/avl.c @@ -0,0 +1,373 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/avl.h" + +#include + +#define ptrcast(ptr) ((void*)(ptr)) +#define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree)) +#define alloc_node(al) (UcxAVLNode*) almalloc((al), sizeof(UcxAVLNode)) + +static void ucx_avl_connect(UcxAVLTree *tree, + UcxAVLNode *node, UcxAVLNode *child, intptr_t nullkey) { + if (child) { + child->parent = node; + } + // if child is NULL, nullkey decides if left or right pointer is cleared + if (tree->cmpfunc( + ptrcast(child ? child->key : nullkey), + ptrcast(node->key), tree->userdata) > 0) { + node->right = child; + } else { + node->left = child; + } + size_t lh = node->left ? node->left->height : 0; + size_t rh = node->right ? node->right->height : 0; + node->height = 1 + (lh > rh ? lh : rh); +} + +#define avlheight(node) ((node) ? (node)->height : 0) + +static UcxAVLNode* avl_rotright(UcxAVLTree *tree, UcxAVLNode *l0) { + UcxAVLNode *p = l0->parent; + UcxAVLNode *l1 = l0->left; + if (p) { + ucx_avl_connect(tree, p, l1, 0); + } else { + l1->parent = NULL; + } + ucx_avl_connect(tree, l0, l1->right, l1->key); + ucx_avl_connect(tree, l1, l0, 0); + return l1; +} + +static UcxAVLNode* avl_rotleft(UcxAVLTree *tree, UcxAVLNode *l0) { + UcxAVLNode *p = l0->parent; + UcxAVLNode *l1 = l0->right; + if (p) { + ucx_avl_connect(tree, p, l1, 0); + } else { + l1->parent = NULL; + } + ucx_avl_connect(tree, l0, l1->left, l1->key); + ucx_avl_connect(tree, l1, l0, 0); + return l1; +} + +static void ucx_avl_balance(UcxAVLTree *tree, UcxAVLNode *n) { + int lh = avlheight(n->left); + int rh = avlheight(n->right); + n->height = 1 + (lh > rh ? lh : rh); + + if (lh - rh == 2) { + UcxAVLNode *c = n->left; + if (avlheight(c->right) - avlheight(c->left) == 1) { + avl_rotleft(tree, c); + } + n = avl_rotright(tree, n); + } else if (rh - lh == 2) { + UcxAVLNode *c = n->right; + if (avlheight(c->left) - avlheight(c->right) == 1) { + avl_rotright(tree, c); + } + n = avl_rotleft(tree, n); + } + + if (n->parent) { + ucx_avl_balance(tree, n->parent); + } else { + tree->root = n; + } +} + +UcxAVLTree *ucx_avl_new(cmp_func cmpfunc) { + return ucx_avl_new_a(cmpfunc, ucx_default_allocator()); +} + +UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator) { + UcxAVLTree* tree = alloc_tree(allocator); + if (tree) { + tree->allocator = allocator; + tree->cmpfunc = cmpfunc; + tree->root = NULL; + tree->userdata = NULL; + } + + return tree; +} + +static void ucx_avl_free_node(UcxAllocator *al, UcxAVLNode *node) { + if (node) { + ucx_avl_free_node(al, node->left); + ucx_avl_free_node(al, node->right); + alfree(al, node); + } +} + +void ucx_avl_free(UcxAVLTree *tree) { + UcxAllocator *al = tree->allocator; + ucx_avl_free_node(al, tree->root); + alfree(al, tree); +} + +static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node, + ucx_destructor destr) { + if (node) { + ucx_avl_free_content_node(al, node->left, destr); + ucx_avl_free_content_node(al, node->right, destr); + if (destr) { + destr(node->value); + } else { + alfree(al, node->value); + } + } +} + +void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) { + ucx_avl_free_content_node(tree->allocator, tree->root, destr); +} + +UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) { + UcxAVLNode *n = tree->root; + int cmpresult; + while (n && (cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + n = cmpresult > 0 ? n->right : n->left; + } + return n; +} + +void *ucx_avl_get(UcxAVLTree *tree, intptr_t key) { + UcxAVLNode *n = ucx_avl_get_node(tree, key); + return n ? n->value : NULL; +} + +UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = tree->root; + UcxAVLNode *closest = NULL; + + intmax_t cmpresult; + intmax_t closest_dist; + closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX; + + while (n && (cmpresult = dfnc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + if (mode == UCX_AVL_FIND_CLOSEST) { + intmax_t dist = cmpresult; + if (dist < 0) dist *= -1; + if (dist < closest_dist) { + closest_dist = dist; + closest = n; + } + } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) { + if (cmpresult > closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) { + if (cmpresult < closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } + n = cmpresult > 0 ? n->right : n->left; + } + return n ? n : closest; +} + +void *ucx_avl_find(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode); + return n ? n->value : NULL; +} + +int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) { + return ucx_avl_put_s(tree, key, value, NULL); +} + +int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, + void **oldvalue) { + if (tree->root) { + UcxAVLNode *n = tree->root; + int cmpresult; + while ((cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + UcxAVLNode *m = cmpresult > 0 ? n->right : n->left; + if (m) { + n = m; + } else { + break; + } + } + + if (cmpresult) { + UcxAVLNode* e = alloc_node(tree->allocator); + if (e) { + e->key = key; e->value = value; e->height = 1; + e->parent = e->left = e->right = NULL; + ucx_avl_connect(tree, n, e, 0); + ucx_avl_balance(tree, n); + return 0; + } else { + return 1; + } + } else { + if (oldvalue) { + *oldvalue = n->value; + } + n->value = value; + return 0; + } + } else { + tree->root = alloc_node(tree->allocator); + if (tree->root) { + tree->root->key = key; tree->root->value = value; + tree->root->height = 1; + tree->root->parent = tree->root->left = tree->root->right = NULL; + + if (oldvalue) { + *oldvalue = NULL; + } + + return 0; + } else { + return 1; + } + } +} + +int ucx_avl_remove(UcxAVLTree *tree, intptr_t key) { + return ucx_avl_remove_s(tree, key, NULL, NULL); +} + +int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node) { + return ucx_avl_remove_s(tree, node->key, NULL, NULL); +} + +int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key, + intptr_t *oldkey, void **oldvalue) { + + UcxAVLNode *n = tree->root; + int cmpresult; + while (n && (cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + n = cmpresult > 0 ? n->right : n->left; + } + if (n) { + if (oldkey) { + *oldkey = n->key; + } + if (oldvalue) { + *oldvalue = n->value; + } + + UcxAVLNode *p = n->parent; + if (n->left && n->right) { + UcxAVLNode *s = n->right; + while (s->left) { + s = s->left; + } + ucx_avl_connect(tree, s->parent, s->right, s->key); + n->key = s->key; n->value = s->value; + p = s->parent; + alfree(tree->allocator, s); + } else { + if (p) { + ucx_avl_connect(tree, p, n->right ? n->right:n->left, n->key); + } else { + tree->root = n->right ? n->right : n->left; + if (tree->root) { + tree->root->parent = NULL; + } + } + alfree(tree->allocator, n); + } + + if (p) { + ucx_avl_balance(tree, p); + } + + return 0; + } else { + return 1; + } +} + +static size_t ucx_avl_countn(UcxAVLNode *node) { + if (node) { + return 1 + ucx_avl_countn(node->left) + ucx_avl_countn(node->right); + } else { + return 0; + } +} + +size_t ucx_avl_count(UcxAVLTree *tree) { + return ucx_avl_countn(tree->root); +} + +UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) { + if (node->left) { + UcxAVLNode* n = node->left; + while (n->right) { + n = n->right; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->right == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} + +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) { + if (node->right) { + UcxAVLNode* n = node->right; + while (n->left) { + n = n->left; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->left == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} diff --git a/ucx/buffer.c b/ucx/buffer.c new file mode 100644 index 0000000..a6a8085 --- /dev/null +++ b/ucx/buffer.c @@ -0,0 +1,297 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/buffer.h" + +#include +#include +#include + +UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags) { + UcxBuffer *buffer = (UcxBuffer*) malloc(sizeof(UcxBuffer)); + if (buffer) { + buffer->flags = flags; + if (!space) { + buffer->space = (char*)malloc(capacity); + if (!buffer->space) { + free(buffer); + return NULL; + } + memset(buffer->space, 0, capacity); + buffer->flags |= UCX_BUFFER_AUTOFREE; + } else { + buffer->space = (char*)space; + } + buffer->capacity = capacity; + buffer->size = 0; + + buffer->pos = 0; + } + + return buffer; +} + +void ucx_buffer_free(UcxBuffer *buffer) { + if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) { + free(buffer->space); + } + free(buffer); +} + +UcxBuffer* ucx_buffer_extract( + UcxBuffer *src, size_t start, size_t length, int flags) { + if (src->size == 0 || length == 0 || + ((size_t)-1) - start < length || start+length > src->capacity) + { + return NULL; + } + + UcxBuffer *dst = (UcxBuffer*) malloc(sizeof(UcxBuffer)); + if (dst) { + dst->space = (char*)malloc(length); + if (!dst->space) { + free(dst); + return NULL; + } + dst->capacity = length; + dst->size = length; + dst->flags = flags | UCX_BUFFER_AUTOFREE; + dst->pos = 0; + memcpy(dst->space, src->space+start, length); + } + return dst; +} + +int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) { + size_t npos; + switch (whence) { + case SEEK_CUR: + npos = buffer->pos; + break; + case SEEK_END: + npos = buffer->size; + break; + case SEEK_SET: + npos = 0; + break; + default: + return -1; + } + + size_t opos = npos; + npos += offset; + + if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { + return -1; + } + + if (npos >= buffer->size) { + return -1; + } else { + buffer->pos = npos; + return 0; + } + +} + +int ucx_buffer_eof(UcxBuffer *buffer) { + return buffer->pos >= buffer->size; +} + +int ucx_buffer_extend(UcxBuffer *buffer, size_t len) { + size_t newcap = buffer->capacity; + + if (buffer->capacity + len < buffer->capacity) { + return -1; + } + + while (buffer->capacity + len > newcap) { + newcap <<= 1; + if (newcap < buffer->capacity) { + return -1; + } + } + + char *newspace = (char*)realloc(buffer->space, newcap); + if (newspace) { + memset(newspace+buffer->size, 0, newcap-buffer->size); + buffer->space = newspace; + buffer->capacity = newcap; + } else { + return -1; + } + + return 0; +} + +size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer) { + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } + size_t required = buffer->pos + len; + if (buffer->pos > required) { + return 0; + } + + if (required > buffer->capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if (ucx_buffer_extend(buffer, required - buffer->capacity)) { + return 0; + } + } else { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len%size; + } + } + } + + if (len == 0) { + return len; + } + + memcpy(buffer->space + buffer->pos, ptr, len); + buffer->pos += len; + if(buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + + return len / size; +} + +size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer) { + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } + if (buffer->pos + len > buffer->size) { + len = buffer->size - buffer->pos; + if (size > 1) len -= len%size; + } + + if (len <= 0) { + return len; + } + + memcpy(ptr, buffer->space + buffer->pos, len); + buffer->pos += len; + + return len / size; +} + +int ucx_buffer_putc(UcxBuffer *buffer, int c) { + if(buffer->pos >= buffer->capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if(ucx_buffer_extend(buffer, 1)) { + return EOF; + } + } else { + return EOF; + } + } + + c &= 0xFF; + buffer->space[buffer->pos] = (char) c; + buffer->pos++; + if(buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + return c; +} + +int ucx_buffer_getc(UcxBuffer *buffer) { + if (ucx_buffer_eof(buffer)) { + return EOF; + } else { + int c = ((unsigned char*)buffer->space)[buffer->pos]; + buffer->pos++; + return c; + } +} + +size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) { + return ucx_buffer_write((const void*)str, 1, strlen(str), buffer); +} + +int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) { + if (shift >= buffer->size) { + buffer->pos = buffer->size = 0; + } else { + memmove(buffer->space, buffer->space + shift, buffer->size - shift); + buffer->size -= shift; + + if (buffer->pos >= shift) { + buffer->pos -= shift; + } else { + buffer->pos = 0; + } + } + return 0; +} + +int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) { + size_t req_capacity = buffer->size + shift; + size_t movebytes; + + // auto extend buffer, if required and enabled + if (buffer->capacity < req_capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) { + return 1; + } + movebytes = buffer->size; + } else { + movebytes = buffer->capacity - shift; + } + } else { + movebytes = buffer->size; + } + + memmove(buffer->space + shift, buffer->space, movebytes); + buffer->size = shift+movebytes; + + buffer->pos += shift; + if (buffer->pos > buffer->size) { + buffer->pos = buffer->size; + } + + return 0; +} + +int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) { + if (shift < 0) { + return ucx_buffer_shift_left(buffer, (size_t) (-shift)); + } else if (shift > 0) { + return ucx_buffer_shift_right(buffer, (size_t) shift); + } else { + return 0; + } +} diff --git a/ucx/list.c b/ucx/list.c new file mode 100644 index 0000000..293592c --- /dev/null +++ b/ucx/list.c @@ -0,0 +1,428 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/list.h" + +UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) { + return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data); +} + +UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l, + copy_func fnc, void *data) { + UcxList *ret = NULL; + while (l) { + if (fnc) { + ret = ucx_list_append_a(alloc, ret, fnc(l->data, data)); + } else { + ret = ucx_list_append_a(alloc, ret, l->data); + } + l = l->next; + } + return ret; +} + +int ucx_list_equals(const UcxList *l1, const UcxList *l2, + cmp_func fnc, void* data) { + if (l1 == l2) return 1; + + while (l1 != NULL && l2 != NULL) { + if (fnc == NULL) { + if (l1->data != l2->data) return 0; + } else { + if (fnc(l1->data, l2->data, data) != 0) return 0; + } + l1 = l1->next; + l2 = l2->next; + } + + return (l1 == NULL && l2 == NULL); +} + +void ucx_list_free(UcxList *l) { + ucx_list_free_a(ucx_default_allocator(), l); +} + +void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) { + UcxList *e = l, *f; + while (e != NULL) { + f = e; + e = e->next; + alfree(alloc, f); + } +} + +void ucx_list_free_content(UcxList* list, ucx_destructor destr) { + if (!destr) destr = free; + while (list != NULL) { + destr(list->data); + list = list->next; + } +} + +UcxList *ucx_list_append(UcxList *l, void *data) { + return ucx_list_append_a(ucx_default_allocator(), l, data); +} + +UcxList *ucx_list_append_a(UcxAllocator *alloc, UcxList *l, void *data) { + UcxList *nl = (UcxList*) almalloc(alloc, sizeof(UcxList)); + if (!nl) { + return NULL; + } + + nl->data = data; + nl->next = NULL; + if (l) { + UcxList *t = ucx_list_last(l); + t->next = nl; + nl->prev = t; + return l; + } else { + nl->prev = NULL; + return nl; + } +} + +UcxList *ucx_list_prepend(UcxList *l, void *data) { + return ucx_list_prepend_a(ucx_default_allocator(), l, data); +} + +UcxList *ucx_list_prepend_a(UcxAllocator *alloc, UcxList *l, void *data) { + UcxList *nl = ucx_list_append_a(alloc, NULL, data); + if (!nl) { + return NULL; + } + l = ucx_list_first(l); + + if (l) { + nl->next = l; + l->prev = nl; + } + return nl; +} + +UcxList *ucx_list_concat(UcxList *l1, UcxList *l2) { + if (l1) { + UcxList *last = ucx_list_last(l1); + last->next = l2; + if (l2) { + l2->prev = last; + } + return l1; + } else { + return l2; + } +} + +UcxList *ucx_list_last(const UcxList *l) { + if (l == NULL) return NULL; + + const UcxList *e = l; + while (e->next != NULL) { + e = e->next; + } + return (UcxList*)e; +} + +ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem) { + ssize_t index = 0; + while (list) { + if (list == elem) { + return index; + } + list = list->next; + index++; + } + return -1; +} + +UcxList *ucx_list_get(const UcxList *l, size_t index) { + if (l == NULL) return NULL; + + const UcxList *e = l; + while (e->next && index > 0) { + e = e->next; + index--; + } + + return (UcxList*)(index == 0 ? e : NULL); +} + +ssize_t ucx_list_find(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { + ssize_t index = 0; + UCX_FOREACH(e, l) { + if (fnc) { + if (fnc(elem, e->data, cmpdata) == 0) { + return index; + } + } else { + if (elem == e->data) { + return index; + } + } + index++; + } + return -1; +} + +int ucx_list_contains(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { + return ucx_list_find(l, elem, fnc, cmpdata) > -1; +} + +size_t ucx_list_size(const UcxList *l) { + if (l == NULL) return 0; + + const UcxList *e = l; + size_t s = 1; + while (e->next != NULL) { + e = e->next; + s++; + } + + return s; +} + +static UcxList *ucx_list_sort_merge(size_t length, + UcxList* ls, UcxList* le, UcxList* re, + cmp_func fnc, void* data) { + + UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length); + UcxList *rc, *lc; + + lc = ls; rc = le; + size_t n = 0; + while (lc && lc != le && rc != re) { + if (fnc(lc->data, rc->data, data) <= 0) { + sorted[n] = lc; + lc = lc->next; + } else { + sorted[n] = rc; + rc = rc->next; + } + n++; + } + while (lc && lc != le) { + sorted[n] = lc; + lc = lc->next; + n++; + } + while (rc && rc != re) { + sorted[n] = rc; + rc = rc->next; + n++; + } + + // Update pointer + sorted[0]->prev = NULL; + for (int i = 0 ; i < length-1 ; i++) { + sorted[i]->next = sorted[i+1]; + sorted[i+1]->prev = sorted[i]; + } + sorted[length-1]->next = NULL; + + UcxList *ret = sorted[0]; + free(sorted); + return ret; +} + +UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) { + if (l == NULL) { + return NULL; + } + + UcxList *lc; + size_t ln = 1; + + UcxList *ls = l, *le, *re; + + // check how many elements are already sorted + lc = ls; + while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) { + lc = lc->next; + ln++; + } + le = lc->next; + + if (le == NULL) { + return l; // this list is already sorted :) + } else { + UcxList *rc; + size_t rn = 1; + rc = le; + // skip already sorted elements + while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) { + rc = rc->next; + rn++; + } + re = rc->next; + + // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them + UcxList *sorted = ucx_list_sort_merge(ln+rn, + ls, le, re, + fnc, data); + + // Something left? Sort it! + size_t remainder_length = ucx_list_size(re); + if (remainder_length > 0) { + UcxList *remainder = ucx_list_sort(re, fnc, data); + + // merge sorted list with (also sorted) remainder + l = ucx_list_sort_merge(ln+rn+remainder_length, + sorted, remainder, NULL, fnc, data); + } else { + // no remainder - we've got our sorted list + l = sorted; + } + + return l; + } +} + +UcxList *ucx_list_first(const UcxList *l) { + if (!l) { + return NULL; + } + + const UcxList *e = l; + while (e->prev) { + e = e->prev; + } + return (UcxList *)e; +} + +UcxList *ucx_list_remove(UcxList *l, UcxList *e) { + return ucx_list_remove_a(ucx_default_allocator(), l, e); +} + +UcxList *ucx_list_remove_a(UcxAllocator *alloc, UcxList *l, UcxList *e) { + if (l == e) { + l = e->next; + } + + if (e->next) { + e->next->prev = e->prev; + } + + if (e->prev) { + e->prev->next = e->next; + } + + alfree(alloc, e); + return l; +} + + +static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata, + int op) { + + UcxList *res = NULL; + UcxList *cur = NULL; + const UcxList *src = left; + + do { + UCX_FOREACH(node, src) { + void* elem = node->data; + if ( + (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) || + (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) || + (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) { + UcxList *nl = almalloc(allocator, sizeof(UcxList)); + nl->prev = cur; + nl->next = NULL; + if (cpfnc) { + nl->data = cpfnc(elem, cpdata); + } else { + nl->data = elem; + } + if (cur != NULL) + cur->next = nl; + cur = nl; + if (res == NULL) + res = cur; + } + } + if (op == 0 && src == left) + src = right; + else + src = NULL; + } while (src != NULL); + + return res; +} + +UcxList* ucx_list_union(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_union_a(ucx_default_allocator(), + left, right, cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_union_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 0); +} + +UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_intersection_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 1); +} + +UcxList* ucx_list_difference(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_difference_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 2); +} diff --git a/ucx/logging.c b/ucx/logging.c new file mode 100644 index 0000000..d6fdce0 --- /dev/null +++ b/ucx/logging.c @@ -0,0 +1,117 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/logging.h" + +#include +#include +#include +#include + +UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask) { + UcxLogger *logger = (UcxLogger*) malloc(sizeof(UcxLogger)); + if (logger != NULL) { + logger->stream = stream; + logger->writer = (write_func)fwrite; + logger->dateformat = (char*) "%F %T %z "; + logger->level = level; + logger->mask = mask; + logger->levels = ucx_map_new(8); + + unsigned int l; + l = UCX_LOGGER_ERROR; + ucx_map_int_put(logger->levels, l, (void*) "[ERROR]"); + l = UCX_LOGGER_WARN; + ucx_map_int_put(logger->levels, l, (void*) "[WARNING]"); + l = UCX_LOGGER_INFO; + ucx_map_int_put(logger->levels, l, (void*) "[INFO]"); + l = UCX_LOGGER_DEBUG; + ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]"); + l = UCX_LOGGER_TRACE; + ucx_map_int_put(logger->levels, l, (void*) "[TRACE]"); + } + + return logger; +} + +void ucx_logger_free(UcxLogger *logger) { + ucx_map_free(logger->levels); + free(logger); +} + +// estimated max. message length (documented) +#define UCX_LOGGER_MSGMAX 4096 + +void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, + const unsigned int line, const char *format, ...) { + if (level <= logger->level) { + char msg[UCX_LOGGER_MSGMAX]; + const char *text; + size_t k = 0; + size_t n; + + if ((logger->mask & UCX_LOGGER_LEVEL) > 0) { + text = (const char*) ucx_map_int_get(logger->levels, level); + if (!text) { + text = "[UNKNOWN]"; + } + n = strlen(text); + n = n > 256 ? 256 : n; + memcpy(msg+k, text, n); + k += n; + msg[k++] = ' '; + } + if ((logger->mask & UCX_LOGGER_TIMESTAMP) > 0) { + time_t now = time(NULL); + k += strftime(msg+k, 128, logger->dateformat, localtime(&now)); + } + if ((logger->mask & UCX_LOGGER_SOURCE) > 0) { + char *fpart = strrchr(file, '/'); + if (fpart) file = fpart+1; + fpart = strrchr(file, '\\'); + if (fpart) file = fpart+1; + n = strlen(file); + memcpy(msg+k, file, n); + k += n; + k += sprintf(msg+k, ":%u ", line); + } + + if (k > 0) { + msg[k++] = '-'; msg[k++] = ' '; + } + + va_list args; + va_start (args, format); + k += vsnprintf(msg+k, UCX_LOGGER_MSGMAX-k-1, format, args); + va_end (args); + + msg[k++] = '\n'; + + logger->writer(msg, 1, k, logger->stream); + } +} diff --git a/ucx/map.c b/ucx/map.c new file mode 100644 index 0000000..ba7961d --- /dev/null +++ b/ucx/map.c @@ -0,0 +1,402 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/map.h" + +#include +#include + +UcxMap *ucx_map_new(size_t size) { + return ucx_map_new_a(NULL, size); +} + +UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size) { + if(size == 0) { + size = 16; + } + + if(!allocator) { + allocator = ucx_default_allocator(); + } + + UcxMap *map = (UcxMap*)almalloc(allocator, sizeof(UcxMap)); + if (!map) { + return NULL; + } + + map->allocator = allocator; + map->map = (UcxMapElement**)alcalloc( + allocator, size, sizeof(UcxMapElement*)); + if(map->map == NULL) { + alfree(allocator, map); + return NULL; + } + map->size = size; + map->count = 0; + + return map; +} + +static void ucx_map_free_elmlist_contents(UcxMap *map) { + for (size_t n = 0 ; n < map->size ; n++) { + UcxMapElement *elem = map->map[n]; + if (elem != NULL) { + do { + UcxMapElement *next = elem->next; + alfree(map->allocator, elem->key.data); + alfree(map->allocator, elem); + elem = next; + } while (elem != NULL); + } + } +} + +void ucx_map_free(UcxMap *map) { + ucx_map_free_elmlist_contents(map); + alfree(map->allocator, map->map); + alfree(map->allocator, map); +} + +void ucx_map_free_content(UcxMap *map, ucx_destructor destr) { + UcxMapIterator iter = ucx_map_iterator(map); + void *val; + UCX_MAP_FOREACH(key, val, iter) { + if (destr) { + destr(val); + } else { + alfree(map->allocator, val); + } + } +} + +void ucx_map_clear(UcxMap *map) { + if (map->count == 0) { + return; // nothing to do + } + ucx_map_free_elmlist_contents(map); + memset(map->map, 0, map->size*sizeof(UcxMapElement*)); + map->count = 0; +} + +int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) { + UcxMapIterator i = ucx_map_iterator(from); + void *value; + UCX_MAP_FOREACH(key, value, i) { + if (ucx_map_put(to, key, fnc ? fnc(value, data) : value)) { + return 1; + } + } + return 0; +} + +UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) { + return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data); +} + +UcxMap *ucx_map_clone_a(UcxAllocator *allocator, + UcxMap const *map, copy_func fnc, void *data) { + size_t bs = (map->count * 5) >> 1; + UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size); + if (!newmap) { + return NULL; + } + ucx_map_copy(map, newmap, fnc, data); + return newmap; +} + +int ucx_map_rehash(UcxMap *map) { + size_t load = (map->size * 3) >> 2; + if (map->count > load) { + UcxMap oldmap; + oldmap.map = map->map; + oldmap.size = map->size; + oldmap.count = map->count; + oldmap.allocator = map->allocator; + + map->size = (map->count * 5) >> 1; + map->map = (UcxMapElement**)alcalloc( + map->allocator, map->size, sizeof(UcxMapElement*)); + if (!map->map) { + *map = oldmap; + return 1; + } + map->count = 0; + ucx_map_copy(&oldmap, map, NULL, NULL); + + /* free the UcxMapElement list of oldmap */ + ucx_map_free_elmlist_contents(&oldmap); + alfree(map->allocator, oldmap.map); + } + return 0; +} + +int ucx_map_put(UcxMap *map, UcxKey key, void *data) { + UcxAllocator *allocator = map->allocator; + + if (key.hash == 0) { + key.hash = ucx_hash((const char*)key.data, key.len); + } + + struct UcxMapKey mapkey; + mapkey.hash = key.hash; + + size_t slot = mapkey.hash%map->size; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *prev = NULL; + + while (elm && elm->key.hash < mapkey.hash) { + prev = elm; + elm = elm->next; + } + + if (!elm || elm->key.hash != mapkey.hash) { + UcxMapElement *e = (UcxMapElement*)almalloc( + allocator, sizeof(UcxMapElement)); + if (!e) { + return -1; + } + e->key.data = NULL; + if (prev) { + prev->next = e; + } else { + map->map[slot] = e; + } + e->next = elm; + elm = e; + } + + if (!elm->key.data) { + void *kd = almalloc(allocator, key.len); + if (!kd) { + return -1; + } + memcpy(kd, key.data, key.len); + mapkey.data = kd; + mapkey.len = key.len; + elm->key = mapkey; + map->count++; + } + elm->data = data; + + return 0; +} + +static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) { + if(key.hash == 0) { + key.hash = ucx_hash((const char*)key.data, key.len); + } + + size_t slot = key.hash%map->size; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *pelm = NULL; + while (elm && elm->key.hash <= key.hash) { + if(elm->key.hash == key.hash) { + int n = (key.len > elm->key.len) ? elm->key.len : key.len; + if (memcmp(elm->key.data, key.data, n) == 0) { + void *data = elm->data; + if (remove) { + if (pelm) { + pelm->next = elm->next; + } else { + map->map[slot] = elm->next; + } + alfree(map->allocator, elm->key.data); + alfree(map->allocator, elm); + map->count--; + } + + return data; + } + } + pelm = elm; + elm = pelm->next; + } + + return NULL; +} + +void *ucx_map_get(UcxMap const *map, UcxKey key) { + return ucx_map_get_and_remove((UcxMap *)map, key, 0); +} + +void *ucx_map_remove(UcxMap *map, UcxKey key) { + return ucx_map_get_and_remove(map, key, 1); +} + +UcxKey ucx_key(const void *data, size_t len) { + UcxKey key; + key.data = data; + key.len = len; + key.hash = ucx_hash((const char*)data, len); + return key; +} + + +int ucx_hash(const char *data, size_t len) { + /* murmur hash 2 */ + + int m = 0x5bd1e995; + int r = 24; + + int h = 25 ^ len; + + int i = 0; + while (len >= 4) { + int k = data[i + 0] & 0xFF; + k |= (data[i + 1] & 0xFF) << 8; + k |= (data[i + 2] & 0xFF) << 16; + k |= (data[i + 3] & 0xFF) << 24; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + i += 4; + len -= 4; + } + + switch (len) { + case 3: h ^= (data[i + 2] & 0xFF) << 16; + /* no break */ + case 2: h ^= (data[i + 1] & 0xFF) << 8; + /* no break */ + case 1: h ^= (data[i + 0] & 0xFF); h *= m; + /* no break */ + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +UcxMapIterator ucx_map_iterator(UcxMap const *map) { + UcxMapIterator i; + i.map = map; + i.cur = NULL; + i.index = 0; + return i; +} + +int ucx_map_iter_next(UcxMapIterator *i, UcxKey *key, void **elm) { + UcxMapElement *e = i->cur; + + if (e) { + e = e->next; + } else { + e = i->map->map[0]; + } + + while (i->index < i->map->size) { + if (e) { + if (e->data) { + i->cur = e; + *elm = e->data; + key->data = e->key.data; + key->hash = e->key.hash; + key->len = e->key.len; + return 1; + } + + e = e->next; + } else { + i->index++; + + if (i->index < i->map->size) { + e = i->map->map[i->index]; + } + } + } + + return 0; +} + +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_union_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata); + ucx_map_copy(second, result, cpfnc, cpdata); + return result; +} + +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_intersection_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ? + first->size : second->size); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + return result; +} + +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_difference_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + + UcxMap *result = ucx_map_new_a(allocator, first->size - second->count); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (!ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + ucx_map_rehash(result); + return result; +} \ No newline at end of file diff --git a/ucx/mempool.c b/ucx/mempool.c new file mode 100644 index 0000000..beedc31 --- /dev/null +++ b/ucx/mempool.c @@ -0,0 +1,237 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/mempool.h" + +#include +#include +#include +#ifdef __cplusplus +#define __STDC_FORMAT_MACROS +#endif +#include + +/** 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 &c 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 ; indata ; 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 ; indata ; i++) { + chunk = (ucx_memchunk*) pool->data[i]; + if(chunk) { + if(chunk->destructor) { + chunk->destructor(&(chunk->c)); + } + free(chunk); + } + } + free(pool->data); + free(pool->allocator); + free(pool); +} + +void ucx_mempool_set_destr(void *ptr, ucx_destructor func) { + *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func; +} + +void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) { + ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc( + pool, + sizeof(ucx_regdestr)); + rd->destructor = destr; + rd->ptr = ptr; + ucx_mempool_set_destr(rd, ucx_mempool_shared_destr); +} + diff --git a/ucx/properties.c b/ucx/properties.c new file mode 100644 index 0000000..1cb4de0 --- /dev/null +++ b/ucx/properties.c @@ -0,0 +1,264 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/properties.h" + +#include +#include +#include + +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(;itmpcap = len + 128; + parser->tmp = (char*)malloc(parser->tmpcap); + parser->tmplen = len; + memcpy(parser->tmp, buf, len); + return 0; + } + + sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i); + // check line + if(delimiter_index == 0) { + line = sstrtrim(line); + if(line.length != 0) { + parser->error = 1; + } + } else { + sstr_t n = sstrn(buf, delimiter_index); + sstr_t v = sstrn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + n = sstrtrim(n); + v = sstrtrim(v); + if(n.length != 0 || v.length != 0) { + *name = n; + *value = v; + parser->pos += i + 1; + return 1; + } else { + parser->error = 1; + } + } + + parser->pos += i + 1; + } + + return 0; +} + +int ucx_properties2map(UcxProperties *parser, UcxMap *map) { + sstr_t name; + sstr_t value; + while(ucx_properties_next(parser, &name, &value)) { + value = sstrdup_a(map->allocator, value); + if(!value.ptr) { + return 1; + } + if(ucx_map_sstr_put(map, name, value.ptr)) { + alfree(map->allocator, value.ptr); + return 1; + } + } + if (parser->error) { + return parser->error; + } else { + return 0; + } +} + +// buffer size is documented - change doc, when you change bufsize! +#define UCX_PROPLOAD_BUFSIZE 1024 +int ucx_properties_load(UcxMap *map, FILE *file) { + UcxProperties *parser = ucx_properties_new(); + if(!(parser && map && file)) { + return 1; + } + + int error = 0; + size_t r; + char buf[UCX_PROPLOAD_BUFSIZE]; + while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) { + ucx_properties_fill(parser, buf, r); + error = ucx_properties2map(parser, map); + if (error) { + break; + } + } + ucx_properties_free(parser); + return error; +} + +int ucx_properties_store(UcxMap *map, FILE *file) { + UcxMapIterator iter = ucx_map_iterator(map); + void *v; + sstr_t value; + size_t written; + + UCX_MAP_FOREACH(k, v, iter) { + value = sstr((char*)v); + + written = 0; + written += fwrite(k.data, 1, k.len, file); + written += fwrite(" = ", 1, 3, file); + written += fwrite(value.ptr, 1, value.length, file); + written += fwrite("\n", 1, 1, file); + + if (written != k.len + value.length + 4) { + return 1; + } + } + + return 0; +} + diff --git a/ucx/stack.c b/ucx/stack.c new file mode 100644 index 0000000..467233e --- /dev/null +++ b/ucx/stack.c @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/stack.h" + +#include + +static size_t ucx_stack_align(size_t n) { + int align = n % sizeof(void*); + if (align) { + n += sizeof(void*) - align; + } + return n; +} + +void ucx_stack_init(UcxStack *stack, char* space, size_t size) { + stack->size = size - size % sizeof(void*); + stack->space = space; + stack->top = NULL; + + stack->allocator.pool = stack; + stack->allocator.malloc = (ucx_allocator_malloc) ucx_stack_malloc; + stack->allocator.calloc = (ucx_allocator_calloc) ucx_stack_calloc; + stack->allocator.realloc = (ucx_allocator_realloc) ucx_stack_realloc; + stack->allocator.free = (ucx_allocator_free) ucx_stack_free; +} + +void *ucx_stack_malloc(UcxStack *stack, size_t n) { + + if (ucx_stack_avail(stack) < ucx_stack_align(n)) { + return NULL; + } else { + char *prev = stack->top; + if (stack->top) { + stack->top += ucx_stack_align(ucx_stack_topsize(stack)); + } else { + stack->top = stack->space; + } + + ((struct ucx_stack_metadata*)stack->top)->prev = prev; + ((struct ucx_stack_metadata*)stack->top)->size = n; + stack->top += sizeof(struct ucx_stack_metadata); + + return stack->top; + } +} + +void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize) { + void *mem = ucx_stack_malloc(stack, nelem*elsize); + memset(mem, 0, nelem*elsize); + return mem; +} + +void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n) { + if (ptr == stack->top) { + if (stack->size - (stack->top - stack->space) < ucx_stack_align(n)) { + return NULL; + } else { + ((struct ucx_stack_metadata*)stack->top - 1)->size = n; + return ptr; + } + } else { + if (ucx_stack_align(((struct ucx_stack_metadata*)ptr - 1)->size) < + ucx_stack_align(n)) { + void *nptr = ucx_stack_malloc(stack, n); + if (nptr) { + memcpy(nptr, ptr, n); + ucx_stack_free(stack, ptr); + + return nptr; + } else { + return NULL; + } + } else { + ((struct ucx_stack_metadata*)ptr - 1)->size = n; + return ptr; + } + } +} + +void ucx_stack_free(UcxStack *stack, void *ptr) { + if (ptr == stack->top) { + stack->top = ((struct ucx_stack_metadata*) stack->top - 1)->prev; + } else { + struct ucx_stack_metadata *next = (struct ucx_stack_metadata*)( + (char*)ptr + + ucx_stack_align(((struct ucx_stack_metadata*) ptr - 1)->size) + ); + next->prev = ((struct ucx_stack_metadata*) ptr - 1)->prev; + } +} + +void ucx_stack_popn(UcxStack *stack, void *dest, size_t n) { + if (ucx_stack_empty(stack)) { + return; + } + + if (dest) { + size_t len = ucx_stack_topsize(stack); + if (len > n) { + len = n; + } + + memcpy(dest, stack->top, len); + } + + ucx_stack_free(stack, stack->top); +} + +size_t ucx_stack_avail(UcxStack *stack) { + size_t avail = ((stack->top ? (stack->size + - (stack->top - stack->space) + - ucx_stack_align(ucx_stack_topsize(stack))) + : stack->size)); + + if (avail > sizeof(struct ucx_stack_metadata)) { + return avail - sizeof(struct ucx_stack_metadata); + } else { + return 0; + } +} + +void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) { + void *space = ucx_stack_malloc(stack, n); + if (space) { + memcpy(space, data, n); + } + return space; +} + +void *ucx_stack_pusharr(UcxStack *stack, + size_t nelem, size_t elsize, const void *data) { + + // skip the memset by using malloc + void *space = ucx_stack_malloc(stack, nelem*elsize); + if (space) { + memcpy(space, data, nelem*elsize); + } + return space; +} diff --git a/ucx/string.c b/ucx/string.c new file mode 100644 index 0000000..5ea54f3 --- /dev/null +++ b/ucx/string.c @@ -0,0 +1,807 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/string.h" + +#include "ucx/allocator.h" + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include /* 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 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 0) { + for(size_t i=length ; i>0 ; i--) { + if(str[i-1] == chr) { + *pos = i-1; + return 1; + } + } + } + return 0; +} + +sstr_t sstrchr(sstr_t s, int c) { + size_t pos = 0; + if(ucx_strchr(s.ptr, s.length, c, &pos)) { + return sstrsubs(s, pos); + } + return sstrn(NULL, 0); +} + +sstr_t sstrrchr(sstr_t s, int c) { + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return sstrsubs(s, pos); + } + return sstrn(NULL, 0); +} + +scstr_t scstrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); +} + +scstr_t scstrrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); +} + +#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \ + ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index]) + +#define ptable_w(useheap, ptable, index, src) do {\ + if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\ + else ((size_t*)ptable)[index] = src;\ + } while (0); + + +static const char* ucx_strstr( + const char *str, + size_t length, + const char *match, + size_t matchlen, + size_t *newlen) +{ + *newlen = length; + if (matchlen == 0) { + return str; + } + + const char *result = NULL; + size_t resultlen = 0; + + /* + * IMPORTANT: + * our prefix table contains the prefix length PLUS ONE + * this is our decision, because we want to use the full range of size_t + * the original algorithm needs a (-1) at one single place + * and we want to avoid that + */ + + /* static prefix table */ + static uint8_t s_prefix_table[256]; + + /* check pattern length and use appropriate prefix table */ + /* if the pattern exceeds static prefix table, allocate on the heap */ + register int useheap = matchlen > 255; + register void* ptable = useheap ? + calloc(matchlen+1, sizeof(size_t)): s_prefix_table; + + /* keep counter in registers */ + register size_t i, j; + + /* fill prefix table */ + i = 0; j = 0; + ptable_w(useheap, ptable, i, j); + while (i < matchlen) { + while (j >= 1 && match[j-1] != match[i]) { + ptable_r(j, useheap, ptable, j-1); + } + i++; j++; + ptable_w(useheap, ptable, i, j); + } + + /* search */ + i = 0; j = 1; + while (i < length) { + while (j >= 1 && str[i] != match[j-1]) { + ptable_r(j, useheap, ptable, j-1); + } + i++; j++; + if (j-1 == matchlen) { + size_t start = i - matchlen; + result = str + start; + resultlen = length - start; + break; + } + } + + /* if prefix table was allocated on the heap, free it */ + if (ptable != s_prefix_table) { + free(ptable); + } + + *newlen = resultlen; + return result; +} + +sstr_t scstrsstr(sstr_t string, scstr_t match) { + sstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; +} + +scstr_t scstrscstr(scstr_t string, scstr_t match) { + scstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; +} + +#undef ptable_r +#undef ptable_w + +sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) { + return scstrsplit_a(ucx_default_allocator(), s, d, n); +} + +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) { + if (s.length == 0 || d.length == 0) { + *n = -1; + return NULL; + } + + /* special cases: delimiter is at least as large as the string */ + if (d.length >= s.length) { + /* exact match */ + if (sstrcmp(s, d) == 0) { + *n = 0; + return NULL; + } else /* no match possible */ { + *n = 1; + sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t)); + if(result) { + *result = sstrdup_a(allocator, s); + } else { + *n = -2; + } + return result; + } + } + + ssize_t nmax = *n; + size_t arrlen = 16; + sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t)); + + if (result) { + scstr_t curpos = s; + ssize_t j = 1; + while (1) { + scstr_t match; + /* optimize for one byte delimiters */ + if (d.length == 1) { + match = curpos; + for (size_t i = 0 ; i < curpos.length ; i++) { + if (curpos.ptr[i] == *(d.ptr)) { + match.ptr = curpos.ptr + i; + break; + } + match.length--; + } + } else { + match = scstrscstr(curpos, d); + } + if (match.length > 0) { + /* is this our last try? */ + if (nmax == 0 || j < nmax) { + /* copy the current string to the array */ + scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr); + result[j-1] = sstrdup_a(allocator, item); + size_t processed = item.length + d.length; + curpos.ptr += processed; + curpos.length -= processed; + + /* allocate memory for the next string */ + j++; + if (j > arrlen) { + arrlen *= 2; + size_t reallocsz; + sstr_t* reallocated = NULL; + if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) { + reallocated = (sstr_t*) alrealloc( + allocator, result, reallocsz); + } + if (reallocated) { + result = reallocated; + } else { + for (ssize_t i = 0 ; i < j-1 ; i++) { + alfree(allocator, result[i].ptr); + } + alfree(allocator, result); + *n = -2; + return NULL; + } + } + } else { + /* nmax reached, copy the _full_ remaining string */ + result[j-1] = sstrdup_a(allocator, curpos); + break; + } + } else { + /* no more matches, copy last string */ + result[j-1] = sstrdup_a(allocator, curpos); + break; + } + } + *n = j; + } else { + *n = -2; + } + + return result; +} + +int scstrcmp(scstr_t s1, scstr_t s2) { + if (s1.length == s2.length) { + return memcmp(s1.ptr, s2.ptr, s1.length); + } else if (s1.length > s2.length) { + return 1; + } else { + return -1; + } +} + +int scstrcasecmp(scstr_t s1, scstr_t s2) { + if (s1.length == s2.length) { +#ifdef _WIN32 + return _strnicmp(s1.ptr, s2.ptr, s1.length); +#else + return strncasecmp(s1.ptr, s2.ptr, s1.length); +#endif + } else if (s1.length > s2.length) { + return 1; + } else { + return -1; + } +} + +sstr_t scstrdup(scstr_t s) { + return sstrdup_a(ucx_default_allocator(), s); +} + +sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) { + sstr_t newstring; + newstring.ptr = (char*)almalloc(allocator, s.length + 1); + if (newstring.ptr) { + newstring.length = s.length; + newstring.ptr[newstring.length] = 0; + + memcpy(newstring.ptr, s.ptr, s.length); + } else { + newstring.length = 0; + } + + return newstring; +} + + +static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) { + const char *newptr = s; + size_t length = len; + + while(length > 0 && isspace(*newptr)) { + newptr++; + length--; + } + while(length > 0 && isspace(newptr[length-1])) { + length--; + } + + *newlen = length; + return newptr - s; +} + +sstr_t sstrtrim(sstr_t string) { + sstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); + return newstr; +} + +scstr_t scstrtrim(scstr_t string) { + scstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); + return newstr; +} + +int scstrprefix(scstr_t string, scstr_t prefix) { + if (string.length == 0) { + return prefix.length == 0; + } + if (prefix.length == 0) { + return 1; + } + + if (prefix.length > string.length) { + return 0; + } else { + return memcmp(string.ptr, prefix.ptr, prefix.length) == 0; + } +} + +int scstrsuffix(scstr_t string, scstr_t suffix) { + if (string.length == 0) { + return suffix.length == 0; + } + if (suffix.length == 0) { + return 1; + } + + if (suffix.length > string.length) { + return 0; + } else { + return memcmp(string.ptr+string.length-suffix.length, + suffix.ptr, suffix.length) == 0; + } +} + +int scstrcaseprefix(scstr_t string, scstr_t prefix) { + if (string.length == 0) { + return prefix.length == 0; + } + if (prefix.length == 0) { + return 1; + } + + if (prefix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubsl(string, 0, prefix.length); + return scstrcasecmp(subs, prefix) == 0; + } +} + +int scstrcasesuffix(scstr_t string, scstr_t suffix) { + if (string.length == 0) { + return suffix.length == 0; + } + if (suffix.length == 0) { + return 1; + } + + if (suffix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubs(string, string.length-suffix.length); + return scstrcasecmp(subs, suffix) == 0; + } +} + +sstr_t scstrlower(scstr_t string) { + sstr_t ret = sstrdup(string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = tolower(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) { + sstr_t ret = sstrdup_a(allocator, string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = tolower(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrupper(scstr_t string) { + sstr_t ret = sstrdup(string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = toupper(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) { + sstr_t ret = sstrdup_a(allocator, string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = toupper(ret.ptr[i]); + } + return ret; +} + +#define REPLACE_INDEX_BUFFER_MAX 100 + +struct scstrreplace_ibuf { + size_t* buf; + unsigned int len; /* small indices */ + struct scstrreplace_ibuf* next; +}; + +static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) { + while (buf) { + struct scstrreplace_ibuf *next = buf->next; + free(buf->buf); + free(buf); + buf = next; + } +} + +sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, + scstr_t pattern, scstr_t replacement, size_t replmax) { + + if (pattern.length == 0 || pattern.length > str.length || replmax == 0) + return sstrdup(str); + + /* Compute expected buffer length */ + size_t ibufmax = str.length / pattern.length; + size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; + if (ibuflen > REPLACE_INDEX_BUFFER_MAX) { + ibuflen = REPLACE_INDEX_BUFFER_MAX; + } + + /* Allocate first index buffer */ + struct scstrreplace_ibuf *firstbuf, *curbuf; + firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!firstbuf) return sstrn(NULL, 0); + firstbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!firstbuf->buf) { + free(firstbuf); + return sstrn(NULL, 0); + } + + /* Search occurrences */ + scstr_t searchstr = str; + size_t found = 0; + do { + scstr_t match = scstrscstr(searchstr, pattern); + if (match.length > 0) { + /* Allocate next buffer in chain, if required */ + if (curbuf->len == ibuflen) { + struct scstrreplace_ibuf *nextbuf = + calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!nextbuf) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + nextbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!nextbuf->buf) { + free(nextbuf); + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + curbuf->next = nextbuf; + curbuf = nextbuf; + } + + /* Record match index */ + found++; + size_t idx = match.ptr - str.ptr; + curbuf->buf[curbuf->len++] = idx; + searchstr.ptr = match.ptr + pattern.length; + searchstr.length = str.length - idx - pattern.length; + } else { + break; + } + } while (searchstr.length > 0 && found < replmax); + + /* Allocate result string */ + sstr_t result; + { + ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; + size_t rcount = 0; + curbuf = firstbuf; + do { + rcount += curbuf->len; + curbuf = curbuf->next; + } while (curbuf); + result.length = str.length + rcount * adjlen; + result.ptr = almalloc(allocator, result.length); + if (!result.ptr) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + } + + /* Build result string */ + curbuf = firstbuf; + size_t srcidx = 0; + char* destptr = result.ptr; + do { + for (size_t i = 0; i < curbuf->len; i++) { + /* Copy source part up to next match*/ + size_t idx = curbuf->buf[i]; + size_t srclen = idx - srcidx; + if (srclen > 0) { + memcpy(destptr, str.ptr+srcidx, srclen); + destptr += srclen; + srcidx += srclen; + } + + /* Copy the replacement and skip the source pattern */ + srcidx += pattern.length; + memcpy(destptr, replacement.ptr, replacement.length); + destptr += replacement.length; + } + curbuf = curbuf->next; + } while (curbuf); + memcpy(destptr, str.ptr+srcidx, str.length-srcidx); + + /* Free index buffer */ + scstrrepl_free_ibuf(firstbuf); + + return result; +} + +sstr_t scstrreplacen(scstr_t str, scstr_t pattern, + scstr_t replacement, size_t replmax) { + return scstrreplacen_a(ucx_default_allocator(), + str, pattern, replacement, replmax); +} + + +// type adjustment functions +scstr_t ucx_sc2sc(scstr_t str) { + return str; +} +scstr_t ucx_ss2sc(sstr_t str) { + scstr_t cs; + cs.ptr = str.ptr; + cs.length = str.length; + return cs; +} +scstr_t ucx_ss2c_s(scstr_t c) { + return c; +} diff --git a/ucx/test.c b/ucx/test.c new file mode 100644 index 0000000..20b80b4 --- /dev/null +++ b/ucx/test.c @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/test.h" + +UcxTestSuite* ucx_test_suite_new() { + UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite)); + if (suite != NULL) { + suite->success = 0; + suite->failure = 0; + suite->tests = NULL; + } + + return suite; +} + +void ucx_test_suite_free(UcxTestSuite* suite) { + UcxTestList *l = suite->tests; + while (l != NULL) { + UcxTestList *e = l; + l = l->next; + free(e); + } + free(suite); +} + +int ucx_test_register(UcxTestSuite* suite, UcxTest test) { + if (suite->tests) { + UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (newelem) { + newelem->test = test; + newelem->next = NULL; + + UcxTestList *last = suite->tests; + while (last->next) { + last = last->next; + } + last->next = newelem; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } else { + suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (suite->tests) { + suite->tests->test = test; + suite->tests->next = NULL; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } +} + +void ucx_test_run(UcxTestSuite* suite, FILE* output) { + suite->success = 0; + suite->failure = 0; + for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) { + elem->test(suite, output); + } + fwrite("\nAll test completed.\n", 1, 21, output); + fprintf(output, " Total: %u\n Success: %u\n Failure: %u\n", + suite->success+suite->failure, suite->success, suite->failure); +} diff --git a/ucx/ucx.c b/ucx/ucx.c new file mode 100644 index 0000000..923330d --- /dev/null +++ b/ucx/ucx.c @@ -0,0 +1,62 @@ +/** + * @mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

+ * Latest available source:
+ * + * https://sourceforge.net/projects/ucx/files/ + *

+ * + *

+ * Repositories:
+ * + * https://sourceforge.net/p/ucx/code + * - or - + * + * https://develop.uap-core.de/hg/ucx + *

+ * + *

LICENCE

+ * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/ucx.h" + +int ucx_szmul_impl(size_t a, size_t b, size_t *result) { + if(a == 0 || b == 0) { + *result = 0; + return 0; + } + size_t r = a * b; + if(r / b == a) { + *result = r; + return 0; + } else { + *result = 0; + return 1; + } +} + diff --git a/ucx/ucx/allocator.h b/ucx/ucx/allocator.h new file mode 100644 index 0000000..2159272 --- /dev/null +++ b/ucx/ucx/allocator.h @@ -0,0 +1,206 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Allocator for custom memory management. + * + * A UCX allocator consists of a pointer to the memory area / pool and four + * function pointers to memory management functions operating on this memory + * area / pool. These functions shall behave equivalent to the standard libc + * functions malloc(), calloc(), realloc() and free(). + * + * 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 malloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_malloc)(void *pool, size_t n); + +/** + * A function pointer to the allocators calloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size); + +/** + * A function pointer to the allocators realloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n); + +/** + * A function pointer to the allocators free() 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 malloc() function for this allocator. + */ + ucx_allocator_malloc malloc; + /** + * The calloc() function for this allocator. + */ + ucx_allocator_calloc calloc; + /** + * The realloc() function for this allocator. + */ + ucx_allocator_realloc realloc; + /** + * The free() 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 malloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to malloc() + * @return return value of malloc() + */ +void *ucx_default_malloc(void *ignore, size_t n); +/** + * A wrapper for the standard libc calloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to calloc() + * @param size argument passed to calloc() + * @return return value of calloc() + */ +void *ucx_default_calloc(void *ignore, size_t n, size_t size); +/** + * A wrapper for the standard libc realloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argumend passed to realloc() + * @param n argument passed to realloc() + * @return return value of realloc() + */ +void *ucx_default_realloc(void *ignore, void *data, size_t n); +/** + * A wrapper for the standard libc free() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argument passed to free() + */ +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 struct definition. + */ +#define UCX_ALLOCATOR_DEFAULT {NULL, \ + ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \ + ucx_default_free } + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ALLOCATOR_H */ + diff --git a/ucx/ucx/array.h b/ucx/ucx/array.h new file mode 100644 index 0000000..5b02ebf --- /dev/null +++ b/ucx/ucx/array.h @@ -0,0 +1,460 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Dynamically allocated array implementation. + * + * @file array.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_ARRAY_H +#define UCX_ARRAY_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX array type. + */ +typedef struct { + /** + * The current capacity of the array. + */ + size_t capacity; + /** + * The actual number of elements in the array. + */ + size_t size; + /** + * The size of an individual element in bytes. + */ + size_t elemsize; + /** + * A pointer to the data. + */ + void* data; + /** + * The allocator used for the data. + */ + UcxAllocator* allocator; +} UcxArray; + +/** + * Sets an element in an arbitrary user defined array. + * The data is copied from the specified data location. + * + * If the capacity is insufficient, the array is automatically reallocated and + * the possibly new pointer is stored in the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @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 array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @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 array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @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 array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @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 NULL) + * @param count number of elements to copy from data (if data is + * NULL, 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 NULL) + * @param count number of elements to copy from data (if data is + * NULL, 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 NULL) + * @param count number of elements to copy from data (if data is + * NULL, 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 NULL, + * 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 NULL + */ +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data); + +/** + * Removes an element from the array. + * + * This is in general an expensive operation, because several elements may + * be moved. If the order of the elements is not relevant, use + * ucx_array_remove_fast() instead. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove(UcxArray *array, size_t index); + +/** + * Removes an element from the array. + * + * This is an O(1) operation, but does not maintain the order of the elements. + * The last element in the array is moved to the location of the removed + * element. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove_fast(UcxArray *array, size_t index); + +/** + * Shrinks the memory to exactly fit the contents. + * + * After this operation, the capacity equals the size. + * + * @param array a pointer to the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_shrink(UcxArray* array); + +/** + * Sets the capacity of the array. + * + * If the new capacity is smaller than the size of the array, the elements + * are removed and the size is adjusted accordingly. + * + * @param array a pointer to the array + * @param capacity the new capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_resize(UcxArray* array, size_t capacity); + +/** + * Resizes the array only, if the capacity is insufficient. + * + * If the requested capacity is smaller than the current capacity, this + * function does nothing. + * + * @param array a pointer to the array + * @param capacity the guaranteed capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_reserve(UcxArray* array, size_t capacity); + +/** + * Resizes the capacity, if the specified number of elements would not fit. + * + * A call to ucx_array_grow(array, count) is effectively the same as + * ucx_array_reserve(array, array->size+count). + * + * @param array a pointer to the array + * @param count the number of elements that should additionally fit + * into the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_grow(UcxArray* array, size_t count); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ARRAY_H */ + diff --git a/ucx/ucx/avl.h b/ucx/ucx/avl.h new file mode 100644 index 0000000..8b251a5 --- /dev/null +++ b/ucx/ucx/avl.h @@ -0,0 +1,353 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @file avl.h + * + * AVL tree implementation. + * + * This binary search tree implementation allows average O(1) insertion and + * removal of elements (excluding binary search time). + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_AVL_H +#define UCX_AVL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#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 (NULL), 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 NULL, 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 NULL, 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 NULL on + * empty trees. + */ +#define UCX_AVL_FIND_CLOSEST 3 + +/** + * Finds a node within the tree. The following modes are supported: + *
    + *
  • #UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()
  • + *
  • #UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least + * as large as the specified key
  • + *
  • #UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most + * as large as the specified key
  • + *
  • #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 NULL on + * empty trees.
  • + *
+ * + * 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 NULL, 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 NULL, 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 NULL 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 NULL if + * the given node is the in-order maximum + */ +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_AVL_H */ + diff --git a/ucx/ucx/buffer.h b/ucx/ucx/buffer.h new file mode 100644 index 0000000..25f659f --- /dev/null +++ b/ucx/ucx/buffer.h @@ -0,0 +1,339 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file buffer.h + * + * Advanced buffer implementation. + * + * Instances of UcxBuffer can be used to read from or to write to like one + * would do with a stream. This allows the use of ucx_stream_copy() to copy + * contents from one buffer to another. + * + * Some features for convenient use of the buffer + * can be enabled. See the documentation of the macro constants for more + * information. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_BUFFER_H +#define UCX_BUFFER_H + +#include "ucx.h" +#include +#include + +#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. + * + * Note: you may provide NULL as argument for + * space. Then this function will allocate the space and enforce + * the #UCX_BUFFER_AUTOFREE flag. + * + * @param space pointer to the memory area, or NULL 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. + * + * Note: 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 shift bytes are discarded. + * The new size of the buffer is the old size minus + * the absolute shift value. + * If this value is larger than the buffer size, the buffer is emptied (but + * not cleared, see the security note below). + * + * The buffer position gets shifted alongside with the content but is kept + * within the boundaries of the buffer. + * + * Security note: the shifting operation does not erase the + * previously occupied memory cells. You can easily do that manually, e.g. by + * calling memset(buffer->space, 0, shift) for a right shift or + * memset(buffer->size, 0, buffer->capacity-buffer->size) + * 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 whence 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 whence + * @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 memset(). + * + * @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. + * + * Note: 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. + * + * Attention: the argument provided is the number of additional + * bytes the buffer shall hold. It is NOT 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 + * at least 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, EOF is returned. + * + * On successful write the position of the buffer is increased. + * + * @param buffer the buffer to write to + * @param c the character to write as int value + * @return the byte that has bean written as int value or + * EOF 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 int value or EOF, 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 sstrn() with the buffer space and size + * as arguments + */ +#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_BUFFER_H */ + diff --git a/ucx/ucx/list.h b/ucx/ucx/list.h new file mode 100644 index 0000000..2bda6b8 --- /dev/null +++ b/ucx/ucx/list.h @@ -0,0 +1,512 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Doubly linked list implementation. + * + * @file list.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_LIST_H +#define UCX_LIST_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX lists. + * + * The first argument is the name of the iteration variable. The scope of + * this variable is limited to the UCX_FOREACH 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 NULL, if this is the + * last element. + */ + UcxList *next; + /** + * Pointer to the previous list element or NULL, 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 + * NULL) + * @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 data 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 + * NULL) + * @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. + * + * Caution: the argument MUST 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 (NULL), 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 NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL 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 NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL 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 should overwrite the old list pointer by calling + * mylist = ucx_list_prepend(mylist, mydata);. 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 NULL 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 NULL 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 NULL. + * + * This function modifies the references to the next/previous element of + * the last/first element of list1/ + * list2. + * + * @param list1 first list + * @param list2 second list + * @return if list1 is NULL, list2 is + * returned, otherwise list1 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 NULL, 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 MUST call this function + * as follows: mylist = ucx_list_sort(mylist, mycmpfnc, mydata);. + * + * @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 + * highly recommended to always update the pointer by calling + * mylist = ucx_list_remove(mylist, myelem);. + * + * @param list the list from which the element shall be removed + * @param element the element to remove + * @return returns the updated list pointer or NULL, 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 NULL, if the list + * @see ucx_list_remove() + */ +UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list, + UcxList *element); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LIST_H */ + diff --git a/ucx/ucx/logging.h b/ucx/ucx/logging.h new file mode 100644 index 0000000..32bbbae --- /dev/null +++ b/ucx/ucx/logging.h @@ -0,0 +1,253 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Logging API. + * + * @file logging.h + * @author Mike Becker, Olaf Wintermann + */ +#ifndef UCX_LOGGING_H +#define UCX_LOGGING_H + +#include "ucx.h" +#include "map.h" +#include "string.h" +#include + +#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: "%F %T %z "). + * @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 unsigned int for the key and + * const char* 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 format 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: + * + * [LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message + * + * 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. + * + * Attention: the message (including automatically generated information) + * is limited to 4096 characters. The level description is limited to + * 256 characters and the timestamp string is limited to 128 characters. + * + * @param logger the logger to use + * @param level the level to log on + * @param file information about the source file + * @param line information about the source line number + * @param format format string + * @param ... arguments + * @see ucx_logger_log() + */ +void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, + const unsigned int line, const char* format, ...); + +/** + * Registers a custom log level. + * @param logger the logger + * @param level the log level as unsigned integer + * @param name a string literal describing the level + */ +#define ucx_logger_register_level(logger, level, name) {\ + unsigned int l; \ + l = level; \ + ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \ + } while (0); + +/** + * Logs a message at the specified level. + * @param logger the logger to use + * @param level the level to log the message on + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_log(logger, level, ...) \ + ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__) + +/** + * Shortcut for logging an error message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_error(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__) + +/** + * Shortcut for logging an information message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_info(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__) + +/** + * Shortcut for logging a warning message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_warn(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__) + +/** + * Shortcut for logging a debug message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_debug(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__) + +/** + * Shortcut for logging a trace message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_trace(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LOGGING_H */ diff --git a/ucx/ucx/map.h b/ucx/ucx/map.h new file mode 100644 index 0000000..4a9b9a2 --- /dev/null +++ b/ucx/ucx/map.h @@ -0,0 +1,549 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file map.h + * + * Hash map implementation. + * + * This implementation uses murmur hash 2 and separate chaining with linked + * lists. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MAP_H +#define UCX_MAP_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX maps. + * + * The key variable is implicitly defined, but the + * value 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. + * Attention: this is NOT the element index! Do NOT + * 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. + * + * Note: the contents are not 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 (NULL), 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. + * + * Note: the contents are not 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. + * + * Note: 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 NULL 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. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always 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 NULL 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. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always 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 NULL 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. + * + * Note: 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 NOT rely on + * the iteration order. + * + * Note: The iterator is NOT initialized. You need to call + * ucx_map_iter_next() at least once before accessing any information. However, + * it is not recommended to access the fields of a UcxMapIterator directly. + * + * @param map the map to create the iterator for + * @return an iterator initialized on the first element of the + * first element list + * @see ucx_map_iter_next() + */ +UcxMapIterator ucx_map_iterator(UcxMap const *map); + +/** + * Proceeds to the next element of the map (if any). + * + * Subsequent calls on the same iterator proceed to the next element and + * store the key/value-pair into the memory specified as arguments of this + * function. + * + * If no further elements are found, this function returns zero and leaves the + * last found key/value-pair in memory. + * + * @param iterator the iterator to use + * @param key a pointer to the memory where to store the key + * @param value a pointer to the memory where to store the value + * @return 1, if another element was found, 0 if all elements has been processed + * @see ucx_map_iterator() + */ +int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MAP_H */ + diff --git a/ucx/ucx/mempool.h b/ucx/ucx/mempool.h new file mode 100644 index 0000000..e4a8ce3 --- /dev/null +++ b/ucx/ucx/mempool.h @@ -0,0 +1,209 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mempool.h + * + * Memory pool implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MEMPOOL_H +#define UCX_MEMPOOL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#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 abort(). + * + * @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 MUST NOT 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 MUST 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. + * However, this does not cause the memory to be freed automatically!. + * If you want to use this function, make the memory pool free non-pooled + * memory, the specified destructor function must call free() + * by itself. But keep in mind, that you then MUST NOT use this destructor + * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it + * would cause a double-free. + * + * @param pool the memory pool + * @param ptr data the destructor is registered for + * @param destr a pointer to the destructor function + */ +void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MEMPOOL_H */ + diff --git a/ucx/ucx/properties.h b/ucx/ucx/properties.h new file mode 100644 index 0000000..819d1e5 --- /dev/null +++ b/ucx/ucx/properties.h @@ -0,0 +1,221 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * + * Load / store utilities for properties files. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "ucx.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UcxProperties object for parsing properties data. + * Most of the fields are for internal use only. You may configure the + * properties parser, e.g. by changing the used delimiter or specifying + * up to three different characters that shall introduce comments. + */ +typedef struct { + /** + * Input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + char *buffer; + + /** + * Length of the input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + size_t buflen; + + /** + * Current buffer position (don't set manually). + * Used by ucx_properties_next(). + */ + size_t pos; + + /** + * Internal temporary buffer (don't set manually). + * Used by ucx_properties_next(). + */ + char *tmp; + + /** + * Internal temporary buffer length (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmplen; + + /** + * Internal temporary buffer capacity (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmpcap; + + /** + * Parser error code. + * This is always 0 on success and a nonzero value on syntax errors. + * The value is set by ucx_properties_next(). + */ + int error; + + /** + * The delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; +} UcxProperties; + + +/** + * Constructs a new UcxProperties object. + * @return a pointer to the new UcxProperties object + */ +UcxProperties *ucx_properties_new(); + +/** + * Destroys a UcxProperties object. + * @param prop the UcxProperties object to destroy + */ +void ucx_properties_free(UcxProperties *prop); + +/** + * Sets the input buffer for the properties parser. + * + * After calling this function, you may parse the data by calling + * ucx_properties_next() until it returns 0. The function ucx_properties2map() + * is a convenience function that reads as much data as possible by using this + * function. + * + * + * @param prop the UcxProperties object + * @param buf a pointer to the new buffer + * @param len the payload length of the buffer + * @see ucx_properties_next() + * @see ucx_properties2map() + */ +void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); + +/** + * Retrieves the next key/value-pair. + * + * This function returns a nonzero value as long as there are key/value-pairs + * found. If no more key/value-pairs are found, you may refill the input buffer + * with ucx_properties_fill(). + * + * Attention: 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 FILE* 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: + * + * [key] = [value]\\n + * + * @param map the map to store + * @param file the FILE* stream to write to + * @return 0 on success, or a non-zero value on error + */ +int ucx_properties_store(UcxMap *map, FILE *file); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_PROPERTIES_H */ + diff --git a/ucx/ucx/stack.h b/ucx/ucx/stack.h new file mode 100644 index 0000000..6fe8a06 --- /dev/null +++ b/ucx/ucx/stack.h @@ -0,0 +1,240 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file stack.h + * + * Default stack memory allocation implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STACK_H +#define UCX_STACK_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * UCX stack structure. + */ +typedef struct { + /** UcxAllocator based on this stack */ + UcxAllocator allocator; + + /** Stack size. */ + size_t size; + + /** Pointer to the bottom of the stack */ + char *space; + + /** Pointer to the top of the stack */ + char *top; +} UcxStack; + +/** + * Metadata for each UCX stack element. + */ +struct ucx_stack_metadata { + /** + * Location of the previous element (NULL 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 NULL 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 + * dest, if specified. + * + * Use #ucx_stack_topsize()# to get the amount of memory that must be available + * at the location of dest. + * + * @param stack a pointer to the stack + * @param dest the location where the contents shall be written to, or + * NULL, 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 + * dest. + * + * This function copies at most n bytes to the destination, but + * the element is always freed as a whole. + * If the element was larger than n, 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 dest + * @see ucx_stack_pop + */ +void ucx_stack_popn(UcxStack *stack, void *dest, size_t n); + +/** + * Returns the remaining available memory on the specified stack. + * + * @param stack a pointer to the stack + * @return the remaining available memory + */ +size_t ucx_stack_avail(UcxStack *stack); + +/** + * Checks, if the stack is empty. + * + * @param stack a pointer to the stack + * @return nonzero, if the stack is empty, zero otherwise + */ +#define ucx_stack_empty(stack) (!(stack)->top) + +/** + * Computes a recommended size for the stack memory area. Note, that + * reallocations have not been taken into account, so you might need to reserve + * twice as much memory to allow many reallocations. + * + * @param size the approximate payload + * @param elems the approximate count of element allocations + * @return a recommended size for the stack space based on the information + * provided + */ +#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \ + (elems + 1)) + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STACK_H */ + diff --git a/ucx/ucx/string.h b/ucx/ucx/string.h new file mode 100644 index 0000000..90b437a --- /dev/null +++ b/ucx/ucx/string.h @@ -0,0 +1,1201 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Bounded string implementation. + * + * The UCX strings (sstr_t) provide an alternative to C strings. + * The main difference to C strings is, that sstr_t does not + * need to be NULL-terminated. Instead the length is stored + * within the structure. + * + * When using sstr_t, developers must be full aware of what type + * of string (NULL-terminated) or not) they are using, when + * accessing the char* ptr directly. + * + * The UCX string module provides some common string functions, known from + * standard libc, working with sstr_t. + * + * @file string.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STRING_H +#define UCX_STRING_H + +#include "ucx.h" +#include "allocator.h" +#include + +/* + * Use this macro to disable the shortcuts if you experience macro collision. + */ +#ifndef UCX_NO_SSTR_SHORTCUTS +/** + * Shortcut for a sstr_t struct + * or scstr_t struct literal. + */ +#define ST(s) { s, sizeof(s)-1 } + +/** Shortcut for the conversion of a C string to a sstr_t. */ +#define S(s) sstrn(s, sizeof(s)-1) + +/** Shortcut for the conversion of a C string to a scstr_t. */ +#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 + * (not necessarily NULL-terminated) */ + 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 + * (not necessarily NULL-terminated) */ + 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 internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @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 internally 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. + * + * Do not use this function manually. + * + * @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 internally 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. + * + * Do not use this function manually. + * + * @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 internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @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 internal function (ab)uses the C standard an expects one single + * argument which is then implicitly converted to scstr_t without a warning. + * + * Do not use this function manually. + * + * @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 strlen(). + * + * Note: 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. + * + * Note: 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 strlen(). + * + * Note: 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. + * + * Note: 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. + * + * Attention: 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. + * + * Attention: 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 malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * 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 malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * 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 free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * 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 free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * 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. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use sstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @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. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-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 string starting at start + * with a maximum length of length + * + * @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. + * + * Attention: the new string references the same memory area as the +* input string and is NOT required to be NULL-terminated. + * Use scstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @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. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-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 string starting at start + * with a maximum length of length + * + * @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 chr + * + * @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 chr + * + * @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 chr + * + * @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 chr + * + * @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 match is an empty string, the complete string 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 + * match, or an empty string, if the sequence is not + * present in string + */ +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 match is an empty string, the complete string 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 + * match, or an empty string, if the sequence is not + * present in string + */ +#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 match is an empty string, the complete string 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 + * match, or an empty string, if the sequence is not + * present in string + */ +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 match is an empty string, the complete string 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 + * match, or an empty string, if the sequence is not + * present in string + */ +#define sstrscstr(string, match) scstrscstr(string, SCSTR(match)) + +/** + * Splits a string into parts by using a delimiter string. + * + * This function will return NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count 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 count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * 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, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). 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 + * NULL 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 NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count 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 count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * 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, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). 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 + * NULL on error + * + * @see sstrsplit_a() + */ +#define sstrsplit(string, delim, count) \ + scstrsplit(SCSTR(string), SCSTR(delim), count) + +/** + * Performing scstrsplit() using a UcxAllocator. + * + * Read the description of scstrsplit() for details. + * + * 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 + * NULL on error + * + * @see scstrsplit() + */ +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim, + ssize_t *count); + +/** + * Performing sstrsplit() using a UcxAllocator. + * + * Read the description of sstrsplit() for details. + * + * 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 + * NULL 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 memcmp(). + * + * At first it compares the scstr_t.length attribute of the two strings. The + * memcmp() 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 + * memcmp() otherwise (i.e. 0 if the strings match) + */ +int scstrcmp(scstr_t s1, scstr_t s2); + +/** + * Compares two UCX strings with standard memcmp(). + * + * At first it compares the sstr_t.length attribute of the two strings. The + * memcmp() 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 + * memcmp() 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 + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * 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 + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * 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 always be NULL- + * 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 always be NULL- + * 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. + * + * Note: the new sstr_t references the same memory, thus you + * MUST NOT pass the sstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = sstrtrim(mystr); 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. + * + * Note: the new scstr_t references the same memory, thus you + * MUST NOT pass the scstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = scstrtrim(mystr); 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 replmax 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 replmax 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 replmax 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 replmax 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 replmax 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 replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @return the resulting string after applying the replacements + */ +#define sstrreplace(str, pattern, replacement) \ + scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STRING_H */ diff --git a/ucx/ucx/test.h b/ucx/ucx/test.h new file mode 100644 index 0000000..b45b1d1 --- /dev/null +++ b/ucx/ucx/test.h @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + *
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * 
+ * + * **** IN SOURCE FILE: **** + *
+ * 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
+ * }
+ * 
+ * + * Note: if a test fails, a longjump is performed + * back to the #UCX_TEST_BEGIN macro! + * + * Attention: 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 +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ + +/** + * Alias for the __func__ preprocessor macro. + * Some compilers use __func__ and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * __func__. + */ +#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 EXIT_SUCCESS on success or + * EXIT_FAILURE 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. + * Note: Any UCX_TEST_ASSERT() calls must be performed after + * #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. + * + * Note: You may only 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. + * Note: Any UCX_TEST_ASSERT() calls must be performed before + * #UCX_TEST_END. + * + * @see #UCX_TEST_BEGIN + */ +#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;} + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ + diff --git a/ucx/ucx/ucx.h b/ucx/ucx/ucx.h new file mode 100644 index 0000000..0944def --- /dev/null +++ b/ucx/ucx/ucx.h @@ -0,0 +1,204 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Main UCX Header providing most common definitions. + * + * @file ucx.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_H +#define UCX_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 2 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 1 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +#include +#include + +#ifdef _WIN32 +#if !(defined __ssize_t_defined || defined _SSIZE_T_) +#include +typedef SSIZE_T ssize_t; +#define __ssize_t_defined +#define _SSIZE_T_ +#endif /* __ssize_t_defined and _SSIZE_T */ +#else /* !_WIN32 */ +#include +#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 + * NULL, 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 + * NULL, it shall be ignored. + + * Attention: if pointers returned by functions of this type may be + * passed to free() depends on the implementation of the + * respective copy_func. + */ +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 fwrite, 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 fread, 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 __builtin_umul_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result) +#else /* __WORDSIZE != 32 */ +/** + * Alias for __builtin_umull_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result) +#endif /* __WORDSIZE */ + +#else /* no GNUC or clang bultin */ + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result) + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * This is a custom implementation in case there is no compiler builtin + * available. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t where the result should be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +int ucx_szmul_impl(size_t a, size_t b, size_t *result); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_H */ + diff --git a/ucx/ucx/utils.h b/ucx/ucx/utils.h new file mode 100644 index 0000000..642397a --- /dev/null +++ b/ucx/ucx/utils.h @@ -0,0 +1,508 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file utils.h + * + * Compare, copy and printf functions. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_UTILS_H +#define UCX_UTILS_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include +#include +#include + +#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 NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, 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 NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, 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 printf() 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, ...); + +/** + * va_list 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 printf() like function which allocates space for a sstr_t + * the result is written to. + * + * Attention: 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. + * + * Note: The sstr_t.ptr of the return value will always be + * NULL-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, ...); + +/** + * va_list 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 printf() like function which writes the output to a + * UcxBuffer. + * + * @param buffer the buffer the data is written to + * @param ... format string and additional arguments + * @return the total number of bytes written + * @see ucx_fprintf() + */ +#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \ + (write_func)ucx_buffer_write, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_UTILS_H */ + diff --git a/ucx/utils.c b/ucx/utils.c new file mode 100644 index 0000000..101c33f --- /dev/null +++ b/ucx/utils.c @@ -0,0 +1,448 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/utils.h" + +#include +#include +#include +#include + +/* COPY FUCNTIONS */ +void* ucx_strcpy(const void* s, void* data) { + const char *str = (const char*) s; + size_t n = 1+strlen(str); + char *cpy = (char*) malloc(n); + memcpy(cpy, str, n); + return cpy; +} + +void* ucx_memcpy(const void* m, void* n) { + size_t k = *((size_t*)n); + void *cpy = malloc(k); + memcpy(cpy, m, k); + return cpy; +} + +size_t ucx_stream_bncopy(void *src, void *dest, read_func readfnc, + write_func writefnc, char* buf, size_t bufsize, size_t n) { + if(n == 0 || bufsize == 0) { + return 0; + } + + char *lbuf; + size_t ncp = 0; + + if(buf) { + lbuf = buf; + } else { + lbuf = (char*)malloc(bufsize); + if(lbuf == NULL) { + return 0; + } + } + + size_t r; + size_t rn = bufsize > n ? n : bufsize; + while((r = readfnc(lbuf, 1, rn, src)) != 0) { + r = writefnc(lbuf, 1, r, dest); + ncp += r; + n -= r; + rn = bufsize > n ? n : bufsize; + if(r == 0 || n == 0) { + break; + } + } + + if (lbuf != buf) { + free(lbuf); + } + + return ncp; +} + +/* COMPARE FUNCTIONS */ + +int ucx_cmp_str(const void *s1, const void *s2, void *data) { + return strcmp((const char*)s1, (const char*)s2); +} + +int ucx_cmp_strn(const void *s1, const void *s2, void *n) { + return strncmp((const char*)s1, (const char*)s2, *((size_t*) n)); +} + +int ucx_cmp_sstr(const void *s1, const void *s2, void *data) { + sstr_t a = *(const sstr_t*) s1; + sstr_t b = *(const sstr_t*) s2; + return sstrcmp(a, b); +} + +int ucx_cmp_int(const void *i1, const void *i2, void *data) { + int a = *((const int*) i1); + int b = *((const int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longint(const void *i1, const void *i2, void *data) { + long int a = *((const long int*) i1); + long int b = *((const long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longlong(const void *i1, const void *i2, void *data) { + long long a = *((const long long*) i1); + long long b = *((const long long*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int16(const void *i1, const void *i2, void *data) { + int16_t a = *((const int16_t*) i1); + int16_t b = *((const int16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int32(const void *i1, const void *i2, void *data) { + int32_t a = *((const int32_t*) i1); + int32_t b = *((const int32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int64(const void *i1, const void *i2, void *data) { + int64_t a = *((const int64_t*) i1); + int64_t b = *((const int64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint(const void *i1, const void *i2, void *data) { + unsigned int a = *((const unsigned int*) i1); + unsigned int b = *((const unsigned int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) { + unsigned long int a = *((const unsigned long int*) i1); + unsigned long int b = *((const unsigned long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) { + unsigned long long a = *((const unsigned long long*) i1); + unsigned long long b = *((const unsigned long long*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint16(const void *i1, const void *i2, void *data) { + uint16_t a = *((const uint16_t*) i1); + uint16_t b = *((const uint16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint32(const void *i1, const void *i2, void *data) { + uint32_t a = *((const uint32_t*) i1); + uint32_t b = *((const uint32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint64(const void *i1, const void *i2, void *data) { + uint64_t a = *((const uint64_t*) i1); + uint64_t b = *((const uint64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int*) i1); + intmax_t b = *((const int*) i2); + return a - b; +} + +intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long int*) i1); + intmax_t b = *((const long int*) i2); + return a - b; +} + +intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long long*) i1); + intmax_t b = *((const long long*) i2); + return a - b; +} + +intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int16_t*) i1); + intmax_t b = *((const int16_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int32_t*) i1); + intmax_t b = *((const int32_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int64_t*) i1); + intmax_t b = *((const int64_t*) i2); + return a - b; +} + +intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned int*) i1); + uintmax_t b = *((const unsigned int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long int*) i1); + uintmax_t b = *((const unsigned long int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long long*) i1); + uintmax_t b = *((const unsigned long long*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint16_t*) i1); + uintmax_t b = *((const uint16_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint32_t*) i1); + uintmax_t b = *((const uint32_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint64_t*) i1); + uintmax_t b = *((const uint64_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) { + float a = *((const float*) f1); + float b = *((const float*) f2); + float e = !epsilon ? 1e-6f : *((float*)epsilon); + if (fabsf(a - b) < e) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) { + double a = *((const double*) d1); + double b = *((const double*) d2); + double e = !epsilon ? 1e-14 : *((double*)epsilon); + if (fabs(a - b) < e) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) { + const intptr_t p1 = (const intptr_t) ptr1; + const intptr_t p2 = (const intptr_t) ptr2; + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} + +int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) { + return memcmp(ptr1, ptr2, *((size_t*)n)); +} + +/* PRINTF FUNCTIONS */ + +#ifdef va_copy +#define UCX_PRINTF_BUFSIZE 256 +#else +#pragma message("WARNING: C99 va_copy macro not supported by this platform" \ + " - limiting ucx_*printf to 2 KiB") +#define UCX_PRINTF_BUFSIZE 0x800 +#endif + +int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = ucx_vfprintf(stream, wfc, fmt, ap); + va_end(ap); + return ret; +} + +int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap) { + char buf[UCX_PRINTF_BUFSIZE]; +#ifdef va_copy + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret < 0) { + return ret; + } else if (ret < UCX_PRINTF_BUFSIZE) { + return (int)wfc(buf, 1, ret, stream); + } else { + if (ret == INT_MAX) { + errno = ENOMEM; + return -1; + } + + int len = ret + 1; + char *newbuf = (char*)malloc(len); + if (!newbuf) { + return -1; + } + + ret = vsnprintf(newbuf, len, fmt, ap2); + if (ret > 0) { + ret = (int)wfc(newbuf, 1, ret, stream); + } + free(newbuf); + } + return ret; +#else + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret < 0) { + return ret; + } else if (ret < UCX_PRINTF_BUFSIZE) { + return (int)wfc(buf, 1, ret, stream); + } else { + errno = ENOMEM; + return -1; + } +#endif +} + +sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...) { + va_list ap; + sstr_t ret; + va_start(ap, fmt); + ret = ucx_vasprintf(allocator, fmt, ap); + va_end(ap); + return ret; +} + +sstr_t ucx_vasprintf(UcxAllocator *a, const char *fmt, va_list ap) { + sstr_t s; + s.ptr = NULL; + s.length = 0; + char buf[UCX_PRINTF_BUFSIZE]; +#ifdef va_copy + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) { + s.ptr = (char*)almalloc(a, ret + 1); + if (s.ptr) { + s.length = (size_t)ret; + memcpy(s.ptr, buf, ret); + s.ptr[s.length] = '\0'; + } + } else if (ret == INT_MAX) { + errno = ENOMEM; + } else { + int len = ret + 1; + s.ptr = (char*)almalloc(a, len); + if (s.ptr) { + ret = vsnprintf(s.ptr, len, fmt, ap2); + if (ret < 0) { + free(s.ptr); + s.ptr = NULL; + } else { + s.length = (size_t)ret; + } + } + } +#else + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) { + s.ptr = (char*)almalloc(a, ret + 1); + if (s.ptr) { + s.length = (size_t)ret; + memcpy(s.ptr, buf, ret); + s.ptr[s.length] = '\0'; + } + } else { + errno = ENOMEM; + } +#endif + return s; +} diff --git a/ui/Makefile b/ui/Makefile new file mode 100644 index 0000000..865a03c --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,47 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../ +include ../config.mk + +OBJ_DIR = ../build/ + +include common/objs.mk + +UI_LIB = ../build/lib/libuitk.$(LIB_EXT) + +include $(TOOLKIT)/objs.mk +OBJ = $(TOOLKITOBJS) $(COMMONOBJS) + +all: $(UI_LIB) + +include $(TOOLKIT)/Makefile + +$(COMMON_OBJPRE)%.o: common/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + diff --git a/ui/common/context.c b/ui/common/context.c new file mode 100644 index 0000000..628a4b4 --- /dev/null +++ b/ui/common/context.c @@ -0,0 +1,519 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#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;kvars_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;inumgroups;k++) { + if(groups[i] == gw->groups[k]) { + check[k] = 1; + } + } + } + + int enable = 1; + for(int i=0;inumgroups;i++) { + if(check[i] == 0) { + enable = 0; + break; + } + } + gw->enable(gw->widget, enable); + } + + if(groups) { + free(groups); + } +} + +void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { + // get groups + UcxList *groups = NULL; + va_list ap; + va_start(ap, enable); + int group; + while((group = va_arg(ap, int)) != -1) { + groups = ucx_list_append(groups, (void*)(intptr_t)group); + } + va_end(ap); + + uic_add_group_widget(ctx, widget, enable, groups); + + ucx_list_free(groups); +} + +void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups) { + UcxMempool *mp = ctx->mempool; + UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget)); + + gw->widget = widget; + gw->enable = enable; + gw->numgroups = ucx_list_size(groups); + gw->groups = ucx_mempool_calloc(mp, gw->numgroups, sizeof(int)); + int i = 0; + UCX_FOREACH(elm, groups) { + gw->groups[i++] = (intptr_t)elm->data; + } + + ctx->group_widgets = ucx_list_append_a( + mp->allocator, + ctx->group_widgets, + gw); +} + +void* ui_malloc(UiContext *ctx, size_t size) { + return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL; +} + +void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) { + return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL; +} + +void ui_free(UiContext *ctx, void *ptr) { + if(ctx) { + ucx_mempool_free(ctx->mempool, ptr); + } +} + +void* ui_realloc(UiContext *ctx, void *ptr, size_t size) { + return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL; +} + diff --git a/ui/common/context.h b/ui/common/context.h new file mode 100644 index 0000000..14b2ed8 --- /dev/null +++ b/ui/common/context.h @@ -0,0 +1,133 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_CONTEXT_H +#define UIC_CONTEXT_H + +#include "../ui/toolkit.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiVar UiVar; +typedef struct UiListPtr UiListPtr; +typedef struct UiListVar UiListVar; +typedef struct UiGroupWidget UiGroupWidget; + +typedef enum UiVarType UiVarType; + +enum UiVarType { + UI_VAR_SPECIAL = 0, + UI_VAR_INTEGER, + UI_VAR_DOUBLE, + UI_VAR_STRING, + UI_VAR_TEXT, + UI_VAR_LIST, + UI_VAR_RANGE +}; + +struct UiContext { + UiContext *parent; + UiObject *obj; + UcxMempool *mempool; + + void *document; + UcxList *documents; + + UcxMap *vars; // manually created context vars + UcxMap *vars_unbound; // unbound vars created by widgets + + UcxList *groups; // int list + UcxList *group_widgets; // UiGroupWidget* list + + void (*attach_document)(UiContext *ctx, void *document); + void (*detach_document2)(UiContext *ctx, void *document); + + char *title; + +#ifdef UI_GTK + GtkAccelGroup *accel_group; +#endif + + ui_callback close_callback; + void *close_data; +}; + +// UiVar replacement, rename it to UiVar when finished +struct UiVar { + void *value; + UiVarType type; + UiVar *from; + UiContext *from_ctx; +}; + +struct UiGroupWidget { + void *widget; + ui_enablefunc enable; + int *groups; + int numgroups; +}; + + +void uic_init_global_context(void); + +UiContext* uic_context(UiObject *toplevel, UcxMempool *mp); +UiContext* uic_root_context(UiContext *ctx); +void uic_context_set_document(UiContext *ctx, void *document); // deprecated +void uic_context_detach_document(UiContext *ctx); // deprecated + +void uic_context_attach_document(UiContext *ctx, void *document); +void uic_context_detach_document2(UiContext *ctx, void *document); +void uic_context_detach_all(UiContext *ctx); + +UiVar* uic_get_var(UiContext *ctx, const char *name); +UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); +void* uic_create_value(UiContext *ctx, UiVarType type); + +void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc); +void uic_save_var2(UiVar *var); +void uic_unbind_var(UiVar *var); + +void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value); + +void uic_remove_bound_var(UiContext *ctx, UiVar *var); + +void uic_check_group_widgets(UiContext *ctx); +void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_CONTEXT_H */ + diff --git a/ui/common/document.c b/ui/common/document.c new file mode 100644 index 0000000..f225949 --- /dev/null +++ b/ui/common/document.c @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "document.h" + +static UcxMap *documents; + +void uic_docmgr_init() { + documents = ucx_map_new(32); +} + +void ui_set_document(UiObject *obj, void *document) { + uic_context_detach_all(obj->ctx); + obj->ctx->attach_document(obj->ctx, document); +} + +void ui_detach_document(UiObject *obj) { + uic_context_detach_all(obj->ctx); +} + +void* ui_get_document(UiObject *obj) { + return obj->ctx->document; +} + +void ui_set_subdocument(void *document, void *sub) { + UiContext *ctx = ui_document_context(document); + if(!ctx) { + fprintf(stderr, "UI Error: pointer is not a document\n"); + } + // TODO +} + +void ui_detach_subdocument(void *document, void *sub) { + UiContext *ctx = ui_document_context(document); + if(!ctx) { + fprintf(stderr, "UI Error: pointer is not a document\n"); + } + // TODO +} + +void* ui_get_subdocument(void *document) { + UiContext *ctx = ui_document_context(document); + if(!ctx) { + fprintf(stderr, "UI Error: pointer is not a document\n"); + } + // TODO + return NULL; +} + +void* ui_document_new(size_t size) { + UcxMempool *mp = ucx_mempool_new(256); + UiContext *ctx = ucx_mempool_calloc(mp, 1, sizeof(UiContext)); + ctx->attach_document = uic_context_attach_document; + ctx->detach_document2 = uic_context_detach_document2; + ctx->mempool = mp; + ctx->vars = ucx_map_new_a(mp->allocator, 16); + + void *document = ucx_mempool_calloc(mp, 1, size); + ucx_map_put(documents, ucx_key(&document, sizeof(void*)), ctx); + return document; +} + +void ui_document_destroy(void *doc) { + // TODO +} + +UiContext* ui_document_context(void *doc) { + if(doc) { + return ucx_map_get(documents, ucx_key(&doc, sizeof(void*))); + } else { + return NULL; + } +} diff --git a/ui/common/document.h b/ui/common/document.h new file mode 100644 index 0000000..0f439d5 --- /dev/null +++ b/ui/common/document.h @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_DOCUMENT_H +#define UIC_DOCUMENT_H + +#include "../ui/toolkit.h" +#include "context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void uic_docmgr_init(); +void uic_document_addvar(void *doc, char *name, int type, size_t vs); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_DOCUMENT_H */ + diff --git a/ui/common/object.c b/ui/common/object.c new file mode 100644 index 0000000..42a31be --- /dev/null +++ b/ui/common/object.c @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "object.h" +#include "context.h" + +void ui_end(UiObject *obj) { + if(!obj->next) { + return; + } + + UiObject *prev = NULL; + while(obj->next) { + prev = obj; + obj = obj->next; + } + + if(prev) { + // TODO: free last obj + prev->next = NULL; + } +} + + +UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) { + UiContext *ctx = toplevel->ctx; + + UiObject *newobj = ucx_mempool_calloc(ctx->mempool, 1, sizeof(UiObject)); + newobj->ctx = ctx; + newobj->widget = widget; + + return newobj; +} + +void uic_obj_add(UiObject *toplevel, UiObject *ctobj) { + UiObject *current = uic_current_obj(toplevel); + current->next = ctobj; +} + +UiObject* uic_current_obj(UiObject *toplevel) { + if(!toplevel) { + return NULL; + } + UiObject *obj = toplevel; + while(obj->next) { + obj = obj->next; + } + return obj; +} + +UiContainer* uic_get_current_container(UiObject *obj) { + return uic_current_obj(obj)->container; +} diff --git a/ui/common/object.h b/ui/common/object.h new file mode 100644 index 0000000..cf0bd3c --- /dev/null +++ b/ui/common/object.h @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_OBJECT_H +#define UIC_OBJECT_H + +#include "../ui/toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget); +void uic_obj_add(UiObject *toplevel, UiObject *ctobj); +UiObject* uic_current_obj(UiObject *toplevel); + +UiContainer* uic_get_current_container(UiObject *obj); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_OBJECT_H */ + diff --git a/ui/common/objs.mk b/ui/common/objs.mk new file mode 100644 index 0000000..2bb35a7 --- /dev/null +++ b/ui/common/objs.mk @@ -0,0 +1,40 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2017 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +COMMON_SRC_DIR = ui/common/ +COMMON_OBJPRE = $(OBJ_DIR)$(COMMON_SRC_DIR) + +COMMON_OBJ = context.o +COMMON_OBJ += document.o +COMMON_OBJ += object.o +COMMON_OBJ += types.o +COMMON_OBJ += properties.o + +TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%) +TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c) + diff --git a/ui/common/properties.c b/ui/common/properties.c new file mode 100644 index 0000000..177e98a --- /dev/null +++ b/ui/common/properties.c @@ -0,0 +1,297 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "properties.h" +#include +#include +#include + +static UiProperties *application_properties; +static UiProperties *language; + +#ifndef UI_COCOA + +static char *locales_dir; +static char *pixmaps_dir; + +#endif + +char* ui_getappdir() { + if(ui_appname() == NULL) { + return NULL; + } + + return ui_configfile(NULL); +} + +char* ui_configfile(char *name) { + char *appname = ui_appname(); + if(!appname) { + return NULL; + } + + UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); + + // add base dir + char *homeenv = getenv("HOME"); + if(homeenv == NULL) { + ucx_buffer_free(buf); + return NULL; + } + sstr_t home = sstr(homeenv); + + ucx_buffer_write(home.ptr, 1, home.length, buf); + if(home.ptr[home.length-1] != '/') { + ucx_buffer_putc(buf, '/'); + } + +#ifdef UI_COCOA + // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/ + ucx_buffer_puts(buf, "Library/Application Support/"); +#else + // app dir is $HOME/.$APPNAME/ + ucx_buffer_putc(buf, '.'); +#endif + ucx_buffer_puts(buf, appname); + ucx_buffer_putc(buf, '/'); + + // add file name + if(name) { + ucx_buffer_puts(buf, name); + } + + char *path = buf->space; + free(buf); + return path; +} + +static int ui_mkdir(char *path) { +#ifdef _WIN32 + return mkdir(path); +#else + return mkdir(path, S_IRWXU); +#endif +} + +void uic_load_app_properties() { + application_properties = ucx_map_new(128); + + if(!ui_appname()) { + // applications without name cannot load app properties + return; + } + + char *dir = ui_configfile(NULL); + if(!dir) { + return; + } + if(ui_mkdir(dir)) { + if(errno != EEXIST) { + fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir); + free(dir); + return; + } + } + free(dir); + + char *path = ui_configfile("application.properties"); + if(!path) { + return; + } + + FILE *file = fopen(path, "r"); + if(!file) { + free(path); + return; + } + + if(ucx_properties_load(application_properties, file)) { + fprintf(stderr, "Ui Error: Cannot load application properties.\n"); + } + + fclose(file); + free(path); +} + +void uic_store_app_properties() { + char *path = ui_configfile("application.properties"); + if(!path) { + return; + } + + FILE *file = fopen(path, "w"); + if(!file) { + fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path); + free(path); + return; + } + + if(ucx_properties_store(application_properties, file)) { + fprintf(stderr, "Ui Error: Cannot store application properties.\n"); + } + + fclose(file); + free(path); +} + + +char* ui_get_property(char *name) { + return ucx_map_cstr_get(application_properties, name); +} + +void ui_set_property(char *name, char *value) { + ucx_map_cstr_put(application_properties, name, value); +} + +void ui_set_default_property(char *name, char *value) { + char *v = ucx_map_cstr_get(application_properties, name); + if(!v) { + ucx_map_cstr_put(application_properties, name, value); + } +} + + + +static char* uic_concat_path(const char *base, const char *p, const char *ext) { + size_t baselen = strlen(base); + + UcxBuffer *buf = ucx_buffer_new(NULL, 32, UCX_BUFFER_AUTOEXTEND); + if(baselen > 0) { + ucx_buffer_write(base, 1, baselen, buf); + if(base[baselen - 1] != '/') { + ucx_buffer_putc(buf, '/'); + } + } + ucx_buffer_write(p, 1, strlen(p), buf); + if(ext) { + ucx_buffer_write(ext, 1, strlen(ext), buf); + } + + char *str = buf->space; + free(buf); + return str; +} + +#ifndef UI_COCOA + +void ui_locales_dir(char *path) { + locales_dir = path; +} + +void ui_pixmaps_dir(char *path) { + pixmaps_dir = path; +} + +char* uic_get_image_path(const char *imgfilename) { + if(pixmaps_dir) { + return uic_concat_path(pixmaps_dir, imgfilename, NULL); + } else { + return NULL; + } +} + +void ui_load_lang(char *locale) { + if(!locale) { + locale = "en_EN"; + } + + char *path = uic_concat_path(locales_dir, locale, ".properties"); + + uic_load_language_file(path); + free(path); +} + +void ui_load_lang_def(char *locale, char *default_locale) { + char tmp[6]; + if(!locale) { + char *lang = getenv("LANG"); + if(lang && strlen(lang) >= 5) { + memcpy(tmp, lang, 5); + tmp[5] = '\0'; + locale = tmp; + } else { + locale = default_locale; + } + } + + char *path = uic_concat_path(locales_dir, locale, ".properties"); + + if(uic_load_language_file(path)) { + if(default_locale) { + ui_load_lang_def(default_locale, NULL); + } else { + // cannot find any language file + fprintf(stderr, "Ui Error: Cannot load language.\n"); + free(path); + exit(-1); + } + } + free(path); +} + +#endif + +int uic_load_language_file(const char *path) { + UcxMap *lang = ucx_map_new(256); + + FILE *file = fopen(path, "r"); + if(!file) { + return 1; + } + + if(ucx_properties_load(lang, file)) { + fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path); + } + + fclose(file); + + ucx_map_rehash(lang); + + language = lang; + + return 0; +} + +char* uistr(char *name) { + char *value = uistr_n(name); + return value ? value : "missing string"; +} + +char* uistr_n(char *name) { + if(!language) { + return NULL; + } + return ucx_map_cstr_get(language, name); +} + diff --git a/ui/common/properties.h b/ui/common/properties.h new file mode 100644 index 0000000..26281d1 --- /dev/null +++ b/ui/common/properties.h @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_PROPERTIES_H +#define UIC_PROPERTIES_H + +#include "../ui/properties.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#define UI_HOME "USERPROFILE" +#else +#define UI_HOME "HOME" +#endif + +void uic_load_app_properties(); +void uic_store_app_properties(); + +int uic_load_language_file(const char *path); +char* uic_get_image_path(const char *imgfilename); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_PROPERTIES_H */ + diff --git a/ui/common/types.c b/ui/common/types.c new file mode 100644 index 0000000..18933f7 --- /dev/null +++ b/ui/common/types.c @@ -0,0 +1,391 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include "../ui/tree.h" +#include "types.h" +#include "context.h" + +UiObserver* ui_observer_new(ui_callback f, void *data) { + UiObserver *observer = malloc(sizeof(UiObserver)); + observer->callback = f; + observer->data = data; + observer->next = NULL; + return observer; +} + +UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer) { + if(!list) { + return observer; + } else { + UiObserver *l = list; + while(l->next) { + l = l->next; + } + l->next = observer; + return list; + } +} + +UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data) { + UiObserver *observer = ui_observer_new(f, data); + return ui_obsvlist_add(list, observer); +} + +void ui_notify(UiObserver *observer, void *data) { + ui_notify_except(observer, NULL, data); +} + +void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data) { + UiEvent evt; + evt.obj = NULL; + evt.window = NULL; + evt.document = NULL; + evt.eventdata = data; + evt.intval = 0; + + while(observer) { + if(observer != exc) { + observer->callback(&evt, observer->data); + } + observer = observer->next; + } +} + +void ui_notify_evt(UiObserver *observer, UiEvent *event) { + while(observer) { + observer->callback(event, observer->data); + observer = observer->next; + } +} + +/* --------------------------- UiList --------------------------- */ + +UiList* ui_list_new(UiContext *ctx, char *name) { + UiList *list = malloc(sizeof(UiList)); + list->first = ui_list_first; + list->next = ui_list_next; + list->get = ui_list_get; + list->count = ui_list_count; + list->observers = NULL; + + list->data = NULL; + list->iter = NULL; + + list->update = NULL; + list->obj = NULL; + + if(name) { + uic_reg_var(ctx, name, UI_VAR_LIST, list); + } + + return list; +} + +void ui_list_free(UiList *list) { + ucx_list_free(list->data); + free(list); +} + +void* ui_list_first(UiList *list) { + UcxList *elm = list->data; + list->iter = elm; + return elm ? elm->data : NULL; +} + +void* ui_list_next(UiList *list) { + UcxList *elm = list->iter; + if(elm) { + elm = elm->next; + if(elm) { + list->iter = elm; + return elm->data; + } + } + return NULL; +} + +void* ui_list_get(UiList *list, int i) { + UcxList *elm = ucx_list_get(list->data, i); + if(elm) { + list->iter = elm; + return elm->data; + } else { + return NULL; + } +} + +int ui_list_count(UiList *list) { + UcxList *elm = list->data; + return (int)ucx_list_size(elm); +} + +void ui_list_append(UiList *list, void *data) { + list->data = ucx_list_append(list->data, data); +} + +void ui_list_prepend(UiList *list, void *data) { + list->data = ucx_list_prepend(list->data, data); +} + +void ui_list_clear(UiList *list) { + ucx_list_free(list->data); + list->data = NULL; +} + +void ui_list_addobsv(UiList *list, ui_callback f, void *data) { + list->observers = ui_add_observer(list->observers, f, data); +} + +void ui_list_notify(UiList *list) { + ui_notify(list->observers, list); +} + + +typedef struct { + int type; + char *name; +} UiColumn; + +UiModel* ui_model(UiContext *ctx, ...) { + UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + + va_list ap; + va_start(ap, ctx); + + UcxList *cols = NULL; + int type; + while((type = va_arg(ap, int)) != -1) { + char *name = va_arg(ap, char*); + + UiColumn *column = malloc(sizeof(UiColumn)); + column->type = type; + column->name = name; + + cols = ucx_list_append(cols, column); + } + + va_end(ap); + + size_t len = ucx_list_size(cols); + info->columns = len; + info->types = ui_calloc(ctx, len, sizeof(UiModelType)); + info->titles = ui_calloc(ctx, len, sizeof(char*)); + + int i = 0; + UCX_FOREACH(elm, cols) { + UiColumn *c = elm->data; + info->types[i] = c->type; + info->titles[i] = c->name; + free(c); + i++; + } + ucx_list_free(cols); + + return info; +} + +void ui_model_free(UiContext *ctx, UiModel *mi) { + ucx_mempool_free(ctx->mempool, mi->types); + ucx_mempool_free(ctx->mempool, mi->titles); + ucx_mempool_free(ctx->mempool, mi); +} + +// types + +// public functions +UiInteger* ui_int_new(UiContext *ctx, char *name) { + UiInteger *i = ui_malloc(ctx, sizeof(UiInteger)); + memset(i, 0, sizeof(UiInteger)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_INTEGER, i); + } + return i; +} + +UiDouble* ui_double_new(UiContext *ctx, char *name) { + UiDouble *d = ui_malloc(ctx, sizeof(UiDouble)); + memset(d, 0, sizeof(UiDouble)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_DOUBLE, d); + } + return d; +} + +UiString* ui_string_new(UiContext *ctx, char *name) { + UiString *s = ui_malloc(ctx, sizeof(UiString)); + memset(s, 0, sizeof(UiString)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_STRING, s); + } + return s; +} + +UiText* ui_text_new(UiContext *ctx, char *name) { + UiText *t = ui_malloc(ctx, sizeof(UiText)); + memset(t, 0, sizeof(UiText)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_TEXT, t); + } + return t; +} + +UiRange* ui_range_new(UiContext *ctx, char *name) { + UiRange *r = ui_malloc(ctx, sizeof(UiRange)); + memset(r, 0, sizeof(UiRange)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_RANGE, r); + } + return r; +} + + +// private functions +void uic_int_copy(UiInteger *from, UiInteger *to) { + to->get = from->get; + to->set = from->set; + to->obj = from->obj; +} + +void uic_double_copy(UiDouble *from, UiDouble *to) { + to->get = from->get; + to->set = from->set; + to->obj = from->obj; +} + +void uic_string_copy(UiString *from, UiString *to) { + to->get = from->get; + to->set = from->set; + to->obj = from->obj; +} + +void uic_text_copy(UiText *from, UiText *to) { + to->get = from->get; + to->set = from->set; + to->getsubstr = from->getsubstr; + to->insert = from->insert; + to->setposition = from->setposition; + to->position = from->position; + to->selection = from->selection; + to->length = from->length; + to->remove = from->remove; + + to->obj = from->obj; + // do not copy the undo manager +} + +void uic_range_copy(UiRange *from, UiRange *to) { + to->get = from->get; + to->set = from->set; + to->setrange = from->setrange; + to->setextent = from->setextent; + to->obj = from->obj; +} + +void uic_list_copy(UiList *from, UiList *to) { + to->update = from->update; + to->obj = from->obj; +} + + +void uic_int_save(UiInteger *i) { + if(!i->obj) return; + i->value = i->get(i); +} + +void uic_double_save(UiDouble *d) { + if(!d->obj) return; + d->value = d->get(d); +} + +void uic_string_save(UiString *s) { + if(!s->obj) return; + s->get(s); +} + +void uic_text_save(UiText *t) { + if(!t->obj) return; + t->get(t); + t->position(t); +} + +void uic_range_save(UiRange *r) { + if(!r->obj) return; + r->get(r); +} + + +void uic_int_unbind(UiInteger *i) { + i->get = NULL; + i->set = NULL; + i->obj = NULL; +} + +void uic_double_unbind(UiDouble *d) { + d->get = NULL; + d->set = NULL; + d->obj = NULL; +} + +void uic_string_unbind(UiString *s) { + s->get = NULL; + s->set = NULL; + s->obj = NULL; +} + +void uic_text_unbind(UiText *t) { + t->set = NULL; + t->get = NULL; + t->getsubstr = NULL; + t->insert = NULL; + t->setposition = NULL; + t->position = NULL; + t->selection = NULL; + t->length = NULL; + t->remove = NULL; + t->obj = NULL; + t->undomgr = NULL; +} + +void uic_range_unbind(UiRange *r) { + r->get = NULL; + r->set = NULL; + r->setextent = NULL; + r->setrange = NULL; + r->obj = NULL; +} + +void uic_list_unbind(UiList *l) { + l->update = NULL; + l->obj = NULL; +} diff --git a/ui/common/types.h b/ui/common/types.h new file mode 100644 index 0000000..b1d5a2c --- /dev/null +++ b/ui/common/types.h @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_TYPES_H +#define UIC_TYPES_H + +#include "../ui/toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +void uic_int_copy(UiInteger *from, UiInteger *to); +void uic_double_copy(UiDouble *from, UiDouble *to); +void uic_string_copy(UiString *from, UiString *to); +void uic_text_copy(UiText *from, UiText *to); +void uic_range_copy(UiRange *from, UiRange *to); +void uic_list_copy(UiList *from, UiList *to); + +void uic_int_save(UiInteger *i); +void uic_double_save(UiDouble *d); +void uic_string_save(UiString *s); +void uic_text_save(UiText *t); +void uic_range_save(UiRange *r); + +void uic_int_unbind(UiInteger *i); +void uic_double_unbind(UiDouble *d); +void uic_string_unbind(UiString *s); +void uic_text_unbind(UiText *t); +void uic_range_unbind(UiRange *r); +void uic_list_unbind(UiList *l); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_TYPES_H */ + diff --git a/ui/gtk/Makefile b/ui/gtk/Makefile new file mode 100644 index 0000000..aba263f --- /dev/null +++ b/ui/gtk/Makefile @@ -0,0 +1,34 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +$(GTK_OBJPRE)%.o: gtk/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + diff --git a/ui/gtk/button.c b/ui/gtk/button.c new file mode 100644 index 0000000..0add84f --- /dev/null +++ b/ui/gtk/button.c @@ -0,0 +1,254 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "button.h" +#include "container.h" +#include +#include "../common/context.h" +#include "../common/object.h" + +UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { + GtkWidget *button = gtk_button_new_with_label(label); + + if(f) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = data; + event->callback = f; + event->value = 0; + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_button_clicked), + event); + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, button, FALSE); + + return button; +} + + +void ui_button_clicked(GtkWidget *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = event->value; + event->callback(&e, event->userdata); +} + +int64_t ui_toggle_button_get(UiInteger *integer) { + GtkToggleButton *button = integer->obj; + integer->value = (int)gtk_toggle_button_get_active(button); + return integer->value; +} + +void ui_toggle_button_set(UiInteger *integer, int64_t value) { + GtkToggleButton *button = integer->obj; + integer->value = value; + gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE); +} + +void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = event->var->value; + e.intval = gtk_toggle_tool_button_get_active(widget); + + UiInteger *i = event->var->value; + ui_notify_evt(i->observers, &e); +} + +UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var) { + GtkWidget *button = gtk_check_button_new_with_label(label); + + // bind value + if(var) { + UiInteger *value = var->value; + value->obj = GTK_TOGGLE_BUTTON(button); + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + gtk_toggle_button_set_active(value->obj, value->value); + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->var = var; + event->observers = NULL; + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_toggled_obs), + event); + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, button, FALSE); + + return button; +} + +UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) { + UiVar *var = NULL; + if(value) { + var = malloc(sizeof(UiVar)); + var->value = value; + var->type = UI_VAR_SPECIAL; + } + return ui_checkbox_var(obj, label, var); +} + +UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER); + return ui_checkbox_var(obj, label, var); +} + + +UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) { + GSList *rg = NULL; + UiInteger *rgroup; + + if(var) { + rgroup = var->value; + rg = rgroup->obj; + } + + GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label); + rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton)); + + if(rgroup) { + rgroup->obj = rg; + rgroup->get = ui_radiobutton_get; + rgroup->set = ui_radiobutton_set; + + ui_radiobutton_set(rgroup, rgroup->value); + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->var = var; + event->observers = NULL; + + g_signal_connect( + rbutton, + "clicked", + G_CALLBACK(ui_radio_obs), + event); + g_signal_connect( + rbutton, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, rbutton, FALSE); + + return rbutton; +} + +UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) { + UiVar *var = NULL; + if(rgroup) { + var = malloc(sizeof(UiVar)); + var->value = rgroup; + var->type = UI_VAR_SPECIAL; + } + return ui_radiobutton_var(obj, label, var); +} + +UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER); + return ui_radiobutton_var(obj, label, var); +} + +void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) { + UiInteger *i = event->var->value; + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = i->get(i); + + ui_notify_evt(i->observers, &e); +} + +int64_t ui_radiobutton_get(UiInteger *value) { + int selection = 0; + GSList *ls = value->obj; + int i = 0; + guint len = g_slist_length(ls); + while(ls) { + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ls->data))) { + selection = len - i - 1; + break; + } + ls = ls->next; + i++; + } + + value->value = selection; + return selection; +} + +void ui_radiobutton_set(UiInteger *value, int64_t i) { + GSList *ls = value->obj; + int s = g_slist_length(ls) - 1 - i; + int j = 0; + while(ls) { + if(j == s) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ls->data), TRUE); + break; + } + ls = ls->next; + j++; + } + + value->value = i; +} + diff --git a/ui/gtk/button.h b/ui/gtk/button.h new file mode 100644 index 0000000..eac2337 --- /dev/null +++ b/ui/gtk/button.h @@ -0,0 +1,60 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include "../ui/toolkit.h" +#include "../ui/button.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// event wrapper +void ui_button_clicked(GtkWidget *widget, UiEventData *event); + + +void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event); + +UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var); + +UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var); + +void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event); + +int64_t ui_radiobutton_get(UiInteger *value); +void ui_radiobutton_set(UiInteger *value, int64_t i); + +#ifdef __cplusplus +} +#endif + +#endif /* BUTTON_H */ + diff --git a/ui/gtk/container.c b/ui/gtk/container.c new file mode 100644 index 0000000..551eb85 --- /dev/null +++ b/ui/gtk/container.c @@ -0,0 +1,627 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "container.h" +#include "toolkit.h" + +#include "../common/context.h" +#include "../common/object.h" + + +void ui_container_begin_close(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + ct->close = 1; +} + +int ui_container_finish(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + if(ct->close) { + ui_end(obj); + return 0; + } + return 1; +} + +GtkWidget* ui_gtk_vbox_new(int spacing) { +#ifdef UI_GTK3 + return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing); +#else + return gtk_vbox_new(FALSE, spacing); +#endif +} + +GtkWidget* ui_gtk_hbox_new(int spacing) { +#ifdef UI_GTK3 + return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing); +#else + return gtk_hbox_new(FALSE, spacing); +#endif +} + +/* -------------------- Frame Container (deprecated) -------------------- */ +UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) { + UiContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiContainer)); + ct->widget = frame; + ct->add = ui_frame_container_add; + return ct; +} + +void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + gtk_container_add(GTK_CONTAINER(ct->widget), widget); + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +/* -------------------- Box Container -------------------- */ +UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) { + UiBoxContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiBoxContainer)); + ct->container.widget = box; + ct->container.add = ui_box_container_add; + return (UiContainer*)ct; +} + +void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiBoxContainer *bc = (UiBoxContainer*)ct; + if(ct->layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(ct->layout.fill); + } + + if(bc->has_fill && fill) { + fprintf(stderr, "UiError: container has 2 filled widgets"); + fill = FALSE; + } + if(fill) { + bc->has_fill = TRUE; + } + + UiBool expand = fill; + gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + +UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) { + UiGridContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiGridContainer)); + ct->container.widget = grid; + ct->container.add = ui_grid_container_add; +#ifdef UI_GTK2 + ct->width = 0; + ct->height = 1; +#endif + return (UiContainer*)ct; +} + +#ifdef UI_GTK3 +void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiGridContainer *grid = (UiGridContainer*)ct; + + if(ct->layout.newline) { + grid->x = 0; + grid->y++; + ct->layout.newline = FALSE; + } + + int hexpand = FALSE; + int vexpand = FALSE; + if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) { + hexpand = ct->layout.hexpand; + } + if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) { + vexpand = ct->layout.vexpand; + } + + if(hexpand) { + gtk_widget_set_hexpand(widget, TRUE); + } + if(vexpand) { + gtk_widget_set_vexpand(widget, TRUE); + } + + int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1; + + gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1); + grid->x += gwidth; + + ui_reset_layout(ct->layout); + ct->current = widget; +} +#endif +#ifdef UI_GTK2 +void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiGridContainer *grid = (UiGridContainer*)ct; + + if(ct->layout.newline) { + grid->x = 0; + grid->y++; + ct->layout.newline = FALSE; + } + + int hexpand = FALSE; + int vexpand = FALSE; + if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) { + hexpand = ct->layout.hexpand; + } + if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) { + vexpand = ct->layout.vexpand; + } + GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; + GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; + + gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0); + grid->x++; + int nw = grid->x > grid->width ? grid->x : grid->width; + if(grid->x > grid->width || grid->y > grid->height) { + grid->width = nw; + grid->height = grid->y + 1; + gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height); + } + + ui_reset_layout(ct->layout); + ct->current = widget; +} +#endif + +UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { + UiContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiContainer)); + ct->widget = scrolledwindow; + ct->add = ui_scrolledwindow_container_add; + return ct; +} + +void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + // TODO: check if the widget implements GtkScrollable +#ifdef UI_GTK3 + gtk_container_add(GTK_CONTAINER(ct->widget), widget); +#else + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget); +#endif + ui_reset_layout(ct->layout); + ct->current = widget; +} + +UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) { + UiTabViewContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiTabViewContainer)); + ct->container.widget = tabview; + ct->container.add = ui_tabview_container_add; + return (UiContainer*)ct; +} + +void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + gtk_notebook_append_page( + GTK_NOTEBOOK(ct->widget), + widget, + gtk_label_new(ct->layout.label)); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +UIWIDGET ui_vbox(UiObject *obj) { + return ui_vbox_sp(obj, 0, 0); +} + +UIWIDGET ui_hbox(UiObject *obj) { + return ui_hbox_sp(obj, 0, 0); +} + +static GtkWidget* box_set_margin(GtkWidget *box, int margin) { + GtkWidget *ret = box; +#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12 + gtk_widget_set_margin_start(box, margin); + gtk_widget_set_margin_end(box, margin); +#else + gtk_widget_set_margin_left(box, margin); + gtk_widget_set_margin_right(box, margin); +#endif + gtk_widget_set_margin_top(box, margin); + gtk_widget_set_margin_bottom(box, margin); +#elif defined(UI_GTK2) + GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin); + gtk_container_add(GTK_CONTAINER(a), box); + ret = a; +#endif + return ret; +} + +UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) { + UiContainer *ct = uic_get_current_container(obj); + + GtkWidget *vbox = ui_gtk_vbox_new(spacing); + GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox; + ct->add(ct, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, vbox); + newobj->container = ui_box_container(obj, vbox); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) { + UiContainer *ct = uic_get_current_container(obj); + + GtkWidget *hbox = ui_gtk_hbox_new(spacing); + GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox; + ct->add(ct, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, hbox); + newobj->container = ui_box_container(obj, hbox); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_grid(UiObject *obj) { + return ui_grid_sp(obj, 0, 0, 0); +} + +UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) { + UiContainer *ct = uic_get_current_container(obj); + GtkWidget *widget; + +#ifdef UI_GTK3 + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing); + gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing); +#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12 + gtk_widget_set_margin_start(grid, margin); + gtk_widget_set_margin_end(grid, margin); +#else + gtk_widget_set_margin_left(grid, margin); + gtk_widget_set_margin_right(grid, margin); +#endif + gtk_widget_set_margin_top(grid, margin); + gtk_widget_set_margin_bottom(grid, margin); + + widget = grid; +#elif defined(UI_GTK2) + GtkWidget *grid = gtk_table_new(1, 1, FALSE); + + gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing); + gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing); + + if(margin > 0) { + GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin); + gtk_container_add(GTK_CONTAINER(a), grid); + widget = a; + } else { + widget = grid; + } +#endif + ct->add(ct, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, grid); + newobj->container = ui_grid_container(obj, grid); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_scrolledwindow(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL); + ct->add(ct, sw, TRUE); + + UiObject *newobj = uic_object_new(obj, sw); + newobj->container = ui_scrolledwindow_container(obj, sw); + uic_obj_add(obj, newobj); + + return sw; +} + +UIWIDGET ui_tabview(UiObject *obj) { + GtkWidget *tabview = gtk_notebook_new(); + gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE); + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, tabview, TRUE); + + UiObject *tabviewobj = uic_object_new(obj, tabview); + tabviewobj->container = ui_tabview_container(obj, tabview); + uic_obj_add(obj, tabviewobj); + + return tabview; +} + +void ui_tab(UiObject *obj, char *title) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.label = title; + ui_vbox(obj); +} + +void ui_select_tab(UIWIDGET tabview, int tab) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab); +} + +/* -------------------- Splitpane -------------------- */ + +static GtkWidget* create_paned(UiOrientation orientation) { +#ifdef UI_GTK3 + switch(orientation) { + case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL); + } +#else + switch(orientation) { + case UI_HORIZONTAL: return gtk_hpaned_new(); + case UI_VERTICAL: return gtk_vpaned_new(); + } +#endif + return NULL; +} + +UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) { + GtkWidget *paned = create_paned(orientation); + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, paned, TRUE); + + if(max <= 0) max = INT_MAX; + + UiPanedContainer *pctn = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiPanedContainer)); + pctn->container.widget = paned; + pctn->container.add = ui_paned_container_add; + pctn->current_pane = paned; + pctn->orientation = orientation; + pctn->max = max; + pctn->cur = 0; + + UiObject *pobj = uic_object_new(obj, paned); + pobj->container = (UiContainer*)pctn; + + uic_obj_add(obj, pobj); + + return paned; +} + +UIWIDGET ui_hsplitpane(UiObject *obj, int max) { + return ui_splitpane(obj, max, UI_HORIZONTAL); +} + +UIWIDGET ui_vsplitpane(UiObject *obj, int max) { + return ui_splitpane(obj, max, UI_VERTICAL); +} + +void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiPanedContainer *pctn = (UiPanedContainer*)ct; + + gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE; + int width = ct->layout.width; + ui_reset_layout(ct->layout); + + if(pctn->cur == 0) { + gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize); + } else if(pctn->cur < pctn->max-1) { + GtkWidget *nextPane = create_paned(pctn->orientation); + gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE); + gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize); + pctn->current_pane = nextPane; + } else if(pctn->cur == pctn->max-1) { + gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize); + width = 0; // disable potential call of gtk_paned_set_position + } else { + fprintf(stderr, "Splitpane max reached: %d\n", pctn->max); + return; + } + + if(width > 0) { + gtk_paned_set_position(GTK_PANED(pctn->current_pane), width); + } + + pctn->cur++; +} + + +/* -------------------- Sidebar (deprecated) -------------------- */ +UIWIDGET ui_sidebar(UiObject *obj) { +#ifdef UI_GTK3 + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); +#else + GtkWidget *paned = gtk_hpaned_new(); +#endif + gtk_paned_set_position(GTK_PANED(paned), 200); + + GtkWidget *sidebar = ui_gtk_vbox_new(0); + gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE); + + UiObject *left = uic_object_new(obj, sidebar); + UiContainer *ct1 = ui_box_container(obj, sidebar); + left->container = ct1; + + UiObject *right = uic_object_new(obj, sidebar); + UiContainer *ct2 = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiContainer)); + ct2->widget = paned; + ct2->add = ui_split_container_add2; + right->container = ct2; + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, paned, TRUE); + + uic_obj_add(obj, right); + uic_obj_add(obj, left); + + return sidebar; +} + +void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) { + // TODO: remove + gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + +void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) { + gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +/* -------------------- Document Tabview -------------------- */ +static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) { + GQuark q = g_quark_from_static_string("ui.tab.object"); + UiObject *tab = g_object_get_qdata(G_OBJECT(page), q); + if(!tab) { + return; + } + + //printf("page_change: %d\n", page_num); + UiContext *ctx = tab->ctx; + uic_context_detach_all(ctx->parent); // TODO: fix? + ctx->parent->attach_document(ctx->parent, ctx->document); +} + +UiTabbedPane* ui_tabbed_document_view(UiObject *obj) { + GtkWidget *tabview = gtk_notebook_new(); + gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE); + + g_signal_connect( + tabview, + "switch-page", + G_CALLBACK(page_change), + NULL); + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, tabview, TRUE); + + UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane)); + tabbedpane->ctx = uic_current_obj(obj)->ctx; + tabbedpane->widget = tabview; + tabbedpane->document = NULL; + + return tabbedpane; +} + +UiObject* ui_document_tab(UiTabbedPane *view) { + GtkWidget *frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); + // TODO: label + gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL); + + UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject)); + tab->widget = NULL; // initialization for uic_context() + tab->ctx = uic_context(tab, view->ctx->mempool); + tab->ctx->parent = view->ctx; + tab->ctx->attach_document = uic_context_attach_document; + tab->ctx->detach_document2 = uic_context_detach_document2; + tab->widget = frame; + tab->window = view->ctx->obj->window; + tab->container = ui_frame_container(tab, frame); + tab->next = NULL; + + GQuark q = g_quark_from_static_string("ui.tab.object"); + g_object_set_qdata(G_OBJECT(frame), q, tab); + + return tab; +} + +void ui_tab_set_document(UiContext *ctx, void *document) { + // TODO: remove? + if(ctx->parent->document) { + //ctx->parent->detach_document(ctx->parent, ctx->parent->document); + } + //uic_context_set_document(ctx, document); + //uic_context_set_document(ctx->parent, document); + //ctx->parent->document = document; +} + +void ui_tab_detach_document(UiContext *ctx) { + // TODO: remove? + //uic_context_detach_document(ctx->parent); +} + + +/* + * -------------------- Layout Functions -------------------- + * + * functions for setting layout attributes for the current container + * + */ + +void ui_layout_fill(UiObject *obj, UiBool fill) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.fill = ui_bool2lb(fill); +} + +void ui_layout_hexpand(UiObject *obj, UiBool expand) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.hexpand = expand; +} + +void ui_layout_vexpand(UiObject *obj, UiBool expand) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.vexpand = expand; +} + +void ui_layout_width(UiObject *obj, int width) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.width = width; +} + +void ui_layout_gridwidth(UiObject *obj, int width) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.gridwidth = width; +} + +void ui_newline(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.newline = TRUE; +} + diff --git a/ui/gtk/container.h b/ui/gtk/container.h new file mode 100644 index 0000000..f2c0740 --- /dev/null +++ b/ui/gtk/container.h @@ -0,0 +1,138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include "../ui/toolkit.h" +#include "../ui/container.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) +#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE) +#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE) + +typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool); + +typedef struct UiDocumentView UiDocumentView; + +typedef struct UiLayout UiLayout; +typedef enum UiLayoutBool UiLayoutBool; + +enum UiLayoutBool { + UI_LAYOUT_UNDEFINED = 0, + UI_LAYOUT_TRUE, + UI_LAYOUT_FALSE, +}; + +struct UiLayout { + UiLayoutBool fill; + UiBool newline; + char *label; + UiBool hexpand; + UiBool vexpand; + int width; + int gridwidth; +}; + +struct UiContainer { + GtkWidget *widget; + GtkMenu *menu; + GtkWidget *current; + + void (*add)(UiContainer*, GtkWidget*, UiBool); + UiLayout layout; + + int close; +}; + +typedef struct UiBoxContainer { + UiContainer container; + UiBool has_fill; +} UiBoxContainer; + +typedef struct UiGridContainer { + UiContainer container; + int x; + int y; +#ifdef UI_GTK2 + int width; + int height; +#endif +} UiGridContainer; + +typedef struct UiPanedContainer { + UiContainer container; + GtkWidget *current_pane; + int orientation; + int max; + int cur; +} UiPanedContainer; + +typedef struct UiTabViewContainer { + UiContainer container; +} UiTabViewContainer; + +GtkWidget* ui_gtk_vbox_new(int spacing); +GtkWidget* ui_gtk_hbox_new(int spacing); + +UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); +void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +UiContainer* ui_box_container(UiObject *obj, GtkWidget *box); +void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid); +void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow); +void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview); +void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill); +void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill); + + +UiObject* ui_add_document_tab(UiDocumentView *view); +void ui_tab_set_document(UiContext *ctx, void *document); +void ui_tab_detach_document(UiContext *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* CONTAINER_H */ + diff --git a/ui/gtk/display.c b/ui/gtk/display.c new file mode 100644 index 0000000..83ce3d4 --- /dev/null +++ b/ui/gtk/display.c @@ -0,0 +1,128 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "display.h" +#include "container.h" +#include +#include "../common/context.h" +#include "../common/object.h" + +static void set_alignment(GtkWidget *widget, float xalign, float yalign) { +#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16 + gtk_label_set_xalign(GTK_LABEL(widget), xalign); + gtk_label_set_yalign(GTK_LABEL(widget), yalign); +#else + gtk_misc_set_alignment(GTK_MISC(widget), xalign, yalign); +#endif +} + +UIWIDGET ui_label(UiObject *obj, char *label) { + GtkWidget *widget = gtk_label_new(label); + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, widget, FALSE); + + return widget; +} + +UIWIDGET ui_llabel(UiObject *obj, char *label) { + UIWIDGET widget = ui_label(obj, label); + set_alignment(widget, 0, .5); + return widget; +} + +UIWIDGET ui_rlabel(UiObject *obj, char *label) { + UIWIDGET widget = ui_label(obj, label); + //gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_RIGHT); + + set_alignment(widget, 1, .5); + return widget; +} + +UIWIDGET ui_space(UiObject *obj) { + GtkWidget *widget = gtk_label_new(""); + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, widget, TRUE); + + return widget; +} + +UIWIDGET ui_separator(UiObject *obj) { +#if UI_GTK3 + GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); +#else + GtkWidget *widget = gtk_hseparator_new(); +#endif + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, widget, FALSE); + + return widget; +} + +/* ------------------------- progress bar ------------------------- */ + +UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = value; + var->type = UI_VAR_SPECIAL; + return ui_progressbar_var(obj, var); +} + +UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE); + return ui_progressbar_var(obj, var); +} + +UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var) { + GtkWidget *progressbar = gtk_progress_bar_new(); + if(var && var->value) { + UiDouble *value = var->value; + value->get = ui_progressbar_get; + value->set = ui_progressbar_set; + value->obj = progressbar; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 0.5); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, progressbar, FALSE); + + return progressbar; +} + +double ui_progressbar_get(UiDouble *d) { + d->value = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj)); + return d->value; +} + +void ui_progressbar_set(UiDouble *d, double value) { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value); + d->value = value; +} diff --git a/ui/gtk/display.h b/ui/gtk/display.h new file mode 100644 index 0000000..f9d94e8 --- /dev/null +++ b/ui/gtk/display.h @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LABEL_H +#define LABEL_H + +#include "../ui/toolkit.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var); +double ui_progressbar_get(UiDouble *d); +void ui_progressbar_set(UiDouble *d, double value); + +#ifdef __cplusplus +} +#endif + +#endif /* LABEL_H */ + diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c new file mode 100644 index 0000000..d110ad4 --- /dev/null +++ b/ui/gtk/dnd.c @@ -0,0 +1,101 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dnd.h" +#include + +#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;idata, uriarray); +} + +char* ui_selection_gettext(UiSelection *sel) { + guchar *text = gtk_selection_data_get_text(sel->data); + if(text) { + char *textcp = strdup((char*)text); + g_free(text); + return textcp; + } + return NULL; +} + +char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { + gchar **uris = gtk_selection_data_get_uris(sel->data); + if(uris) { + size_t al = 32; + char **array = malloc(al * sizeof(char*)); + size_t i = 0; + while(uris[i] != NULL) { + if(i >= al) { + al *= 2; + array = realloc(array, al * sizeof(char*)); + } + array[i] = strdup((char*)uris[i]); + i++; + } + *nelm = i; + g_strfreev(uris); + return array; + } + return NULL; +} diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h new file mode 100644 index 0000000..43ab5c2 --- /dev/null +++ b/ui/gtk/dnd.h @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DND_H +#define DND_H + +#include "../ui/dnd.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* DND_H */ + diff --git a/ui/gtk/draw_cairo.c b/ui/gtk/draw_cairo.c new file mode 100644 index 0000000..6e1ebca --- /dev/null +++ b/ui/gtk/draw_cairo.c @@ -0,0 +1,132 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "container.h" + +#include "draw_cairo.h" + +#ifdef UI_GTK3 +gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) { + UiCairoGraphics g; + g.g.width = gtk_widget_get_allocated_width(w); + g.g.height = gtk_widget_get_allocated_height(w); + g.widget = w; + g.cr = cr; + + UiDrawEvent *event = data; + UiEvent ev; + ev.obj = event->obj; + ev.window = event->obj->window; + ev.document = event->obj->ctx->document; + + event->callback(&ev, &g.g, event->userdata); + + return FALSE; +} +#else +gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) { + UiCairoGraphics g; + g.g.width = w->allocation.width; + g.g.height = w->allocation.height; + g.widget = w; + g.cr = gdk_cairo_create(w->window); + + UiDrawEvent *event = data; + UiEvent ev; + ev.obj = event->obj; + ev.window = event->obj->window; + ev.document = event->obj->ctx->document; + + event->callback(&ev, &g.g, event->userdata); + + return FALSE; +} +#endif + +// function from graphics.h + +void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) { +#ifdef UI_GTK3 + g_signal_connect(G_OBJECT(widget), + "draw", + G_CALLBACK(ui_drawingarea_expose), + event); +#else + g_signal_connect(G_OBJECT(widget), + "expose_event", + G_CALLBACK(ui_canvas_expose), + event); +#endif +} + + +PangoContext *ui_get_pango_context(UiGraphics *g) { + UiCairoGraphics *gr = (UiCairoGraphics*)g; + //return gtk_widget_get_pango_context(gr->widget); + return pango_cairo_create_context(gr->cr); +} + + +// drawing functions +void ui_graphics_color(UiGraphics *g, int red, int green, int blue) { + UiCairoGraphics *gr = (UiCairoGraphics*)g; + double dred = (double)red / (double)255; + double dgreen = (double)green / (double)255; + double dblue = (double)blue / (double)255; + cairo_set_source_rgb(gr->cr, dred, dgreen, dblue); +} + + +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) { + UiCairoGraphics *gr = (UiCairoGraphics*)g; + cairo_set_line_width(gr->cr, 1); + cairo_move_to(gr->cr, (double)x1 + 0.5, (double)y1 + 0.5); + cairo_line_to(gr->cr, (double)x2 + 0.5, (double)y2 + 0.5); + cairo_stroke(gr->cr); +} + +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { + UiCairoGraphics *gr = (UiCairoGraphics*)g; + cairo_set_line_width(gr->cr, 1); + cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h); + if(fill) { + cairo_fill(gr->cr); + } else { + cairo_stroke(gr->cr); + } +} + +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) { + UiCairoGraphics *gr = (UiCairoGraphics*)g; + cairo_move_to(gr->cr, x, y); + pango_cairo_show_layout(gr->cr, text->layout); +} + diff --git a/ui/gtk/draw_cairo.h b/ui/gtk/draw_cairo.h new file mode 100644 index 0000000..6be96a7 --- /dev/null +++ b/ui/gtk/draw_cairo.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAW_CAIRO_H +#define DRAW_CAIRO_H + +#include "graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiCairoGraphics { + UiGraphics g; + GtkWidget *widget; + cairo_t *cr; +} UiCairoGraphics; + +// ui_canvas_expose + +#ifdef __cplusplus +} +#endif + +#endif /* DRAW_CAIRO_H */ + diff --git a/ui/gtk/draw_gdk.c b/ui/gtk/draw_gdk.c new file mode 100644 index 0000000..5e43ed7 --- /dev/null +++ b/ui/gtk/draw_gdk.c @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "container.h" + +#include "draw_gdk.h" + + +gboolean ui_drawingarea_expose(GtkWidget *w, GdkEventExpose *e, void *data) { + UiGdkGraphics g; + g.g.width = w->allocation.width; + g.g.height = w->allocation.height; + g.widget = w; + g.gc = gdk_gc_new(w->window); + + UiDrawEvent *event = data; + UiEvent ev; + ev.obj = event->obj; + ev.window = event->obj->window; + ev.document = event->obj->ctx->document; + + event->callback(&ev, &g.g, event->userdata); + + return FALSE; +} + +// function from graphics.h + +void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) { + g_signal_connect(G_OBJECT(widget), + "expose_event", + G_CALLBACK(ui_drawingarea_expose), + event); +} + +PangoContext *ui_get_pango_context(UiGraphics *g) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + return gtk_widget_get_pango_context(gr->widget); +} + +// drawing functions +void ui_graphics_color(UiGraphics *g, int red, int green, int blue) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + GdkColor color; + color.red = red * 257; + color.green = green * 257; + color.blue = blue * 257; + gdk_gc_set_rgb_fg_color(gr->gc, &color); + //gdk_gc_set_rgb_bg_color(g->gc, &color); +} + +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_line(gr->widget->window, gr->gc, x1, y1, x2, y2); +} + +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_rectangle(gr->widget->window, gr->gc, fill, x, y, w, h); +} + +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_layout(gr->widget->window, gr->gc, x, y, text->layout); +} diff --git a/ui/gtk/draw_gdk.h b/ui/gtk/draw_gdk.h new file mode 100644 index 0000000..f4aa821 --- /dev/null +++ b/ui/gtk/draw_gdk.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAW_GDK_H +#define DRAW_GDK_H + +#include "graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiGdkGraphics { + UiGraphics g; + GtkWidget *widget; + GdkGC *gc; +} UiGdkGraphics; + +gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* DRAW_GDK_H */ + diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c new file mode 100644 index 0000000..631beb0 --- /dev/null +++ b/ui/gtk/entry.c @@ -0,0 +1,214 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" +#include "entry.h" + +#include + +UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = i; + var->type = UI_VAR_SPECIAL; + return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER); +} + +UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = d; + var->type = UI_VAR_SPECIAL; + return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE); +} + +UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = r; + var->type = UI_VAR_SPECIAL; + return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE); +} + +UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER); + return ui_spinner_var(obj, step, 0, var, UI_VAR_INTEGER); +} + +UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_DOUBLE); + return ui_spinner_var(obj, step, digits, var, UI_VAR_DOUBLE); +} + +UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_RANGE); + UiRange *r = var->value; + return ui_spinner_var(obj, r->extent, 1, var, UI_VAR_RANGE); +} + +UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type) { + double min = 0; + double max = 1000; + if(type == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(step == 0) { + step = 1; + } +#ifdef UI_GTK2LEGACY + if(min == max) { + max = min + 1; + } +#endif + GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits); + if(var) { + double value = 0; + UiObserver **obs = NULL; + switch(type) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = spin; + value = (double)i->value; + obs = &i->observers; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = spin; + value = d->value; + obs = &d->observers; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = spin; + value = r->value; + obs = &r->observers; + break; + } + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value); + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->var = var; + event->observers = obs; + + g_signal_connect( + spin, + "value-changed", + G_CALLBACK(ui_spinner_changed), + event); + g_signal_connect( + spin, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, spin, FALSE); + + return spin; +} + +void ui_spinner_setrange(UIWIDGET spinner, double min, double max) { + gtk_spin_button_set_range(GTK_SPIN_BUTTON(spinner), min, max); +} + +void ui_spinner_setdigits(UIWIDGET spinner, int digits) { + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), digits); +} + + +void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = event->var->value; + e.intval = 0; + + UiObserver *observer = *event->observers; + ui_notify_evt(observer, &e); +} + + +int64_t ui_spinbutton_getint(UiInteger *i) { + i->value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(i->obj)); + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->obj), (double)val); + i->value = val; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + d->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->obj)); + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->obj), val); + d->value = val; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + r->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(r->obj)); + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->obj), val); + r->value = val; +} +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + gtk_spin_button_set_range(GTK_SPIN_BUTTON(r->obj), min, max); + r->min = min; + r->max = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + gtk_spin_button_set_increments(GTK_SPIN_BUTTON(r->obj), extent, extent*10); + r->extent = extent; +} diff --git a/ui/gtk/entry.h b/ui/gtk/entry.h new file mode 100644 index 0000000..34961ab --- /dev/null +++ b/ui/gtk/entry.h @@ -0,0 +1,45 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/* + * File: entry.h + * Author: olaf + * + * Created on 11. November 2017, 13:38 + */ + +#ifndef ENTRY_H +#define ENTRY_H + +#include "toolkit.h" +#include "../ui/entry.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVarType type); +void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event); + +int64_t ui_spinbutton_getint(UiInteger *i); +void ui_spinbutton_setint(UiInteger *i, int64_t val); + +double ui_spinbutton_getdouble(UiDouble *d); +void ui_spinbutton_setdouble(UiDouble *d, double val); + +double ui_spinbutton_getrangeval(UiRange *r); +void ui_spinbutton_setrangeval(UiRange *r, double val); +void ui_spinbutton_setrange(UiRange *r, double min, double max); +void ui_spinbutton_setextent(UiRange *r, double extent); + + +#ifdef __cplusplus +} +#endif + +#endif /* ENTRY_H */ + diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c new file mode 100644 index 0000000..b7c279f --- /dev/null +++ b/ui/gtk/graphics.c @@ -0,0 +1,157 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "graphics.h" +#include "container.h" +#include "../common/object.h" + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { + GtkWidget *widget = gtk_drawing_area_new(); + + if(f) { + UiDrawEvent *event = malloc(sizeof(UiDrawEvent)); + event->obj = obj; + event->callback = f; + event->userdata = userdata; + ui_connect_draw_handler(widget, event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, widget, TRUE); + + return widget; +} + + +static gboolean widget_button_pressed( + GtkWidget *widget, + GdkEvent *event, + gpointer userdata) +{ + UiEventData *eventdata = userdata; + + UiMouseEvent me; + me.x = (int)event->button.x; + me.y = (int)event->button.y; + + int exec = 0; + if(event->button.type == GDK_BUTTON_PRESS) { + exec = 1; + me.type = UI_PRESS; + } else if(event->button.type == GDK_2BUTTON_PRESS) { + exec = 1; + me.type = UI_PRESS2; + } + + if(exec) { + UiEvent e; + e.obj = eventdata->obj; + e.window = eventdata->obj->window; + e.document = eventdata->obj->ctx->document; + e.eventdata = &me; + e.intval = 0; + eventdata->callback(&e, eventdata->userdata); + } + return TRUE; +} + +void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) { +#ifdef UI_GTK3 + *width = gtk_widget_get_allocated_width(drawingarea); + *height = gtk_widget_get_allocated_height(drawingarea); +#else + *width = drawingarea->allocation.width; + *height = drawingarea->allocation.height; +#endif +} + +void ui_drawingarea_redraw(UIWIDGET drawingarea) { + gtk_widget_queue_draw(drawingarea); +} + +void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) { + gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK); + if(f) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->callback = f; + event->userdata = u; + + g_signal_connect(G_OBJECT(widget), + "button-press-event", + G_CALLBACK(widget_button_pressed), + event); + } else { + // TODO: warning + } +} + + +// text layout +UiTextLayout* ui_text(UiGraphics *g) { + UiTextLayout *layout = malloc(sizeof(UiTextLayout)); + PangoContext *pc = ui_get_pango_context(g); + layout->layout = pango_layout_new(pc); + return layout; +} + +void ui_text_setstring(UiTextLayout *layout, char *str) { + pango_layout_set_text(layout->layout, str, -1); +} + +void ui_text_setstringl(UiTextLayout *layout, char *str, int len) { + pango_layout_set_text(layout->layout, str, len); +} + +void ui_text_setfont(UiTextLayout *layout, char *font, int size) { + PangoFontDescription *fontDesc; + fontDesc = pango_font_description_from_string(font); + pango_font_description_set_size(fontDesc, size * PANGO_SCALE); + pango_layout_set_font_description(layout->layout, fontDesc); + pango_font_description_free(fontDesc); +} + +void ui_text_getsize(UiTextLayout *layout, int *width, int *height) { + pango_layout_get_size(layout->layout, width, height); + *width = *width / PANGO_SCALE; + *height = *height / PANGO_SCALE; +} + +void ui_text_setwidth(UiTextLayout *layout, int width) { + pango_layout_set_width(layout->layout, width * PANGO_SCALE); + pango_layout_set_ellipsize(layout->layout, PANGO_ELLIPSIZE_END); + //pango_layout_set_wrap(layout->layout, PANGO_WRAP_WORD_CHAR); +} + +void ui_text_free(UiTextLayout *text) { + g_object_unref(text->layout); + free(text); +} diff --git a/ui/gtk/graphics.h b/ui/gtk/graphics.h new file mode 100644 index 0000000..67616bd --- /dev/null +++ b/ui/gtk/graphics.h @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAWINGAREA_H +#define DRAWINGAREA_H + +#include "../ui/graphics.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiDrawEvent { + ui_drawfunc callback; + UiObject *obj; + void *userdata; +} UiDrawEvent; + +struct UiTextLayout { + PangoLayout *layout; +}; + +// implemented in draw_*.h +void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event); +PangoContext *ui_get_pango_context(UiGraphics *g); + +#ifdef __cplusplus +} +#endif + +#endif /* DRAWINGAREA_H */ + diff --git a/ui/gtk/image.c b/ui/gtk/image.c new file mode 100644 index 0000000..7b37be7 --- /dev/null +++ b/ui/gtk/image.c @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "image.h" +#include "../common/properties.h" + +static UcxMap *image_map; + +static GtkIconTheme *icon_theme; + +void ui_image_init(void) { + image_map = ucx_map_new(8); + + icon_theme = gtk_icon_theme_get_default(); +} + +// **** deprecated functions **** + +GdkPixbuf* ui_get_image(const char *name) { + UiImage *img = ucx_map_cstr_get(image_map, name); + if(img) { + return img->pixbuf; + } else { + //ui_add_image(name, name); + //return ucx_map_cstr_get(image_map, name); + // TODO + return NULL; + } +} + +// **** new functions **** + +static UiIcon* get_icon(const char *name, int size, int scale) { +#ifdef UI_SUPPORTS_SCALE + GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0); +#else + GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0); +#endif + if(info) { + UiIcon *icon = malloc(sizeof(UiIcon)); + icon->info = info; + return icon; + } + return NULL; +} + +UiIcon* ui_icon(const char *name, int size) { + return get_icon(name, size, ui_get_scalefactor()); +} + +UiIcon* ui_icon_unscaled(const char *name, int size) { + return get_icon(name, size, 1); +} + +void ui_free_icon(UiIcon *icon) { + g_object_unref(icon->info); + free(icon); +} + +UiImage* ui_icon_image(UiIcon *icon) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + if(pixbuf) { + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + return img; + } + return NULL; +} + +UiImage* ui_image(const char *filename) { + return ui_named_image(filename, NULL); +} + +UiImage* ui_named_image(const char *filename, const char *name) { + char *path = uic_get_image_path(filename); + if(!path) { + fprintf(stderr, "UiError: pixmaps directory not set\n"); + return NULL; + } + UiImage *img = ui_load_image_from_path(path, name); + free(path); + return img; +} + +UiImage* ui_load_image_from_path(const char *path, const char *name) { + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error); + if(!pixbuf) { + fprintf(stderr, "UiError: Cannot load image: %s\n", path); + return NULL; + } + + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + if(name) { + ucx_map_cstr_put(image_map, name, img); + } + return img; +} + +void ui_free_image(UiImage *img) { + g_object_unref(img->pixbuf); + free(img); +} diff --git a/ui/gtk/image.h b/ui/gtk/image.h new file mode 100644 index 0000000..d335d54 --- /dev/null +++ b/ui/gtk/image.h @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef IMAGE_H +#define IMAGE_H + +#include "../ui/image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10 +#define UI_SUPPORTS_SCALE +#endif + + +struct UiIcon { + GtkIconInfo *info; +}; + +struct UiImage { + GdkPixbuf *pixbuf; +}; + +void ui_image_init(void); + +GdkPixbuf* ui_get_image(const char *name); + + +#ifdef __cplusplus +} +#endif + +#endif /* IMAGE_H */ + diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c new file mode 100644 index 0000000..f7be2f5 --- /dev/null +++ b/ui/gtk/menu.c @@ -0,0 +1,609 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "menu.h" +#include "toolkit.h" +#include "../common/context.h" +#include "../ui/properties.h" +#include "../ui/window.h" +#include "container.h" + +static UcxList *menus; +static UcxList *current; + +void ui_menu(char *label) { + // free current menu hierarchy + ucx_list_free(current); + + // create menu + UiMenu *menu = malloc(sizeof(UiMenu)); + menu->item.add_to = (ui_menu_add_f)add_menu_widget; + + menu->label = label; + menu->items = NULL; + menu->parent = NULL; + + current = ucx_list_prepend(NULL, menu); + menus = ucx_list_append(menus, menu); + +} + +void ui_submenu(char *label) { + UiMenu *menu = malloc(sizeof(UiMenu)); + menu->item.add_to = (ui_menu_add_f)add_menu_widget; + + menu->label = label; + menu->items = NULL; + menu->parent = NULL; + + // add submenu to current menu + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, menu); + + // set the submenu to current menu + current = ucx_list_prepend(current, menu); +} + +void ui_submenu_end() { + if(ucx_list_size(current) < 2) { + return; + } + current = ucx_list_remove(current, current); + //UcxList *c = current; +} + +void ui_menuitem(char *label, ui_callback f, void *userdata) { + ui_menuitem_gr(label, f, userdata, -1); +} + +void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) { + ui_menuitem_stgr(stockid, f, userdata, -1); +} + +void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) { + if(!current) { + return; + } + + UiMenuItem *item = malloc(sizeof(UiMenuItem)); + item->item.add_to = (ui_menu_add_f)add_menuitem_widget; + + item->label = label; + item->userdata = userdata; + item->callback = f; + item->groups = NULL; + + // add groups + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + va_end(ap); + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) { + if(!current) { + return; + } + + UiStMenuItem *item = malloc(sizeof(UiStMenuItem)); + item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget; + + item->stockid = stockid; + item->userdata = userdata; + item->callback = f; + item->groups = NULL; + + // add groups + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + va_end(ap); + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +void ui_menuseparator() { + if(!current) { + return; + } + + UiMenuItemI *item = malloc(sizeof(UiMenuItemI)); + item->add_to = (ui_menu_add_f)add_menuseparator_widget; + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +void ui_checkitem(char *label, ui_callback f, void *userdata) { + if(!current) { + return; + } + + UiCheckItem *item = malloc(sizeof(UiCheckItem)); + item->item.add_to = (ui_menu_add_f)add_checkitem_widget; + item->label = label; + item->callback = f; + item->userdata = userdata; + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +void ui_checkitem_nv(char *label, char *vname) { + if(!current) { + return; + } + + UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV)); + item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget; + item->varname = vname; + item->label = label; + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) { + if(!current) { + return; + } + + UiMenuItemList *item = malloc(sizeof(UiMenuItemList)); + item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget; + item->callback = f; + item->userdata = userdata; + item->list = items; + + UiMenu *cm = current->data; + cm->items = ucx_list_append(cm->items, item); +} + +// private menu functions +GtkWidget *ui_create_menubar(UiObject *obj) { + if(menus == NULL) { + return NULL; + } + + GtkWidget *mb = gtk_menu_bar_new(); + + UcxList *ls = menus; + while(ls) { + UiMenu *menu = ls->data; + menu->item.add_to(mb, 0, &menu->item, obj); + + ls = ls->next; + } + + return mb; +} + +void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenu *menu = (UiMenu*)item; + + GtkWidget *menu_widget = gtk_menu_new(); + GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget); + + UcxList *ls = menu->items; + int index = 0; + while(ls) { + UiMenuItemI *i = ls->data; + i->add_to(menu_widget, index, i, obj); + + ls = ls->next; + index++; + } + + gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item); +} + +void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *i = (UiMenuItem*)item; + + //GtkWidget *widget = gtk_menu_item_new_with_label(i->title); + GtkWidget *widget = gtk_menu_item_new_with_mnemonic(i->label); + + if(i->callback != NULL) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = i->userdata; + event->callback = i->callback; + event->value = 0; + + g_signal_connect( + widget, + "activate", + G_CALLBACK(ui_menu_event_wrapper), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); + + if(i->groups) { + uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups); + } +} + +void add_menuitem_st_widget( + GtkWidget *parent, + int index, + UiMenuItemI *item, + UiObject *obj) +{ + UiStMenuItem *i = (UiStMenuItem*)item; + + GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group); + + if(i->callback != NULL) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = i->userdata; + event->callback = i->callback; + event->value = 0; + + g_signal_connect( + widget, + "activate", + G_CALLBACK(ui_menu_event_wrapper), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); + + if(i->groups) { + uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups); + } +} + +void add_menuseparator_widget( + GtkWidget *parent, + int index, + UiMenuItemI *item, + UiObject *obj) +{ + gtk_menu_shell_append( + GTK_MENU_SHELL(parent), + gtk_separator_menu_item_new()); +} + +void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { + UiCheckItem *ci = (UiCheckItem*)item; + GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); + gtk_menu_shell_append(GTK_MENU_SHELL(p), widget); + + if(ci->callback) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = ci->userdata; + event->callback = ci->callback; + event->value = 0; + + g_signal_connect( + widget, + "toggled", + G_CALLBACK(ui_menu_event_toggled), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } +} + +void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { + UiCheckItemNV *ci = (UiCheckItemNV*)item; + GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); + gtk_menu_shell_append(GTK_MENU_SHELL(p), widget); + + UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + value->obj = widget; + value->get = ui_checkitem_get; + value->set = ui_checkitem_set; + value = 0; + } else { + // TODO: error + } +} + +void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuItemList *il = (UiMenuItemList*)item; + UcxMempool *mp = obj->ctx->mempool; + + UiActiveMenuItemList *ls = ucx_mempool_malloc( + mp, + sizeof(UiActiveMenuItemList)); + + ls->object = obj; + ls->menu = GTK_MENU_SHELL(p); + ls->index = index; + ls->oldcount = 0; + ls->list = il->list; + ls->callback = il->callback; + ls->userdata = il->userdata; + + ls->list->observers = ui_add_observer( + ls->list->observers, + (ui_callback)ui_update_menuitem_list, + ls); + + ui_update_menuitem_list(NULL, ls); +} + + +void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) { + // remove old items + if(list->oldcount > 0) { + int i = 0; + GList *mi = gtk_container_get_children(GTK_CONTAINER(list->menu)); + while(mi) { + if(i >= list->index && i < list->index + list->oldcount) { + //gtk_container_remove(GTK_CONTAINER(list->menu), mi->data); + gtk_widget_destroy(mi->data); + } + mi = mi->next; + i++; + } + } + + char *str = ui_list_first(list->list); + if(str) { + GtkWidget *widget = gtk_separator_menu_item_new(); + gtk_menu_shell_insert(list->menu, widget, list->index); + gtk_widget_show(widget); + } + int i = 1; + while(str) { + GtkWidget *widget = gtk_menu_item_new_with_label(str); + gtk_menu_shell_insert(list->menu, widget, list->index + i); + gtk_widget_show(widget); + + if(list->callback) { + // TODO: use mempool + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = list->object; + event->userdata = list->userdata; + event->callback = list->callback; + event->value = i - 1; + + g_signal_connect( + widget, + "activate", + G_CALLBACK(ui_menu_event_wrapper), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + str = ui_list_next(list->list); + i++; + } + + list->oldcount = i; +} + +void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) { + UiEvent evt; + evt.obj = event->obj; + evt.window = event->obj->window; + evt.document = event->obj->ctx->document; + evt.eventdata = NULL; + evt.intval = event->value; + event->callback(&evt, event->userdata); +} + +void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) { + UiEvent evt; + evt.obj = event->obj; + evt.window = event->obj->window; + evt.document = event->obj->ctx->document; + evt.eventdata = NULL; + evt.intval = gtk_check_menu_item_get_active(ci); + event->callback(&evt, event->userdata); +} + +int64_t ui_checkitem_get(UiInteger *i) { + int state = gtk_check_menu_item_get_active(i->obj); + i->value = state; + return state; +} + +void ui_checkitem_set(UiInteger *i, int64_t value) { + i->value = value; + gtk_check_menu_item_set_active(i->obj, value); +} + + +/* + * widget menu functions + */ + +static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) { + if(event->type == GDK_BUTTON_PRESS) { + GdkEventButton *e = (GdkEventButton*)event; + if(e->button == 3) { + gtk_widget_show_all(GTK_WIDGET(menu)); + ui_contextmenu_popup(menu); + return TRUE; + } + } + return FALSE; +} + +UIMENU ui_contextmenu(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + return ui_contextmenu_w(obj, ct->current); +} + +UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) { + UiContainer *ct = uic_get_current_container(obj); + + GtkMenu *menu = GTK_MENU(gtk_menu_new()); + g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu); + + ct->menu = menu; + return menu; +} + +void ui_contextmenu_popup(UIMENU menu) { +#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16 + gtk_menu_popup_at_pointer(menu, NULL); +#else + gtk_menu_popup(menu, NULL, NULL, 0, 0, 0, gtk_get_current_event_time()); +#endif +} + +void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) { + ui_widget_menuitem_gr(obj, label, f, userdata, -1); +} + +void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) { + UiContainer *ct = uic_get_current_container(obj); + if(!ct->menu) { + return; + } + + // add groups + UcxList *groups = NULL; + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + ucx_list_append(groups, (void*)(intptr_t)group); + } + va_end(ap); + + // create menuitem + GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label); + gtk_widget_show(widget); + + if(f) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = userdata; + event->callback = f; + event->value = 0; + + g_signal_connect( + widget, + "activate", + G_CALLBACK(ui_menu_event_wrapper), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget); + + if(groups) { + uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); + } +} + +void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) { + ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1); +} + +void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) { + UiContainer *ct = uic_get_current_container(obj); + if(!ct->menu) { + return; + } + + // add groups + UcxList *groups = NULL; + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + ucx_list_append(groups, (void*)(intptr_t)group); + } + va_end(ap); + + // create menuitem + GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group); + gtk_widget_show(widget); + + if(f) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = userdata; + event->callback = f; + event->value = 0; + + g_signal_connect( + widget, + "activate", + G_CALLBACK(ui_menu_event_wrapper), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget); + + if(groups) { + uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); + } +} diff --git a/ui/gtk/menu.h b/ui/gtk/menu.h new file mode 100644 index 0000000..c42b955 --- /dev/null +++ b/ui/gtk/menu.h @@ -0,0 +1,130 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MENU_H +#define MENU_H + +#include "../ui/menu.h" +#include +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiMenuItemI UiMenuItemI; +typedef struct UiMenu UiMenu; +typedef struct UiMenuItem UiMenuItem; +typedef struct UiStMenuItem UiStMenuItem; +typedef struct UiCheckItem UiCheckItem; +typedef struct UiCheckItemNV UiCheckItemNV; +typedef struct UiMenuItemList UiMenuItemList; + +typedef struct UiActiveMenuItemList UiActiveMenuItemList; + +typedef GtkWidget*(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*); + +struct UiMenuItemI { + ui_menu_add_f add_to; +}; + +struct UiMenu { + UiMenuItemI item; + char *label; + UcxList *items; + UiMenu *parent; +}; + +struct UiMenuItem { + UiMenuItemI item; + ui_callback callback; + char *label; + void *userdata; + UcxList *groups; +}; + +struct UiStMenuItem { + UiMenuItemI item; + ui_callback callback; + char *stockid; + void *userdata; + UcxList *groups; +}; + +struct UiCheckItem { + UiMenuItemI item; + char *label; + ui_callback callback; + void *userdata; +}; + +struct UiCheckItemNV { + UiMenuItemI item; + char *label; + char *varname; +}; + +struct UiMenuItemList { + UiMenuItemI item; + ui_callback callback; + void *userdata; + UiList *list; +}; + +struct UiActiveMenuItemList { + UiObject *object; + GtkMenuShell *menu; + int index; + int oldcount; + UiList *list; + ui_callback callback; + void *userdata; +}; + +GtkWidget *ui_create_menubar(UiObject *obj); + +void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); +void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); +void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); + +void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list); +void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event); +void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event); +int64_t ui_checkitem_get(UiInteger *i); +void ui_checkitem_set(UiInteger *i, int64_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* MENU_H */ + diff --git a/ui/gtk/model.c b/ui/gtk/model.c new file mode 100644 index 0000000..6db4e61 --- /dev/null +++ b/ui/gtk/model.c @@ -0,0 +1,539 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#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;icolumns;i++) { + UiModelType type = info->types[i]; + if(type == UI_ICON_TEXT) { + model->columntypes[ncol] = G_TYPE_OBJECT; + ncol++; + model->columntypes[ncol] = G_TYPE_STRING; + } else { + model->columntypes[ncol] = ui_gtk_type(info->types[i]); + } + ncol++; + } + model->numcolumns = ncol; + return model; +} + +void ui_list_model_dispose(GObject *obj) { + +} + +void ui_list_model_finalize(GObject *obj) { + +} + + +GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) { + return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST); +} + +gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) { + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0); + UiListModel *model = UI_LIST_MODEL(tree_model); + return model->numcolumns; +} + +GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) { + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID); + UiListModel *model = UI_LIST_MODEL(tree_model); + g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID); + return model->columntypes[index]; +} + +gboolean ui_list_model_get_iter( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + g_assert(IS_UI_LIST_MODEL(tree_model)); + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + + // check the depth of the path + // a list must have a depth of 1 + gint depth = gtk_tree_path_get_depth(path); + g_assert(depth == 1); + + // get row + gint *indices = gtk_tree_path_get_indices(path); + gint row = indices[0]; + + // check row + if(row == 0) { + // we don't need to count if the first element is requested + if(list->first(list) == NULL) { + return FALSE; + } + } else if(row >= list->count(list)) { + return FALSE; + } + + // the UiList has an integrated iterator + // we only get a value to adjust it + void *val = NULL; + if(row == 0) { + val = list->first(list); + } else { + val = list->get(list, row); + } + + iter->stamp = model->stamp; + iter->user_data = list->iter; + iter->user_data2 = (gpointer)(intptr_t)row; // list->index + iter->user_data3 = val; + + return val ? TRUE : FALSE; +} + +GtkTreePath* ui_list_model_get_path( + GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL); + g_return_val_if_fail(iter != NULL, NULL); + g_return_val_if_fail(iter->user_data != NULL, NULL); + + UiListModel *model = UI_LIST_MODEL(tree_model); + + GtkTreePath *path = gtk_tree_path_new(); + gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index + + return path; +} + +void ui_list_model_get_value( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + g_return_if_fail(IS_UI_LIST_MODEL(tree_model)); + g_return_if_fail(iter != NULL); + g_return_if_fail(iter->user_data != NULL); + + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + + g_return_if_fail(column < model->numcolumns); + + // TODO: return correct value from column + + //value->g_type = G_TYPE_STRING; + list->iter = iter->user_data; + //list->index = (int)(intptr_t)iter->user_data2; + //list->current = iter->user_data3; + if(model->model->getvalue) { + void *data = model->model->getvalue(iter->user_data3, column); + if(model->columntypes[column] == G_TYPE_OBJECT) { + UiImage *img = data; + ui_model_set_value(model->columntypes[column], img->pixbuf, value); + } else { + ui_model_set_value(model->columntypes[column], data, value); + } + } else { + value->g_type = G_TYPE_INVALID; + } +} + +gboolean ui_list_model_iter_next( + GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); + g_return_val_if_fail(iter != NULL, FALSE); + //g_return_val_if_fail(iter->user_data != NULL, FALSE); + + if(!iter->user_data) { + return FALSE; + } + + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + list->iter = iter->user_data; + //list->index = (int)(intptr_t)iter->user_data2; + void *val = list->next(list); + iter->user_data = list->iter; + intptr_t index = (intptr_t)iter->user_data2; + index++; + //iter->user_data2 = (gpointer)(intptr_t)list->index; + iter->user_data2 = (gpointer)index; + iter->user_data3 = val; + return val ? TRUE : FALSE; +} + +gboolean ui_list_model_iter_children( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); + + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + + if(parent) { + return FALSE; + } + + /* + * a list element has no children + * we set the iter to the first element + */ + void *val = list->first(list); + iter->stamp = model->stamp; + iter->user_data = list->iter; + iter->user_data2 = (gpointer)0; + iter->user_data3 = val; + + return val ? TRUE : FALSE; +} + +gboolean ui_list_model_iter_has_child( + GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + return FALSE; +} + +gint ui_list_model_iter_n_children( + GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_assert(IS_UI_LIST_MODEL(tree_model)); + + if(!iter) { + // return number of rows + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + return list->count(list); + } + + return 0; +} + +gboolean ui_list_model_iter_nth_child( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); + + if(parent) { + return FALSE; + } + + UiListModel *model = UI_LIST_MODEL(tree_model); + UiList *list = model->var->value; + + // check n + if(n == 0) { + // we don't need to count if the first element is requested + if(list->first(list) == NULL) { + return FALSE; + } + } else if(n >= list->count(list)) { + return FALSE; + } + + void *val = list->get(list, n); + iter->stamp = model->stamp; + iter->user_data = list->iter; + iter->user_data2 = (gpointer)(intptr_t)n; // list->index + iter->user_data3 = val; + + return val ? TRUE : FALSE; +} + +gboolean ui_list_model_iter_parent( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return FALSE; +} + +// ****** dnd ****** + +gboolean ui_list_model_drag_data_received( + GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data) +{ + //printf("drag received\n"); + UiListModel *model = UI_LIST_MODEL(drag_dest); + if(model->model->drop) { + gint *indices = gtk_tree_path_get_indices(dest_path); + gint row = indices[0]; + UiEvent e; + e.obj = model->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = 0; + UiSelection s; + s.data = selection_data; + model->model->drop(&e, &s, model->var->value, row); + } + return TRUE; +} + +gboolean ui_list_model_row_drop_possible( + GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data) +{ + //printf("row_drop_possible\n"); + UiListModel *model = UI_LIST_MODEL(drag_dest); + if(model->model->candrop) { + gint *indices = gtk_tree_path_get_indices(dest_path); + gint row = indices[0]; + UiEvent e; + e.obj = model->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = 0; + UiSelection s; + s.data = selection_data; + return model->model->candrop(&e, &s, model->var->value, row); + } + return TRUE; +} + +gboolean ui_list_model_row_draggable( + GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + //printf("row_draggable\n"); + UiListModel *model = UI_LIST_MODEL(drag_source); + if(model->model->candrag) { + gint *indices = gtk_tree_path_get_indices(path); + gint row = indices[0]; + UiEvent e; + e.obj = model->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = 0; + return model->model->candrag(&e, model->var->value, row); + } + return TRUE; +} + +gboolean ui_list_model_drag_data_get( + GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data) +{ + //printf("drag_data_get\n"); + UiListModel *model = UI_LIST_MODEL(drag_source); + if(model->model->data_get) { + gint *indices = gtk_tree_path_get_indices(path); + gint row = indices[0]; + UiEvent e; + e.obj = model->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = 0; + UiSelection s; + s.data = selection_data; + model->model->data_get(&e, &s, model->var->value, row); + } + return TRUE; +} + +gboolean ui_list_model_drag_data_delete( + GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + //printf("drag_data_delete\n"); + UiListModel *model = UI_LIST_MODEL(drag_source); + if(model->model->data_get) { + gint *indices = gtk_tree_path_get_indices(path); + gint row = indices[0]; + UiEvent e; + e.obj = model->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = 0; + model->model->data_delete(&e, model->var->value, row); + } + return TRUE; +} diff --git a/ui/gtk/model.h b/ui/gtk/model.h new file mode 100644 index 0000000..e237826 --- /dev/null +++ b/ui/gtk/model.h @@ -0,0 +1,149 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MODEL_H +#define MODEL_H + +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../ui/tree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListModel UiListModel; + +/* + * UiList to GtkTreeModel wrapper + */ +struct UiListModel { + GObject object; + UiObject *obj; + UiModel *model; + UiVar *var; + GType *columntypes; + int numcolumns; + int stamp; +}; + +/* + * initialize the class and register the type + */ +void ui_list_init(); + +/* + * Creates a UiListModel for a given UiList + */ +UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info); + +void ui_list_model_dispose(GObject *obj); +void ui_list_model_finalize(GObject *obj); + + +// interface functions + +GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model); + +gint ui_list_model_get_n_columns(GtkTreeModel *tree_model); + +GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index); + +gboolean ui_list_model_get_iter( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path); + +GtkTreePath* ui_list_model_get_path( + GtkTreeModel *tree_model, + GtkTreeIter *iter); + +void ui_list_model_get_value( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value); + +gboolean ui_list_model_iter_next( + GtkTreeModel *tree_model, + GtkTreeIter *iter); + +gboolean ui_list_model_iter_children( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent); + +gboolean ui_list_model_iter_has_child( + GtkTreeModel *tree_model, + GtkTreeIter *iter); + +gint ui_list_model_iter_n_children( + GtkTreeModel *tree_model, + GtkTreeIter *iter); + +gboolean ui_list_model_iter_nth_child( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); + +gboolean ui_list_model_iter_parent( + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child); + +/* dnd */ + +gboolean ui_list_model_drag_data_received( + GtkTreeDragDest *drag_dest, + GtkTreePath *dest, + GtkSelectionData *selection_data); + +gboolean ui_list_model_row_drop_possible( + GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data); + +gboolean ui_list_model_row_draggable( + GtkTreeDragSource *drag_source, + GtkTreePath *path); + +gboolean ui_list_model_drag_data_get( + GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data); + +gboolean ui_list_model_drag_data_delete( + GtkTreeDragSource *drag_source, + GtkTreePath *path); + +#ifdef __cplusplus +} +#endif + +#endif /* MODEL_H */ diff --git a/ui/gtk/objs.mk b/ui/gtk/objs.mk new file mode 100644 index 0000000..3043aad --- /dev/null +++ b/ui/gtk/objs.mk @@ -0,0 +1,50 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2017 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +GTK_SRC_DIR = ui/gtk/ +GTK_OBJPRE = $(OBJ_DIR)$(GTK_SRC_DIR) + +# some objects are defined in config.mk +GTKOBJ += toolkit.o +GTKOBJ += window.o +GTKOBJ += container.o +GTKOBJ += menu.o +GTKOBJ += toolbar.o +GTKOBJ += button.o +GTKOBJ += display.o +GTKOBJ += text.o +GTKOBJ += model.o +GTKOBJ += tree.o +GTKOBJ += image.o +GTKOBJ += graphics.o +GTKOBJ += range.o +GTKOBJ += entry.o +GTKOBJ += dnd.o + +TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%) +TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c) diff --git a/ui/gtk/range.c b/ui/gtk/range.c new file mode 100644 index 0000000..c191b0a --- /dev/null +++ b/ui/gtk/range.c @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "range.h" +#include "container.h" +#include +#include "../common/context.h" +#include "../common/object.h" + + +static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) { +#ifdef UI_GTK3 + GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL); +#else + GtkWidget *scrollbar; + if(orientation == UI_HORIZONTAL) { + scrollbar = gtk_hscrollbar_new(NULL); + } else { + scrollbar = gtk_hscrollbar_new(NULL); + } +#endif + + if(range) { + range->get = ui_scrollbar_get; + range->set = ui_scrollbar_set; + range->setrange = ui_scrollbar_setrange; + range->setextent = ui_scrollbar_setextent; + range->obj = scrollbar; + } + + if(f) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = userdata; + event->callback = f; + event->value = 0; + + g_signal_connect( + G_OBJECT(scrollbar), + "value-changed", + G_CALLBACK(ui_scrollbar_value_changed), + event); + g_signal_connect( + scrollbar, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, scrollbar, FALSE); + + return scrollbar; +} + +UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) { + return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata); +} + +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) { + return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata); +} + +gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = event->value; + event->callback(&e, event->userdata); + return TRUE; +} + +double ui_scrollbar_get(UiRange *range) { + double value = gtk_range_get_value(GTK_RANGE(range->obj)); + range->value = value; + return value; +} + +void ui_scrollbar_set(UiRange *range, double value) { + gtk_range_set_value(GTK_RANGE(range->obj), value); + range->value = value; +} + +void ui_scrollbar_setrange(UiRange *range, double min, double max) { + gtk_range_set_range(GTK_RANGE(range->obj), min, max); + range->min = min; + range->max = max; +} + +void ui_scrollbar_setextent(UiRange *range, double extent) { + GtkAdjustment *a = gtk_range_get_adjustment(GTK_RANGE(range->obj)); +#ifdef UI_GTK2LEGACY + a->page_size = extent; +#else + gtk_adjustment_set_page_size(a, extent); +#endif +#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18) + gtk_adjustment_changed(a); +#endif + range->extent = extent; +} diff --git a/ui/gtk/range.h b/ui/gtk/range.h new file mode 100644 index 0000000..0856e3b --- /dev/null +++ b/ui/gtk/range.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RANGE_H +#define RANGE_H + +#include "toolkit.h" +#include "../ui/range.h" + +#ifdef __cplusplus +extern "C" { +#endif + +gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event); + +double ui_scrollbar_get(UiRange *range); +void ui_scrollbar_set(UiRange *range, double value); +void ui_scrollbar_setrange(UiRange *range, double min, double max); +void ui_scrollbar_setextent(UiRange *range, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* RANGE_H */ + diff --git a/ui/gtk/text.c b/ui/gtk/text.c new file mode 100644 index 0000000..2b86ec2 --- /dev/null +++ b/ui/gtk/text.c @@ -0,0 +1,657 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#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 32) { + return 1; + } + } + + return 0; +} + +void ui_text_undo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + if(mgr->cur) { + UiTextBufOp *op = mgr->cur->data; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + GtkTextIter begin; + GtkTextIter end; + gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); + gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); + gtk_text_buffer_delete(value->obj, &begin, &end); + break; + } + case UI_TEXTBUF_DELETE: { + GtkTextIter begin; + GtkTextIter end; + gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); + gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); + gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); + break; + } + } + mgr->event = 1; + mgr->cur = mgr->cur->prev; + } +} + +void ui_text_redo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + UcxList *elm = NULL; + if(mgr->cur) { + if(mgr->cur->next) { + elm = mgr->cur->next; + } + } else if(mgr->begin) { + elm = mgr->begin; + } + + if(elm) { + UiTextBufOp *op = elm->data; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + GtkTextIter begin; + GtkTextIter end; + gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); + gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); + gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); + break; + } + case UI_TEXTBUF_DELETE: { + GtkTextIter begin; + GtkTextIter end; + gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); + gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); + gtk_text_buffer_delete(value->obj, &begin, &end); + break; + } + } + mgr->event = 1; + mgr->cur = elm; + } +} + + +static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) { + GtkWidget *textfield = gtk_entry_new(); + + UiTextField *uitext = malloc(sizeof(UiTextField)); + uitext->ctx = obj->ctx; + uitext->var = var; + + g_signal_connect( + textfield, + "destroy", + G_CALLBACK(ui_textfield_destroy), + uitext); + + if(width > 0) { + gtk_entry_set_width_chars(GTK_ENTRY(textfield), width); + } + if(frameless) { + // TODO: gtk2legacy workaroud + gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); + } + if(password) { + gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, textfield, FALSE); + + if(var) { + UiString *value = var->value; + if(value->value.ptr) { + gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr); + value->value.free(value->value.ptr); + value->value.ptr = NULL; + value->value.free = NULL; + } + + value->get = ui_textfield_get; + value->set = ui_textfield_set; + value->value.ptr = NULL; + value->value.free = NULL; + value->obj = GTK_ENTRY(textfield); + + g_signal_connect( + textfield, + "changed", + G_CALLBACK(ui_textfield_changed), + uitext); + } + + return textfield; +} + +static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING); + if(var) { + return create_textfield_var(obj, width, frameless, password, var); + } else { + // TODO: error + } + return NULL; +} + +static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) { + UiVar *var = NULL; + if(value) { + var = malloc(sizeof(UiVar)); + var->value = value; + var->type = UI_VAR_SPECIAL; + var->from = NULL; + var->from_ctx = NULL; + } + return create_textfield_var(obj, width, frameless, password, var); +} + +void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { + if(textfield->var) { + ui_destroy_boundvar(textfield->ctx, textfield->var); + } + free(textfield); +} + +void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { + UiString *value = textfield->var->value; + if(value->observers) { + UiEvent e; + e.obj = textfield->ctx->obj; + e.window = e.obj->window; + e.document = textfield->ctx->document; + e.eventdata = value; + e.intval = 0; + ui_notify_evt(value->observers, &e); + } +} + +UIWIDGET ui_textfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, FALSE, FALSE, value); +} + +UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, FALSE, FALSE, varname); +} + +UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) { + return create_textfield(obj, width, FALSE, FALSE, value); +} + +UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) { + return create_textfield_nv(obj, width, FALSE, FALSE, varname); +} + +UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, TRUE, FALSE, value); +} + +UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, TRUE, FALSE, varname); +} + +UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, FALSE, TRUE, value); +} + +UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, FALSE, TRUE, varname); +} + +UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) { + return create_textfield(obj, width, FALSE, TRUE, value); +} + +UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) { + return create_textfield_nv(obj, width, FALSE, TRUE, varname); +} + +char* ui_textfield_get(UiString *str) { + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + str->value.ptr = g_strdup(gtk_entry_get_text(str->obj)); + str->value.free = (ui_freefunc)g_free; + return str->value.ptr; +} + +void ui_textfield_set(UiString *str, char *value) { + gtk_entry_set_text(str->obj, value); + if(str->value.ptr) { + str->value.free(str->value.ptr); + str->value.ptr = NULL; + str->value.free = NULL; + } +} diff --git a/ui/gtk/text.h b/ui/gtk/text.h new file mode 100644 index 0000000..0eefa2a --- /dev/null +++ b/ui/gtk/text.h @@ -0,0 +1,112 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "../ui/text.h" +#include "toolkit.h" +#include +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_TEXTBUF_INSERT 0 +#define UI_TEXTBUF_DELETE 1 +typedef struct UiTextBufOp { + int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE + int start; + int end; + int len; + char *text; +} UiTextBufOp; + +typedef struct UiUndoMgr { + UcxList *begin; + UcxList *cur; + int length; + int event; +} UiUndoMgr; + +typedef struct UiTextArea { + UiContext *ctx; + UiVar *var; + int last_selection_state; +} UiTextArea; + +typedef struct UiTextField { + UiContext *ctx; + UiVar *var; + // TODO: validatefunc +} UiTextField; + +UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var); +void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea); + +char* ui_textarea_get(UiText *text); +void ui_textarea_set(UiText *text, char *str); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); +void ui_textarea_remove(UiText *text, int begin, int end); + +void ui_textarea_realize_event(GtkWidget *widget, gpointer data); +void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea); + +void ui_textbuf_insert( + GtkTextBuffer *textbuffer, + GtkTextIter *location, + char *text, + int len, + void *data); +void ui_textbuf_delete( + GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + void *data); +UiUndoMgr* ui_create_undomgr(); +void ui_free_textbuf_op(UiTextBufOp *op); +int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); + +void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield); +void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield); + +char* ui_textfield_get(UiString *str); +void ui_textfield_set(UiString *str, char *value); + +#ifdef __cplusplus +} +#endif + +#endif /* TEXT_H */ + diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c new file mode 100644 index 0000000..3365722 --- /dev/null +++ b/ui/gtk/toolbar.c @@ -0,0 +1,422 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "toolbar.h" +#include "button.h" +#include "image.h" +#include "tree.h" +#include +#include "../common/context.h" + +static UcxMap *toolbar_items; +static UcxList *defaults; + +void ui_toolbar_init() { + toolbar_items = ucx_map_new(16); +} + +void ui_toolitem(char *name, char *label, ui_callback f, void *udata) { + ui_toolitem_img(name, label, NULL, f, udata); +} + +void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) { + ui_toolitem_stgr(name, stockid, f, userdata, -1); +} + +void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *userdata) { + ui_toolitem_stgri(name, stockid, f, userdata, -1); +} + +void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) { + va_list ap; + va_start(ap, userdata); + ui_toolitem_vstgr(name, stockid, 0, f, userdata, ap); + va_end(ap); +} + +void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...) { + va_list ap; + va_start(ap, userdata); + ui_toolitem_vstgr(name, stockid, 1, f, userdata, ap); + va_end(ap); +} + +void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) { + UiToolItem *item = malloc(sizeof(UiToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget; + item->label = label; + item->image = img; + item->callback = f; + item->userdata = udata; + item->isimportant = 0; + item->groups = NULL; + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_vstgr( + char *name, + char *stockid, + int isimportant, + ui_callback f, + void *userdata, + va_list ap) +{ + UiStToolItem *item = malloc(sizeof(UiStToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget; + item->stockid = stockid; + item->callback = f; + item->userdata = userdata; + item->groups = NULL; + item->isimportant = isimportant; + + // add groups + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i) { + UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget; + item->label = label; + item->image = img; + item->stockid = NULL; + item->groups = NULL; + item->isimportant = 0; + item->value = i; + item->var = NULL; + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i) { + UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget; + item->label = NULL; + item->image = NULL; + item->stockid = stockid; + item->groups = NULL; + item->isimportant = 0; + item->value = i; + item->var = NULL; + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar) { + UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget; + item->label = label; + item->image = img; + item->stockid = NULL; + item->groups = NULL; + item->isimportant = 0; + item->value = NULL; + item->var = intvar; + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar) { + UiToggleToolItem *item = malloc(sizeof(UiToggleToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget; + item->label = NULL; + item->image = NULL; + item->stockid = stockid; + item->groups = NULL; + item->isimportant = 0; + item->value = NULL; + item->var = intvar; + + ucx_map_cstr_put(toolbar_items, name, item); +} + + +void ui_toolbar_combobox( + char *name, + UiList *list, + ui_getvaluefunc getvalue, + ui_callback f, + void *udata) +{ + UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox)); + cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox; + UiVar *var = malloc(sizeof(UiVar)); + var->value = list; + var->type = UI_VAR_SPECIAL; + var->from = NULL; + var->from_ctx = NULL; + cb->var = var; + cb->getvalue = getvalue; + cb->callback = f; + cb->userdata = udata; + + ucx_map_cstr_put(toolbar_items, name, cb); +} + +void ui_toolbar_combobox_str( + char *name, + UiList *list, + ui_callback f, + void *udata) +{ + ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata); +} + +void ui_toolbar_combobox_nv( + char *name, + char *listname, + ui_getvaluefunc getvalue, + ui_callback f, + void *udata) +{ + UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV)); + cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv; + cb->listname = listname; + cb->getvalue = getvalue; + cb->callback = f; + cb->userdata = udata; + + ucx_map_cstr_put(toolbar_items, name, cb); +} + + +void ui_toolbar_add_default(char *name) { + char *s = strdup(name); + defaults = ucx_list_append(defaults, s); +} + +GtkWidget* ui_create_toolbar(UiObject *obj) { + if(!defaults) { + return NULL; + } + + GtkWidget *toolbar = gtk_toolbar_new(); +#ifdef UI_GTK3 + gtk_style_context_add_class( + gtk_widget_get_style_context(toolbar), + GTK_STYLE_CLASS_PRIMARY_TOOLBAR); +#endif + + GtkToolbar *tb = GTK_TOOLBAR(toolbar); + UCX_FOREACH(elm, defaults) { + UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data); + if(item) { + item->add_to(tb, item, obj); + } else if(!strcmp(elm->data, "@separator")) { + gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1); + } else { + fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data); + } + } + + return toolbar; +} + +void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj) { + GtkToolItem *button = gtk_tool_button_new(NULL, item->label); + gtk_tool_item_set_homogeneous(button, FALSE); + if(item->image) { + GdkPixbuf *pixbuf = ui_get_image(item->image); + GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image); + } else { + gtk_tool_item_set_is_important(button, TRUE); + } + + if(item->callback) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = item->userdata; + event->callback = item->callback; + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_button_clicked), + event); + } + + gtk_toolbar_insert(tb, button, -1); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups); + } +} + +void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) { + GtkToolItem *button = gtk_tool_button_new_from_stock(item->stockid); + gtk_tool_item_set_homogeneous(button, FALSE); + if(item->isimportant) { + gtk_tool_item_set_is_important(button, TRUE); + } + + if(item->callback) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = item->userdata; + event->callback = item->callback; + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_button_clicked), + event); + } + + gtk_toolbar_insert(tb, button, -1); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups); + } +} + +void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj) { + GtkToolItem *button; + if(item->stockid) { + button = gtk_toggle_tool_button_new_from_stock(item->stockid); + } else { + button = gtk_toggle_tool_button_new(); + gtk_tool_item_set_homogeneous(button, FALSE); + if(item->label) { + gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->label); + } + if(item->image) { + GdkPixbuf *pixbuf = ui_get_image(item->image); + GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image); + } + } + + UiVar *var; + if(item->value) { + var = malloc(sizeof(UiVar)); + var->value = item->value; + var->type = UI_VAR_SPECIAL; + var->from = NULL; + var->from_ctx = NULL; + } else { + var = uic_create_var(obj->ctx, item->var, UI_VAR_INTEGER); + } + + if(var->value) { + UiInteger *i = var->value; + i->get = ui_tool_toggle_button_get; + i->set = ui_tool_toggle_button_set; + i->obj = button; + + if(i->value != 0) { + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE); + } + } + + // register event + // the event func will call the UiInteger observer callbacks + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = var; + event->callback = NULL; + + g_signal_connect( + button, + "toggled", + G_CALLBACK(ui_tool_button_toggled), + event); + + // add item to toolbar + gtk_toolbar_insert(tb, button, -1); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups); + } +} + +void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = gtk_toggle_tool_button_get_active(widget); + + UiVar *var = event->userdata; + UiInteger *i = var->value; + + ui_notify_evt(i->observers, &e); +} + +int64_t ui_tool_toggle_button_get(UiInteger *integer) { + integer->value = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj)); + return integer->value; +} + +void ui_tool_toggle_button_set(UiInteger *integer, int64_t value) { + gboolean s = value != 0 ? TRUE : FALSE; + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj), s); + integer->value = s; +} + +void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) { + UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1); + modelinfo->getvalue = cb->getvalue; + UiListModel *model = ui_list_model_new(obj, cb->var, modelinfo); + + GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata); + GtkToolItem *item = gtk_tool_item_new(); + gtk_container_add(GTK_CONTAINER(item), combobox); + gtk_toolbar_insert(tb, item, -1); +} + +void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj) { + UiVar *var = uic_create_var(obj->ctx, cb->listname, UI_VAR_LIST); + if(var) { + UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1); + modelinfo->getvalue = cb->getvalue; + UiListModel *model = ui_list_model_new(obj, var, modelinfo); + + GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata); + GtkToolItem *item = gtk_tool_item_new(); + gtk_container_add(GTK_CONTAINER(item), combobox); + gtk_toolbar_insert(tb, item, -1); + } +} + diff --git a/ui/gtk/toolbar.h b/ui/gtk/toolbar.h new file mode 100644 index 0000000..68e95ac --- /dev/null +++ b/ui/gtk/toolbar.h @@ -0,0 +1,135 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLBAR_H +#define TOOLBAR_H + +#include "../ui/toolbar.h" +#include +#include + +#include "model.h" +#include "tree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolItemI UiToolItemI; +typedef struct UiToolItem UiToolItem; +typedef struct UiStToolItem UiStToolItem; +typedef struct UiToggleToolItem UiToggleToolItem; + +typedef struct UiToolbarComboBox UiToolbarComboBox; +typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV; + +typedef void(*ui_toolbar_add_f)(GtkToolbar*, UiToolItemI*, UiObject*); + +struct UiToolItemI { + ui_toolbar_add_f add_to; +}; + +struct UiToolItem { + UiToolItemI item; + const char *label; + const char *image; + ui_callback callback; + void *userdata; + const char *varname; + UcxList *groups; + int isimportant; +}; + +struct UiStToolItem { + UiToolItemI item; + const char *stockid; + ui_callback callback; + void *userdata; + const char *varname; + UcxList *groups; + int isimportant; +}; + +struct UiToggleToolItem { + UiToolItemI item; + const char *label; + const char *image; + const char *stockid; + UiInteger *value; + const char *var; + UcxList *groups; + int isimportant; +}; + +struct UiToolbarComboBox { + UiToolItemI item; + UiVar *var; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +struct UiToolbarComboBoxNV { + UiToolItemI item; + char *listname; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +void ui_toolbar_init(); + +void ui_toolitem_vstgr( + char *name, + char *stockid, + int isimportant, + ui_callback f, + void *userdata, + va_list ap); + +GtkWidget* ui_create_toolbar(UiObject *obj); + +void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj); +void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj); +void add_toolitem_toggle_widget(GtkToolbar *tb, UiToggleToolItem *item, UiObject *obj); + +void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj); +void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_update(UiEvent *event, void *combobox); + +void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event); +int64_t ui_tool_toggle_button_get(UiInteger *integer); +void ui_tool_toggle_button_set(UiInteger *integer, int64_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLBAR_H */ + diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c new file mode 100644 index 0000000..a6e0484 --- /dev/null +++ b/ui/gtk/toolkit.c @@ -0,0 +1,264 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "toolbar.h" +#include "model.h" +#include "image.h" +#include "../common/document.h" +#include "../common/properties.h" + +#include + +#include + +#ifndef UI_GTK2 +static GtkApplication *app; +#endif + +static char *application_name; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +void *open_data; +static ui_callback exit_func; +void *exit_data; + +static ui_callback appclose_fnc; +static void *appclose_udata; + +static UiObject *active_window; + +static int scale_factor = 1; + +void ui_init(char *appname, int argc, char **argv) { + uic_init_global_context(); + + gtk_init(&argc, &argv); + application_name = appname; + + uic_docmgr_init(); + ui_toolbar_init(); + + // init custom types + ui_list_init(); + + ui_image_init(); + + uic_load_app_properties(); + +#ifdef UI_SUPPORTS_SCALE + scale_factor = gdk_monitor_get_scale_factor( + gdk_display_get_primary_monitor(gdk_display_get_default())); +#endif +} + +char* ui_appname() { + return application_name; +} + +void ui_onstartup(ui_callback f, void *userdata) { + startup_func = f; + startup_data = userdata; +} + +void ui_onopen(ui_callback f, void *userdata) { + open_func = f; + open_data = userdata; +} + +void ui_onexit(ui_callback f, void *userdata) { + exit_func = f; + exit_data = userdata; +} + + +#ifndef UI_GTK2 +static void app_startup(GtkApplication* app, gpointer userdata) { + if(startup_func) { + startup_func(NULL, startup_data); + } +} + +static void app_activate(GtkApplication* app, gpointer userdata) { + printf("activate\n"); +} +#endif + +void ui_main() { +#ifndef UI_GTK2 + sstr_t appid = ucx_sprintf( + "ui.%s", + application_name ? application_name : "application1"); + + app = gtk_application_new( + appid.ptr, + G_APPLICATION_FLAGS_NONE); + g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + g_application_run(G_APPLICATION (app), 0, NULL); + g_object_unref (app); + + free(appid.ptr); +#else + if(startup_func) { + startup_func(NULL, startup_data); + } + gtk_main(); +#endif + if(exit_func) { + exit_func(NULL, exit_data); + } + uic_store_app_properties(); +} + +#ifndef UI_GTK2 +void ui_app_quit() { + g_application_quit(G_APPLICATION(app)); +} + +GtkApplication* ui_get_application() { + return app; +} +#endif + +void ui_show(UiObject *obj) { + uic_check_group_widgets(obj->ctx); + gtk_widget_show_all(obj->widget); +} + +void ui_close(UiObject *obj) { + gtk_widget_destroy(obj->widget); +} + + +static gboolean ui_job_finished(void *data) { + UiJob *job = data; + + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + + job->finish_callback(&event, job->finish_data); + free(job); + return FALSE; +} + +static void* ui_jobthread(void *data) { + UiJob *job = data; + int result = job->job_func(job->job_data); + if(!result) { + g_idle_add(ui_job_finished, job); + } + return NULL; +} + +void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { + UiJob *job = malloc(sizeof(UiJob)); + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + pthread_t pid; + pthread_create(&pid, NULL, ui_jobthread, job); +} + +void ui_set_enabled(UIWIDGET widget, int enabled) { + gtk_widget_set_sensitive(widget, enabled); +} + +void ui_set_show_all(UIWIDGET widget, int value) { + gtk_widget_set_no_show_all(widget, !value); +} + +void ui_set_visible(UIWIDGET widget, int visible) { + if(visible) { + gtk_widget_set_no_show_all(widget, FALSE); + gtk_widget_show_all(widget); + } else { + gtk_widget_hide(widget); + } +} + +void ui_clipboard_set(char *str) { + GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(cb, str, strlen(str)); +} + +char* ui_clipboard_get() { + GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + char *str = gtk_clipboard_wait_for_text(cb); + if(str) { + char *copy = strdup(str); + g_free(str); + return copy; + } else { + return NULL; + } +} + +int ui_get_scalefactor() { + return scale_factor; +} + +void ui_destroy_userdata(GtkWidget *object, void *userdata) { + free(userdata); +} + +void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) { + ui_destroy_boundvar(data->obj->ctx, data->var); + free(data); +} + +void ui_destroy_boundvar(UiContext *ctx, UiVar *var) { + if(var->type == UI_VAR_SPECIAL) { + free(var); + } else { + uic_remove_bound_var(ctx, var); + } +} + +void ui_set_active_window(UiObject *obj) { + active_window = obj; +} + +UiObject *ui_get_active_window() { + return active_window; +} + + diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h new file mode 100644 index 0000000..48f4d10 --- /dev/null +++ b/ui/gtk/toolkit.h @@ -0,0 +1,90 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLKIT_H +#define TOOLKIT_H + +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +typedef struct UiEventData { + UiObject *obj; + ui_callback callback; + void *userdata; + int value; +} UiEventData; + +typedef struct UiVarEventData { + UiObject *obj; + UiVar *var; + UiObserver **observers; +} UiVarEventData; + + +typedef struct UiJob { + UiObject *obj; + ui_threadfunc job_func; + void *job_data; + ui_callback finish_callback; + void *finish_data; +} UiJob; + +struct UiSelection { + GtkSelectionData *data; +}; + +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; + +#ifndef UI_GTK2 +void ui_app_quit(); +GtkApplication* ui_get_application(); +#endif + +int ui_get_scalefactor(); + +void ui_destroy_userdata(GtkWidget *object, void *userdata); +void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data); +void ui_destroy_boundvar(UiContext *ctx, UiVar *var); + +void ui_set_active_window(UiObject *obj); +UiObject *ui_get_active_window(); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLKIT_H */ + diff --git a/ui/gtk/tree.c b/ui/gtk/tree.c new file mode 100644 index 0000000..d93a6f0 --- /dev/null +++ b/ui/gtk/tree.c @@ -0,0 +1,562 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#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;icolumns;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;iobj; + UiListModel *model = ui_list_model_new(view->obj, view->var, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model)); + g_object_unref(G_OBJECT(model)); + // TODO: free old model +} + +void ui_listview_destroy(GtkWidget *w, UiListView *v) { + gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + +void ui_combobox_destroy(GtkWidget *w, UiListView *v) { + gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + + +void ui_listview_activate_event( + GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event) +{ + UiListSelection *selection = ui_listview_selection( + gtk_tree_view_get_selection(treeview), + event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = selection; + e.intval = selection->count > 0 ? selection->rows[0] : -1; + event->activate(&e, event->userdata); + + if(selection->count > 0) { + free(selection->rows); + } + free(selection); +} + +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event) +{ + UiListSelection *selection = ui_listview_selection(treeselection, event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = selection; + e.intval = selection->count > 0 ? selection->rows[0] : -1; + event->selection(&e, event->userdata); + + if(selection->count > 0) { + free(selection->rows); + } + free(selection); +} + +UiListSelection* ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection *ls = malloc(sizeof(UiListSelection)); + ls->count = g_list_length(rows); + ls->rows = calloc(ls->count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls->rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) { + return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata); +} + +UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = list; + var->type = UI_VAR_SPECIAL; + return ui_combobox_var(obj, var, getvalue, f, udata); +} + +UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); + if(var) { + return ui_combobox_var(obj, var, getvalue, f, udata); + } else { + // TODO: error + } + return NULL; +} + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = getvalue; + UiListModel *listmodel = ui_list_model_new(obj, var, model); + + GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata); + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, combobox, FALSE); + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model)); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + uicbox->var = model->var; + uicbox->model = model->model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + UiList *list = model->var->value; + list->update = ui_combobox_modelupdate; + list->obj = uicbox; + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); + gtk_cell_layout_set_attributes( + GTK_CELL_LAYOUT(combobox), + renderer, + "text", + 0, + NULL); + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + UiListModel *model = ui_list_model_new(view->obj, view->var, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model)); +} + diff --git a/ui/gtk/tree.h b/ui/gtk/tree.h new file mode 100644 index 0000000..e771ea1 --- /dev/null +++ b/ui/gtk/tree.h @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "toolkit.h" +#include "model.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; +} UiListView; + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *userdata; +} UiTreeEventData; + +void* ui_strmodel_getvalue(void *elm, int column); + +UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); + +GtkWidget* ui_get_tree_widget(UIWIDGET widget); + +void ui_listview_update(UiList *list, int i); +void ui_combobox_destroy(GtkWidget *w, UiListView *v); +void ui_listview_destroy(GtkWidget *w, UiListView *v); + +void ui_listview_activate_event( + GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event); +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event); +UiListSelection* ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event); +int ui_tree_path_list_index(GtkTreePath *path); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_modelupdate(UiList *list, int i); + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff --git a/ui/gtk/window.c b/ui/gtk/window.c new file mode 100644 index 0000000..7313b0b --- /dev/null +++ b/ui/gtk/window.c @@ -0,0 +1,187 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "../ui/window.h" +#include "../ui/properties.h" +#include "../common/context.h" + +#include "menu.h" +#include "toolbar.h" +#include "container.h" + +static int nwindows = 0; + +static int window_default_width = 650; +static int window_default_height = 550; + +void ui_exit_event(GtkWidget *widget, gpointer data) { + UiObject *obj = data; + UiEvent ev; + ev.window = obj->window; + ev.document = obj->ctx->document; + ev.obj = obj; + ev.eventdata = NULL; + ev.intval = 0; + + if(obj->ctx->close_callback) { + obj->ctx->close_callback(&ev, obj->ctx->close_data); + } + // TODO: free UiObject + + nwindows--; +#ifdef UI_GTK2 + if(nwindows == 0) { + gtk_main_quit(); + } +#endif +} + +static UiObject* create_window(char *title, void *window_data, UiBool simple) { + UcxMempool *mp = ucx_mempool_new(256); + UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject)); + +#ifndef UI_GTK2 + obj->widget = gtk_application_window_new(ui_get_application()); +#else + obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); +#endif + + + obj->ctx = uic_context(obj, mp); + obj->window = window_data; + + if(title != NULL) { + gtk_window_set_title(GTK_WINDOW(obj->widget), title); + } + + char *width = ui_get_property("ui.window.width"); + char *height = ui_get_property("ui.window.height"); + if(width && height) { + gtk_window_set_default_size( + GTK_WINDOW(obj->widget), + atoi(width), + atoi(height)); + } else { + gtk_window_set_default_size( + GTK_WINDOW(obj->widget), + window_default_width, + window_default_height); + } + + g_signal_connect( + obj->widget, + "destroy", + G_CALLBACK(ui_exit_event), + obj); + + GtkWidget *vbox = ui_gtk_vbox_new(0); + gtk_container_add(GTK_CONTAINER(obj->widget), vbox); + + if(!simple) { + // menu + GtkWidget *mb = ui_create_menubar(obj); + if(mb) { + gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0); + } + + // toolbar + GtkWidget *tb = ui_create_toolbar(obj); + if(tb) { + gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); + } + } + + // window content + // the content has a (TODO: not yet) configurable frame + GtkWidget *frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); + gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + + // content vbox + GtkWidget *content_box = ui_gtk_vbox_new(0); + gtk_container_add(GTK_CONTAINER(frame), content_box); + obj->container = ui_box_container(obj, content_box); + + nwindows++; + return obj; +} + + +UiObject* ui_window(char *title, void *window_data) { + return create_window(title, window_data, FALSE); +} + +UiObject* ui_simplewindow(char *title, void *window_data) { + return create_window(title, window_data, TRUE); +} + +static char* ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action) { + char *button; + char *title; + + if(action == GTK_FILE_CHOOSER_ACTION_OPEN) { + button = GTK_STOCK_OPEN; + title = "Datei öffnen..."; + } else { + button = GTK_STOCK_SAVE; + title = "Datei speichern..."; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + title, + GTK_WINDOW(obj->widget), + action, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + button, + GTK_RESPONSE_ACCEPT, + NULL); + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + gtk_widget_destroy(dialog); + char *copy = strdup(file); + g_free(file); + return copy; + } else { + gtk_widget_destroy(dialog); + return NULL; + } +} + +char* ui_openfiledialog(UiObject *obj) { + return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN); +} + +char* ui_savefiledialog(UiObject *obj) { + return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE); +} + diff --git a/ui/motif/Makefile b/ui/motif/Makefile new file mode 100644 index 0000000..3f7c064 --- /dev/null +++ b/ui/motif/Makefile @@ -0,0 +1,33 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +$(MOTIF_OBJPRE)%.o: motif/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) diff --git a/ui/motif/button.c b/ui/motif/button.c new file mode 100644 index 0000000..49d0701 --- /dev/null +++ b/ui/motif/button.c @@ -0,0 +1,207 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "button.h" +#include "container.h" +#include "../common/context.h" +#include + + +UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { + UiContainer *ct = uic_get_current_container(obj); + XmString str = XmStringCreateLocalized(label); + + int n = 0; + Arg args[16]; + + XtSetArg(args[n], XmNlabelString, str); + n++; + + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget button = XmCreatePushButton(parent, "button", args, n); + ct->add(ct, button); + + if(f) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = data; + event->callback = f; + event->value = 0; + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + event); + } + + XtManageChild(button); + + return button; +} + +// wrapper +int64_t ui_toggle_button_get(UiInteger *i) { + int state = 0; + XtVaGetValues(i->obj, XmNset, &state, NULL); + i->value = state; + return state; +} + +void ui_toggle_button_set(UiInteger *i, int64_t value) { + Arg arg; + XtSetArg(arg, XmNset, value); + XtSetValues(i->obj, &arg, 1); + i->value = value; +} + +void ui_toggle_button_callback( + Widget widget, + UiEventData *event, + XmToggleButtonCallbackStruct *tb) +{ + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + // TODO: e.document + e.intval = tb->set; + event->callback(&e, event->userdata); +} + +void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.intval = event->value; + event->callback(&e, event->userdata); +} + + +static void radio_callback( + Widget widget, + RadioEventData *event, + XmToggleButtonCallbackStruct *tb) +{ + if(tb->set) { + RadioButtonGroup *group = event->group; + if(group->current) { + Arg arg; + XtSetArg(arg, XmNset, FALSE); + XtSetValues(group->current, &arg, 1); + } + group->current = widget; + } +} + +UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) { + UiContainer *ct = uic_get_current_container(obj); + XmString str = XmStringCreateLocalized(label); + + int n = 0; + Arg args[16]; + + XtSetArg(args[n], XmNlabelString, str); + n++; + XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND); + n++; + + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget button = XmCreateToggleButton(parent, "radiobutton", args, n); + ct->add(ct, button); + + if(rgroup) { + RadioButtonGroup *group; + if(rgroup->obj) { + group = rgroup->obj; + group->buttons = ucx_list_append(group->buttons, button); + group->ref++; + } else { + group = malloc(sizeof(RadioButtonGroup)); + group->buttons = ucx_list_append(NULL, button); + group->current = button; + // this is the first button in the radiobutton group + // so we should enable it + Arg arg; + XtSetArg(arg, XmNset, TRUE); + XtSetValues(button, &arg, 1); + rgroup->obj = group; + + group->current = button; + } + + RadioEventData *event = malloc(sizeof(RadioEventData)); + event->obj = obj; + event->callback = NULL; + event->userdata = NULL; + event->group = group; + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)radio_callback, + event); + + rgroup->get = ui_radiobutton_get; + rgroup->set = ui_radiobutton_set; + } + + XtManageChild(button); + return button; +} + +int64_t ui_radiobutton_get(UiInteger *value) { + RadioButtonGroup *group = value->obj; + + int i = ucx_list_find(group->buttons, group->current, NULL, NULL); + if (i >= 0) { + value->value = i; + return i; + } else { + return 0; + } +} + +void ui_radiobutton_set(UiInteger *value, int64_t i) { + RadioButtonGroup *group = value->obj; + Arg arg; + + XtSetArg(arg, XmNset, FALSE); + XtSetValues(group->current, &arg, 1); + + UcxList *elm = ucx_list_get(group->buttons, i); + if(elm) { + Widget button = elm->data; + XtSetArg(arg, XmNset, TRUE); + XtSetValues(button, &arg, 1); + group->current = button; + } +} diff --git a/ui/motif/button.h b/ui/motif/button.h new file mode 100644 index 0000000..b317dd3 --- /dev/null +++ b/ui/motif/button.h @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include "../ui/button.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + UcxList *buttons; + Widget current; + int ref; +} RadioButtonGroup; + +typedef struct { + UiObject *obj; + ui_callback callback; + void *userdata; + RadioButtonGroup *group; +} RadioEventData; + +// wrapper +int64_t ui_toggle_button_get(UiInteger *i); +void ui_toggle_button_set(UiInteger *i, int64_t value); +void ui_toggle_button_callback( + Widget widget, + UiEventData *data, + XmToggleButtonCallbackStruct *e); +void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d); + +int64_t ui_radiobutton_get(UiInteger *value); +void ui_radiobutton_set(UiInteger *value, int64_t i); + +#ifdef __cplusplus +} +#endif + +#endif /* BUTTON_H */ + diff --git a/ui/motif/container.c b/ui/motif/container.c new file mode 100644 index 0000000..a90a678 --- /dev/null +++ b/ui/motif/container.c @@ -0,0 +1,805 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" + +#define UI_GRID_MAX_COLUMNS 512 + +static UiBool ui_lb2bool(UiLayoutBool b) { + return b == UI_LAYOUT_TRUE ? TRUE : FALSE; +} + +static UiLayoutBool ui_bool2lb(UiBool b) { + return b ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE; +} + + +UiContainer* ui_frame_container(UiObject *obj, Widget frame) { + UiContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiContainer)); + ct->widget = frame; + ct->prepare = ui_frame_container_prepare; + ct->add = ui_frame_container_add; + return ct; +} + +Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) { + return ct->widget; +} + +void ui_frame_container_add(UiContainer *ct, Widget widget) { + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation) { + UiBoxContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiBoxContainer)); + ct->container.widget = box; + ct->container.prepare = ui_box_container_prepare; + ct->container.add = ui_box_container_add; + ct->orientation = orientation; + ct->margin = margin; + ct->spacing = spacing; + return (UiContainer*)ct; +} + +Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) { + UiBoxContainer *bc = (UiBoxContainer*)ct; + if(ct->layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(ct->layout.fill); + } + + if(bc->has_fill && fill) { + fprintf(stderr, "UiError: container has 2 filled widgets"); + fill = FALSE; + } + if(fill) { + bc->has_fill = TRUE; + } + + int a = *n; + // determine fixed and dynamic attachments + void *f1; + void *f2; + void *d1; + void *d2; + void *w1; + void *w2; + if(bc->orientation == UI_BOX_VERTICAL) { + f1 = XmNleftAttachment; + f2 = XmNrightAttachment; + d1 = XmNtopAttachment; + d2 = XmNbottomAttachment; + w1 = XmNtopWidget; + w2 = XmNbottomWidget; + + // margin/spacing + XtSetArg(args[a], XmNleftOffset, bc->margin); a++; + XtSetArg(args[a], XmNrightOffset, bc->margin); a++; + + XtSetArg(args[a], XmNtopOffset, bc->prev_widget ? bc->spacing : bc->margin); a++; + } else { + f1 = XmNtopAttachment; + f2 = XmNbottomAttachment; + d1 = XmNleftAttachment; + d2 = XmNrightAttachment; + w1 = XmNleftWidget; + w2 = XmNrightWidget; + + // margin/spacing + XtSetArg(args[a], XmNtopOffset, bc->margin); a++; + XtSetArg(args[a], XmNbottomOffset, bc->margin); a++; + + XtSetArg(args[a], XmNleftOffset, bc->prev_widget ? bc->spacing : bc->margin); a++; + } + XtSetArg(args[a], f1, XmATTACH_FORM); a++; + XtSetArg(args[a], f2, XmATTACH_FORM); a++; + + if(fill) { + XtSetArg(args[a], d2, XmATTACH_FORM); a++; + } + if(bc->prev_widget) { + XtSetArg(args[a], d1, XmATTACH_WIDGET); a++; + XtSetArg(args[a], w1, bc->prev_widget); a++; + } else { + XtSetArg(args[a], d1, XmATTACH_FORM); a++; + } + + *n = a; + return ct->widget; +} + +void ui_box_container_add(UiContainer *ct, Widget widget) { + UiBoxContainer *bc = (UiBoxContainer*)ct; + // determine dynamic attachments + void *d1; + void *d2; + void *w1; + void *w2; + if(bc->orientation == UI_BOX_VERTICAL) { + d1 = XmNtopAttachment; + d2 = XmNbottomAttachment; + w1 = XmNtopWidget; + w2 = XmNbottomWidget; + + } else { + d1 = XmNleftAttachment; + d2 = XmNrightAttachment; + w1 = XmNleftWidget; + w2 = XmNrightWidget; + } + + if(bc->prev_widget) { + int v = 0; + XtVaGetValues(bc->prev_widget, d2, &v, NULL); + if(v == XmATTACH_FORM) { + XtVaSetValues( + bc->prev_widget, + d2, + XmATTACH_WIDGET, + w2, + widget, + NULL); + XtVaSetValues( + widget, + d1, + XmATTACH_NONE, + d2, + XmATTACH_FORM, + NULL); + } + } + bc->prev_widget = widget; + + ui_reset_layout(ct->layout); + ct->current = widget; +} + +UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing) { + UiGridContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiGridContainer)); + ct->container.widget = form; + ct->container.prepare = ui_grid_container_prepare; + ct->container.add = ui_grid_container_add; + ct->columnspacing = columnspacing; + ct->rowspacing = rowspacing; + return (UiContainer*)ct; +} + +void ui_grid_newline(UiGridContainer *grid) { + if(grid->current) { + grid->current = NULL; + } + grid->container.layout.newline = FALSE; +} + +Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) { + UiGridContainer *grid = (UiGridContainer*)ct; + if(ct->layout.newline) { + ui_grid_newline(grid); + } + return ct->widget; +} + +void ui_grid_container_add(UiContainer *ct, Widget widget) { + UiGridContainer *grid = (UiGridContainer*)ct; + + if(grid->current) { + grid->current = ucx_list_append(grid->current, widget); + } else { + grid->current = ucx_list_append(grid->current, widget); + grid->lines = ucx_list_append(grid->lines, grid->current); + } + + ui_reset_layout(ct->layout); + ct->current = widget; +} + +static void ui_grid_resize(Widget widget, XtPointer udata, XtPointer cdata) { + UiGridContainer *grid = udata; + + UcxList *rowdim = NULL; + int coldim[UI_GRID_MAX_COLUMNS]; + memset(coldim, 0, UI_GRID_MAX_COLUMNS*sizeof(int)); + int numcol = 0; + + // get the minimum size of the columns and rows + int sumw = 0; + int sumh = 0; + UCX_FOREACH(row, grid->lines) { + int rheight = 0; + int i=0; + int sum_width = 0; + UCX_FOREACH(elm, row->data) { + Widget w = elm->data; + int widget_width = 0; + int widget_height = 0; + XtVaGetValues( + w, + XmNwidth, + &widget_width, + XmNheight, + &widget_height, + NULL); + + // get the maximum height in this row + if(widget_height > rheight) { + rheight = widget_height; + } + + // get the maximum width in this column + if(widget_width > coldim[i]) { + coldim[i] = widget_width; + } + sum_width += widget_width; + if(sum_width > sumw) { + sumw = sum_width; + } + + i++; + if(i > numcol) { + numcol = i; + } + } + rowdim = ucx_list_append(rowdim, (void*)(intptr_t)rheight); + sumh += rheight; + } + + // check container size + int gwidth = 0; + int gheight = 0; + XtVaGetValues(widget, XmNwidth, &gwidth, XmNheight, &gheight, NULL); + if(gwidth < sumw || gheight < sumh) { + XtVaSetValues(widget, XmNwidth, sumw, XmNheight, sumh, NULL); + ucx_list_free(rowdim); + return; + } + + + // adjust the positions of all children + int y = 0; + UCX_FOREACH(row, grid->lines) { + int x = 0; + int i=0; + UCX_FOREACH(elm, row->data) { + Widget w = elm->data; + XtVaSetValues( + w, + XmNx, x, + XmNy, y, + XmNwidth, coldim[i], + XmNheight, rowdim->data, + NULL); + + x += coldim[i]; + i++; + } + y += (intptr_t)rowdim->data; + rowdim = rowdim->next; + } + + ucx_list_free(rowdim); +} + +UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) { + UiContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiContainer)); + ct->widget = scrolledwindow; + ct->prepare = ui_scrolledwindow_container_prepare; + ct->add = ui_scrolledwindow_container_add; + return ct; +} + +Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) { + return ct->widget; +} + +void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget) { + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +UiContainer* ui_tabview_container(UiObject *obj, Widget frame) { + UiTabViewContainer *ct = ucx_mempool_calloc( + obj->ctx->mempool, + 1, + sizeof(UiTabViewContainer)); + ct->context = obj->ctx; + ct->container.widget = frame; + ct->container.prepare = ui_tabview_container_prepare; + ct->container.add = ui_tabview_container_add; + return (UiContainer*)ct; +} + +Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) { + int a = *n; + XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNtopAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++; + *n = a; + return ct->widget; +} + +void ui_tabview_container_add(UiContainer *ct, Widget widget) { + UiTabViewContainer *tabview = (UiTabViewContainer*)ct; + + if(tabview->current) { + XtUnmanageChild(tabview->current); + } + + tabview->current = widget; + tabview->tabs = ucx_list_append(tabview->tabs, widget); + + ui_select_tab(ct->widget, 0); + ui_reset_layout(ct->layout); + ct->current = widget; +} + +UIWIDGET ui_box(UiObject *obj, int margin, int spacing, UiBoxOrientation orientation) { + UiContainer *ct = uic_get_current_container(obj); + + Arg args[16]; + int n = 0; + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget form = XmCreateForm(parent, "vbox", args, n); + ct->add(ct, form); + XtManageChild(form); + + UiObject *newobj = uic_object_new(obj, form); + newobj->container = ui_box_container(obj, form, margin, spacing, orientation); + uic_obj_add(obj, newobj); + + return form; +} + +UIWIDGET ui_vbox(UiObject *obj) { + return ui_box(obj, 0, 0, UI_BOX_VERTICAL); +} + +UIWIDGET ui_hbox(UiObject *obj) { + return ui_box(obj, 0, 0, UI_BOX_HORIZONTAL); +} + +UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) { + return ui_box(obj, margin, spacing, UI_BOX_VERTICAL); +} + +UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) { + return ui_box(obj, margin, spacing, UI_BOX_HORIZONTAL); +} + +UIWIDGET ui_grid(UiObject *obj) { + return ui_grid_sp(obj, 0, 0, 0); +} + +UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) { + UiContainer *ct = uic_get_current_container(obj); + + Arg args[16]; + int n = 0; + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget grid = XmCreateDrawingArea(parent, "grid", args, n); + ct->add(ct, grid); + XtManageChild(grid); + + UiObject *newobj = uic_object_new(obj, grid); + newobj->container = ui_grid_container(obj, grid, columnspacing, rowspacing); + uic_obj_add(obj, newobj); + + XtAddCallback (grid, XmNresizeCallback , ui_grid_resize, newobj->container); + + return grid; +} + +UIWIDGET ui_scrolledwindow(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + + Arg args[16]; + int n = 0; + XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); // TODO: dosn't work, use XmAPPLICATION_DEFINED + n++; + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", args, n); + ct->add(ct, scrolledwindow); + XtManageChild(scrolledwindow); + + UiObject *newobj = uic_object_new(obj, scrolledwindow); + newobj->container = ui_scrolledwindow_container(obj, scrolledwindow); + uic_obj_add(obj, newobj); + + return scrolledwindow; +} + +UIWIDGET ui_sidebar(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNorientation, XmHORIZONTAL); + n++; + + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget pane = XmCreatePanedWindow(parent, "pane", args, n); + ct->add(ct, pane); + XtManageChild(pane); + + // add sidebar widget + Widget sidebar = XmCreateForm(pane, "sidebar", args, 0); + XtManageChild(sidebar); + + UiObject *left = uic_object_new(obj, sidebar); + left->container = ui_box_container(left, sidebar, 0, 0, UI_BOX_VERTICAL); + + // add content widget + XtSetArg (args[0], XmNpaneMaximum, 8000); + Widget content = XmCreateForm(pane, "content_area", args, 1); + XtManageChild(content); + + UiObject *right = uic_object_new(obj, content); + right->container = ui_box_container(right, content, 0, 0, UI_BOX_VERTICAL); + + uic_obj_add(obj, right); + uic_obj_add(obj, left); + + return sidebar; +} + +UIWIDGET ui_tabview(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + + // create a simple frame as container widget + // when tabs are selected, the current child will be replaced by the + // the new tab widget + Arg args[16]; + int n = 0; + XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_OUT); + n++; + XtSetArg(args[n], XmNshadowThickness, 0); + n++; + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget form = XmCreateForm(parent, "tabview", args, n); + ct->add(ct, form); + XtManageChild(form); + + UiObject *tabviewobj = uic_object_new(obj, form); + tabviewobj->container = ui_tabview_container(obj, form); + uic_obj_add(obj, tabviewobj); + + XtVaSetValues(form, XmNuserData, tabviewobj->container, NULL); + + return form; +} + +void ui_tab(UiObject *obj, char *title) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.label = title; + + ui_vbox(obj); +} + +void ui_select_tab(UIWIDGET tabview, int tab) { + UiTabViewContainer *ct = NULL; + XtVaGetValues(tabview, XmNuserData, &ct, NULL); + if(ct) { + XtUnmanageChild(ct->current); + UcxList *elm = ucx_list_get(ct->tabs, tab); + if(elm) { + XtManageChild(elm->data); + ct->current = elm->data; + } else { + fprintf(stderr, "UiError: front tab index: %d\n", tab); + } + } else { + fprintf(stderr, "UiError: widget is not a tabview\n"); + } +} + + +/* document tabview */ + +static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) { + MotifTabbedPane *v = (MotifTabbedPane*)udata; + + int width = 0; + int height = 0; + XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL); + int button_width = width / 4; + int x = 0; + UCX_FOREACH(elm, v->tabs) { + UiTab *tab = elm->data; + XtVaSetValues( + tab->tab_button, + XmNx, x, + XmNy, 0, + XmNwidth, + button_width, + + NULL); + x += button_width; + } + + if(height <= v->height) { + XtVaSetValues(widget, XmNheight, v->height + 4, NULL); + } +} + +static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) { + MotifTabbedPane *v = (MotifTabbedPane*)udata; + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata; + XEvent *event = cbs->event; + Display *dpy = XtDisplay(widget); + + XGCValues gcvals; + GC gc; + Pixel fgpix; + + int tab_x; + int tab_width; + XtVaGetValues(v->current->tab_button, XmNx, &tab_x, XmNwidth, &tab_width, XmNhighlightColor, &fgpix, NULL); + + gcvals.foreground = v->bg1; + gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals); + + int width = 0; + int height = 0; + XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL); + XFillRectangle(dpy, XtWindow(widget), gc, 0, 0, width, height); + + gcvals.foreground = fgpix; + gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals); + + XFillRectangle(dpy, XtWindow(widget), gc, tab_x, 0, tab_width, height); + +} + +UiTabbedPane* ui_tabbed_document_view(UiObject *obj) { + int n = 0; + Arg args[16]; + + UiContainer *ct = uic_get_current_container(obj); + Widget parent = ct->prepare(ct, args, &n, TRUE); + + Widget tabview = XmCreateForm(parent, "tabview_form", args, n); + XtManageChild(tabview); + + XtSetArg(args[0], XmNorientation, XmHORIZONTAL); + XtSetArg(args[1], XmNpacking, XmPACK_TIGHT); + XtSetArg(args[2], XmNspacing, 1); + XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM); + XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM); + XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM); + XtSetArg(args[6], XmNmarginWidth, 0); + XtSetArg(args[7], XmNmarginHeight, 0); + Widget tabbar = XmCreateDrawingArea(tabview, "tabbar", args, 8); + XtManageChild(tabbar); + + XtSetArg(args[0], XmNleftAttachment, XmATTACH_FORM); + XtSetArg(args[1], XmNrightAttachment, XmATTACH_FORM); + XtSetArg(args[2], XmNtopAttachment, XmATTACH_WIDGET); + XtSetArg(args[3], XmNtopWidget, tabbar); + XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM); + XtSetArg(args[5], XmNshadowThickness, 0); + Widget tabct = XmCreateForm(tabview, "tabview", args, 6); + XtManageChild(tabct); + + MotifTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(MotifTabbedPane)); + tabbedpane->view.ctx = uic_current_obj(obj)->ctx; + tabbedpane->view.widget = tabct; + tabbedpane->view.document = NULL; + tabbedpane->tabbar = tabbar; + tabbedpane->tabs = NULL; + tabbedpane->current = NULL; + tabbedpane->height = 0; + + XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabbedpane); + XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabbedpane); + + return &tabbedpane->view; +} + +UiObject* ui_document_tab(UiTabbedPane *view) { + MotifTabbedPane *v = (MotifTabbedPane*)view; + int n = 0; + Arg args[16]; + + // hide the current tab content + if(v->current) { + XtUnmanageChild(v->current->content->widget); + } + + UiTab *tab = ui_malloc(view->ctx, sizeof(UiTab)); + + // create the new tab content + XtSetArg(args[0], XmNshadowThickness, 0); + XtSetArg(args[1], XmNleftAttachment, XmATTACH_FORM); + XtSetArg(args[2], XmNrightAttachment, XmATTACH_FORM); + XtSetArg(args[3], XmNtopAttachment, XmATTACH_FORM); + XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM); + XtSetArg(args[5], XmNuserData, tab); + Widget frame = XmCreateFrame(view->widget, "tab", args, 6); + XtManageChild(frame); + + UiObject *content = ui_malloc(view->ctx, sizeof(UiObject)); + content->widget = NULL; // initialization for uic_context() + content->ctx = uic_context(content, view->ctx->mempool); + content->ctx->parent = view->ctx; + content->ctx->attach_document = uic_context_attach_document; + content->ctx->detach_document2 = uic_context_detach_document2; + content->widget = frame; + content->window = view->ctx->obj->window; + content->container = ui_frame_container(content, frame); + content->next = NULL; + + // add tab button + v->tabs = ucx_list_append_a(view->ctx->mempool->allocator, v->tabs, tab); + + XmString label = XmStringCreateLocalized("tab"); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNshadowThickness, 0); + XtSetArg(args[2], XmNhighlightThickness, 0); + + Widget button = XmCreatePushButton(v->tabbar, "tab_button", args, 3); + tab->tabbedpane = v; + tab->content = content; + tab->tab_button = button; + XtManageChild(button); + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_tab_button_callback, + tab); + + if(v->height == 0) { + XtVaGetValues( + button, + XmNarmColor, + &v->bg1, + XmNbackground, + &v->bg2, + XmNheight, + &v->height, + NULL); + v->height += 2; // border + } + + ui_change_tab(v, tab); + ui_tabbar_resize(v->tabbar, v, NULL); + + return content; +} + +void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) { + MotifTabbedPane *t = tab->tabbedpane; + if(t->current) { + XtUnmanageChild(t->current->content->widget); + XtVaSetValues(t->current->tab_button, XmNset, 0, NULL); + } + XtManageChild(tab->content->widget); + + ui_change_tab(t, tab); + +} + +void ui_change_tab(MotifTabbedPane *pane, UiTab *tab) { + UiContext *ctx = tab->content->ctx; + ctx->parent->detach_document2(ctx->parent, pane->current->content->ctx->document); + ctx->parent->attach_document(ctx->parent, ctx->document); + + if(pane->current) { + XtVaSetValues(pane->current->tab_button, XmNshadowThickness, 0, XmNbackground, pane->bg1, NULL); + } + XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, pane->bg2, NULL); + + pane->current = tab; + pane->index = ucx_list_find(pane->tabs, tab, NULL, NULL); + printf("index: %d\n", pane->index); + + // redraw tabbar + Display *dpy = XtDisplay(pane->tabbar); + Window window = XtWindow(pane->tabbar); + if(dpy && window) { + XClearArea(dpy, XtWindow(pane->tabbar), 0, 0, 0, 0, TRUE); + XFlush(dpy); + } +} + +void ui_tab_set_document(UiContext *ctx, void *document) { + if(ctx->parent->document) { + //ctx->parent->detach_document(ctx->parent, ctx->parent->document); + } + uic_context_attach_document(ctx, document); + //uic_context_set_document(ctx->parent, document); + //ctx->parent->document = document; + + UiTab *tab = NULL; + XtVaGetValues( + ctx->obj->widget, + XmNuserData, + &tab, + NULL); + if(tab) { + if(tab->tabbedpane->current == tab) { + ctx->parent->attach_document(ctx->parent, ctx->document); + } + } else { + fprintf(stderr, "UiError: ui_bar_set_document: Cannot set document"); + } +} + + + +/* + * -------------------- Layout Functions -------------------- + * + * functions for setting layout attributes for the current container + * + */ + +void ui_layout_fill(UiObject *obj, UiBool fill) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.fill = ui_bool2lb(fill); +} + +void ui_layout_hexpand(UiObject *obj, UiBool expand) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.hexpand = expand; +} + +void ui_layout_vexpand(UiObject *obj, UiBool expand) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.vexpand = expand; +} + +void ui_layout_gridwidth(UiObject *obj, int width) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.gridwidth = width; +} + +void ui_newline(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.newline = TRUE; +} diff --git a/ui/motif/container.h b/ui/motif/container.h new file mode 100644 index 0000000..57c2c3b --- /dev/null +++ b/ui/motif/container.h @@ -0,0 +1,160 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include "../ui/toolkit.h" +#include "../ui/container.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) + +typedef struct MotifTabbedPane MotifTabbedPane; +typedef struct UiTab UiTab; +typedef struct UiBoxContainer UiBoxContainer; +typedef struct UiGridContainer UiGridContainer; +typedef struct UiTabViewContainer UiTabViewContainer; +typedef struct UiLayout UiLayout; + +typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool); + +typedef enum UiLayoutBool UiLayoutBool; +typedef enum UiBoxOrientation UiBoxOrientation; + + +enum UiLayoutBool { + UI_LAYOUT_UNDEFINED = 0, + UI_LAYOUT_TRUE, + UI_LAYOUT_FALSE, +}; + +enum UiBoxOrientation { + UI_BOX_VERTICAL = 0, + UI_BOX_HORIZONTAL +}; + +struct UiLayout { + UiLayoutBool fill; + UiBool newline; + char *label; + UiBool hexpand; + UiBool vexpand; + int gridwidth; +}; + +struct UiContainer { + Widget widget; + Widget (*prepare)(UiContainer*, Arg *, int*, UiBool); + void (*add)(UiContainer*, Widget); + UiLayout layout; + Widget current; + Widget menu; +}; + +struct UiBoxContainer { + UiContainer container; + Widget prev_widget; + UiBool has_fill; + UiBoxOrientation orientation; + int margin; + int spacing; +}; + +struct UiGridContainer { + UiContainer container; + UcxList *lines; + UcxList *current; + int columnspacing; + int rowspacing; +}; + +struct UiTabViewContainer { + UiContainer container; + UiContext *context; + Widget widget; + UcxList *tabs; + Widget current; +}; + +struct MotifTabbedPane { + UiTabbedPane view; + Widget tabbar; + UcxList *tabs; + UiTab *current; + int index; + Pixel bg1; + Pixel bg2; + int height; +}; + +struct UiTab { + MotifTabbedPane *tabbedpane; + UiObject *content; + Widget tab_button; +}; + + +UiContainer* ui_frame_container(UiObject *obj, Widget frame); +Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill); +void ui_frame_container_add(UiContainer *ct, Widget widget); + +UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation); +Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill); +void ui_box_container_add(UiContainer *ct, Widget widget); + +UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing); +Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill); +void ui_grid_container_add(UiContainer *ct, Widget widget); + +UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow); +Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill); +void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget); + +UiContainer* ui_tabview_container(UiObject *obj, Widget rowcolumn); +Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill); +void ui_tabview_container_add(UiContainer *ct, Widget widget); + +void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d); +void ui_change_tab(MotifTabbedPane *pane, UiTab *tab); + +void ui_tab_set_document(UiContext *ctx, void *document); +void ui_tab_detach_document(UiContext *ctx); + + +#ifdef __cplusplus +} +#endif + +#endif /* CONTAINER_H */ + diff --git a/ui/motif/dnd.c b/ui/motif/dnd.c new file mode 100644 index 0000000..2f2313e --- /dev/null +++ b/ui/motif/dnd.c @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dnd.h" + +void ui_selection_settext(UiSelection *sel, char *str, int len) { + +} + +void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) { + +} + +char* ui_selection_gettext(UiSelection *sel) { + return NULL; +} + +char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { + return NULL; +} diff --git a/ui/motif/dnd.h b/ui/motif/dnd.h new file mode 100644 index 0000000..3653692 --- /dev/null +++ b/ui/motif/dnd.h @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DND_H +#define DND_H + +#include "../ui/dnd.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* DND_H */ + diff --git a/ui/motif/graphics.c b/ui/motif/graphics.c new file mode 100644 index 0000000..fefeca6 --- /dev/null +++ b/ui/motif/graphics.c @@ -0,0 +1,283 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "graphics.h" + +#include "container.h" + +static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) { + UiDrawEvent *drawevent = u; + //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c; + //XEvent *event = cbs->event; + Display *dpy = XtDisplay(widget); + + UiEvent ev; + ev.obj = drawevent->obj; + ev.window = drawevent->obj->window; + ev.document = drawevent->obj->ctx->document; + ev.eventdata = NULL; + ev.intval = 0; + + XtVaGetValues( + widget, + XmNwidth, + &drawevent->gr.g.width, + XmNheight, + &drawevent->gr.g.height, + NULL); + + XGCValues gcvals; + gcvals.foreground = BlackPixelOfScreen(XtScreen(widget)); + drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals); + + drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata); +} + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { + UiContainer *ct = uic_get_current_container(obj); + + int n = 0; + Arg args[16]; + + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n); + + if(f) { + UiDrawEvent *event = malloc(sizeof(UiDrawEvent)); + event->obj = obj; + event->callback = f; + event->userdata = userdata; + + event->gr.display = XtDisplay(drawingarea); + event->gr.widget = drawingarea; + + Colormap colormap; + XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL); + event->gr.colormap = colormap; + + XtAddCallback( + drawingarea, + XmNexposeCallback, + ui_drawingarea_expose, + event); + + XtVaSetValues(drawingarea, XmNuserData, event, NULL); + } + + XtManageChild(drawingarea); + return drawingarea; +} + +static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) { + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + UiMouseEventData *event = u; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + UiMouseEvent me; + me.x = xevent->xbutton.x; + me.y = xevent->xbutton.y; + // TODO: configurable double click time + me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2; + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &me; + e.intval = 0; + event->callback(&e, event->userdata); + + + event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time; + } + } + +} + +void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) { + if(f) { + UiMouseEventData *event = malloc(sizeof(UiMouseEventData)); + event->obj = obj; + event->callback = f; + event->userdata = u; + event->last_event = 0; + + XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event); + } +} + +void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) { + XtVaGetValues( + drawingarea, + XmNwidth, + width, + XmNheight, + height, + NULL); +} + +void ui_drawingarea_redraw(UIWIDGET drawingarea) { + //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True); + UiDrawEvent *event; + XtVaGetValues(drawingarea, XmNuserData, &event, NULL); + ui_drawingarea_expose(drawingarea, event, NULL); +} + + +/* -------------------- text layout functions -------------------- */ +UiTextLayout* ui_text(UiGraphics *g) { + UiTextLayout *text = malloc(sizeof(UiTextLayout)); + memset(text, 0, sizeof(UiTextLayout)); + text->text = NULL; + text->length = 0; + text->widget = ((UiXlibGraphics*)g)->widget; + text->fontset = NULL; + return text; +} + +static void create_default_fontset(UiTextLayout *layout) { + char **missing = NULL; + int num_missing = 0; + char *def = NULL; + Display *dpy = XtDisplay(layout->widget); + XFontSet fs = XCreateFontSet( + dpy, + "-dt-interface system-medium-r-normal-s*utf*:," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni," + "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208", + &missing, &num_missing, &def); + layout->fontset = fs; +} + +void ui_text_free(UiTextLayout *text) { + // TODO +} + +void ui_text_setstring(UiTextLayout *layout, char *str) { + ui_text_setstringl(layout, str, strlen(str)); +} + +void ui_text_setstringl(UiTextLayout *layout, char *str, int len) { + layout->text = str; + layout->length = len; + layout->changed = 1; +} + +void ui_text_setfont(UiTextLayout *layout, char *font, int size) { + create_default_fontset(layout);//TODO + layout->changed = 1; +} + +void ui_text_getsize(UiTextLayout *layout, int *width, int *height) { + if(layout->changed) { + XRectangle ext, lext; + XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext); + layout->width = ext.width; + layout->height = ext.height; + layout->changed = 0; + } + *width = layout->width; + *height = layout->height; +} + +void ui_text_setwidth(UiTextLayout *layout, int width) { + layout->maxwidth = width; +} + + +/* -------------------- drawing functions -------------------- */ + +void ui_graphics_color(UiGraphics *g, int red, int green, int blue) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + XColor color; + color.flags= DoRed | DoGreen | DoBlue; + color.red = red * 257; + color.green = green * 257; + color.blue = blue * 257; + XAllocColor(gr->display, gr->colormap, &color); + XSetForeground(gr->display, gr->gc, color.pixel); +} + +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2); +} + +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + if(fill) { + XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h); + } else { + XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h); + } +} + +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + int width, height; + ui_text_getsize(text, &width, &height); + if(text->maxwidth > 0) { + XRectangle clip; + clip.x = x; + clip.y = y; + clip.width = text->maxwidth; + clip.height = height; + XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted); + } + + XmbDrawString( + gr->display, + XtWindow(gr->widget), + text->fontset, + gr->gc, + x, + y + height, + text->text, + text->length); + + XSetClipMask(gr->display, gr->gc, None); +} diff --git a/ui/motif/graphics.h b/ui/motif/graphics.h new file mode 100644 index 0000000..fa248b7 --- /dev/null +++ b/ui/motif/graphics.h @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include "../ui/graphics.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiXlibGraphics { + UiGraphics g; + Display *display; + Widget widget; + Colormap colormap; + GC gc; +} UiXlibGraphics; + +typedef struct UiDrawEvent { + ui_drawfunc callback; + UiObject *obj; + void *userdata; + UiXlibGraphics gr; +} UiDrawEvent; + +typedef struct UiMouseEventData { + UiObject *obj; + ui_callback callback; + void *userdata; + Time last_event; +} UiMouseEventData; + +struct UiTextLayout { + char *text; + size_t length; + Widget widget; + XFontSet fontset; + int maxwidth; + int width; + int height; + int changed; +}; + + +#ifdef __cplusplus +} +#endif + +#endif /* GRAPHICS_H */ + diff --git a/ui/motif/image.c b/ui/motif/image.c new file mode 100644 index 0000000..5142dbc --- /dev/null +++ b/ui/motif/image.c @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +#include "image.h" + +UiIcon* ui_icon(const char *name, int size) { + return NULL; +} + +UiIcon* ui_icon_unscaled(const char *name, int size) { + return NULL; +} + +void ui_free_icon(UiIcon *icon) { + +} + +UiImage* ui_icon_image(UiIcon *icon) { + return NULL; +} + +UiImage* ui_image(const char *filename) { + return NULL; +} + +UiImage* ui_named_image(const char *filename, const char *name) { + return NULL; +} + +UiImage* ui_load_image_from_path(const char *path, const char *name) { + return NULL; +} + +void ui_free_image(UiImage *img) { + +} + diff --git a/ui/motif/image.h b/ui/motif/image.h new file mode 100644 index 0000000..681d232 --- /dev/null +++ b/ui/motif/image.h @@ -0,0 +1,31 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/* + * File: image.h + * Author: olaf + * + * Created on 1. Juli 2018, 19:01 + */ + +#ifndef IMAGE_H +#define IMAGE_H + +#include "../ui/image.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* IMAGE_H */ + diff --git a/ui/motif/label.c b/ui/motif/label.c new file mode 100644 index 0000000..dd1444e --- /dev/null +++ b/ui/motif/label.c @@ -0,0 +1,70 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "label.h" +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" +#include + +UIWIDGET ui_label(UiObject *obj, char *label) { + UiContainer *ct = uic_get_current_container(obj); + XmString str = XmStringCreateLocalized(label); + + int n = 0; + Arg args[16]; + XtSetArg(args[n], XmNlabelString, str); + n++; + + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget widget = XmCreateLabel(parent, "label", args, n); + ct->add(ct, widget); + XtManageChild(widget); + + return widget; +} + +UIWIDGET ui_space(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + XmString str = XmStringCreateLocalized(""); + + int n = 0; + Arg args[16]; + XtSetArg(args[n], XmNlabelString, str); + n++; + + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget widget = XmCreateLabel(parent, "space_label", args, n); + ct->add(ct, widget); + XtManageChild(widget); + + return widget; +} diff --git a/ui/motif/label.h b/ui/motif/label.h new file mode 100644 index 0000000..adc644c --- /dev/null +++ b/ui/motif/label.h @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LABEL_H +#define LABEL_H + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* LABEL_H */ + diff --git a/ui/motif/list.c b/ui/motif/list.c new file mode 100644 index 0000000..a92f9c8 --- /dev/null +++ b/ui/motif/list.c @@ -0,0 +1,207 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#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;ictx->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;inext(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;ievent.obj; + e.window = event->event.obj->window; + e.document = event->event.obj->ctx->document; + UiList *list = event->var->value; + e.eventdata = list->get(list, cbs->item_position - 1); + e.intval = cbs->item_position - 1; + event->event.callback(&e, event->event.userdata); +} + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) { + return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata); +} + +UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = list; + var->type = UI_VAR_SPECIAL; + return ui_combobox_var(obj, var, getvalue, f, udata); +} + +UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); + if(var) { + UiListVar *value = var->value; + return ui_combobox_var(obj, var, getvalue, f, udata); + } else { + // TODO: error + } + return NULL; +} + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiListView *listview = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiListView)); + + UiContainer *ct = uic_get_current_container(obj); + Arg args[16]; + int n = 0; + XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_NONE); + n++; + XtSetArg(args[n], XmNtraversalOn, FALSE); + n++; + XtSetArg(args[n], XmNwidth, 160); + n++; + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget combobox = XmCreateDropDownList(parent, "combobox", args, n); + XtManageChild(combobox); + listview->widget = combobox; + listview->list = var; + listview->getvalue = getvalue; + + ui_listview_update(NULL, listview); + +} diff --git a/ui/motif/list.h b/ui/motif/list.h new file mode 100644 index 0000000..35f97ea --- /dev/null +++ b/ui/motif/list.h @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIST_H +#define LIST_H + +#include "toolkit.h" +#include "../ui/tree.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + Widget widget; + UiVar *list; + ui_getvaluefunc getvalue; +} UiListView; + +typedef struct UiListViewEventData { + UiEventData event; + UiVar *var; +} UiListViewEventData; + +void* ui_strmodel_getvalue(void *elm, int column); + +XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count); +void ui_listview_update(UiEvent *event, UiListView *view); +void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); + +#ifdef __cplusplus +} +#endif + +#endif /* LIST_H */ + diff --git a/ui/motif/menu.c b/ui/motif/menu.c new file mode 100644 index 0000000..15a248d --- /dev/null +++ b/ui/motif/menu.c @@ -0,0 +1,626 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#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;ioldcount;i++) { + XtDestroyWidget(children[list->index + i]); + } + } + + char *str = ui_list_first(list->list); + if(str) { + // add separator + XtSetArg(args[0], XmNpositionIndex, list->index); + Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1); + XtManageChild(s); + } + int i = 1; + while(str) { + XmString label = XmStringCreateLocalized(str); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNpositionIndex, list->index + i); + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + list->menu, + args, + 2); + XmStringFree(label); + + if(list->callback) { + // TODO: use mempool + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = list->object; + event->userdata = list->userdata; + event->callback = list->callback; + event->value = i - 1; + + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + event); + } + + str = ui_list_next(list->list); + i++; + } + + list->oldcount = i; +} + +void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) { + UiEventData *event = udata; + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.intval = 0; + event->callback(&e, event->userdata); +} + + +/* + * widget menu functions + */ + +static void ui_popup_handler(Widget widget, XtPointer data, XEvent *event, Boolean *c) { + Widget menu = data; + XmMenuPosition(menu, (XButtonPressedEvent *)event); + XtManageChild(menu); + + *c = FALSE; +} + +UIMENU ui_contextmenu(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + if(ct->current) { + return ui_contextmenu_w(obj, ct->current); + } else { + return NULL; // TODO: warn + } +} + +UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) { + UiContainer *ct = uic_get_current_container(obj); + + Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0); + ct->menu = menu; + + XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu); + + return menu; +} + +void ui_contextmenu_popup(UIMENU menu) { + +} + +void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) { + ui_widget_menuitem_gr(obj, label, f, userdata, -1); +} + +void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) { + UiContainer *ct = uic_get_current_container(obj); + if(!ct->menu) { + return; + } + + // add groups + UcxList *groups = NULL; + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + ucx_list_append(groups, (void*)(intptr_t)group); + } + va_end(ap); + + // create menuitem + Arg args[4]; + XmString labelstr = XmStringCreateLocalized(label); + XtSetArg(args[0], XmNlabelString, labelstr); + + Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1); + XtManageChild(item); + XmStringFree(labelstr); +} + +void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) { + ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1); +} + +void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) { + UiContainer *ct = uic_get_current_container(obj); + if(!ct->menu) { + return; + } + + // add groups + UcxList *groups = NULL; + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + ucx_list_append(groups, (void*)(intptr_t)group); + } + va_end(ap); + + // create menuitem + UiStockItem *stockItem = ui_get_stock_item(stockid); + Arg args[4]; + XmString labelstr = XmStringCreateLocalized(stockItem->label); + XtSetArg(args[0], XmNlabelString, labelstr); + + Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1); + XtManageChild(item); + XmStringFree(labelstr); +} diff --git a/ui/motif/menu.h b/ui/motif/menu.h new file mode 100644 index 0000000..d8919cb --- /dev/null +++ b/ui/motif/menu.h @@ -0,0 +1,127 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MENU_H +#define MENU_H + +#include "../ui/menu.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiMenuItemI UiMenuItemI; +typedef struct UiMenu UiMenu; +typedef struct UiMenuItem UiMenuItem; +typedef struct UiStMenuItem UiStMenuItem; +typedef struct UiCheckItem UiCheckItem; +typedef struct UiCheckItemNV UiCheckItemNV; +typedef struct UiMenuItemList UiMenuItemList; + +typedef struct UiActiveMenuItemList UiActiveMenuItemList; + +typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*); + +struct UiMenuItemI { + ui_menu_add_f add_to; +}; + +struct UiMenu { + UiMenuItemI item; + char *label; + UcxList *items; + UiMenu *parent; +}; + +struct UiMenuItem { + UiMenuItemI item; + ui_callback callback; + char *label; + void *userdata; + UcxList *groups; +}; + +struct UiStMenuItem { + UiMenuItemI item; + ui_callback callback; + char *stockid; + void *userdata; + UcxList *groups; +}; + +struct UiCheckItem { + UiMenuItemI item; + char *label; + ui_callback callback; + void *userdata; +}; + +struct UiCheckItemNV { + UiMenuItemI item; + char *label; + char *varname; +}; + +struct UiMenuItemList { + UiMenuItemI item; + ui_callback callback; + void *userdata; + UiList *list; +}; + +struct UiActiveMenuItemList { + UiObject *object; + Widget menu; + int index; + int oldcount; + UiList *list; + ui_callback callback; + void *userdata; +}; + +void ui_create_menubar(UiObject *obj); + +int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_menuseparator_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_checkitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_checkitemnv_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +int add_menuitem_list_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); + +void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list); +void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata); + + +#ifdef __cplusplus +} +#endif + +#endif /* MENU_H */ + diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk new file mode 100644 index 0000000..179deac --- /dev/null +++ b/ui/motif/objs.mk @@ -0,0 +1,49 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +MOTIF_SRC_DIR = ui/motif/ +MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR) + +MOTIFOBJ = toolkit.o +MOTIFOBJ += stock.o +MOTIFOBJ += window.o +MOTIFOBJ += container.o +MOTIFOBJ += menu.o +MOTIFOBJ += toolbar.o +MOTIFOBJ += button.o +MOTIFOBJ += label.o +MOTIFOBJ += text.o +MOTIFOBJ += list.o +MOTIFOBJ += tree.o +MOTIFOBJ += graphics.o +MOTIFOBJ += range.o +MOTIFOBJ += dnd.o +MOTIFOBJ += image.o + +TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%) +TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c) diff --git a/ui/motif/range.c b/ui/motif/range.c new file mode 100644 index 0000000..07d7ada --- /dev/null +++ b/ui/motif/range.c @@ -0,0 +1,138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "range.h" +#include "container.h" +#include +#include "../common/context.h" +#include "../common/object.h" + + +static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) { + UiContainer *ct = uic_get_current_container(obj); + + int n = 0; + Arg args[16]; + XtSetArg(args[n], XmNorientation, orientation == UI_HORIZONTAL ? XmHORIZONTAL : XmVERTICAL); + n++; + XtSetArg (args[n], XmNmaximum, 10); + n++; + XtSetArg (args[n], XmNsliderSize, 1); + n++; + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget scrollbar = XmCreateScrollBar(parent, "scrollbar", args, n); + XtManageChild(scrollbar); + ct->add(ct, scrollbar); + + if(range) { + range->get = ui_scrollbar_get; + range->set = ui_scrollbar_set; + range->setrange = ui_scrollbar_setrange; + range->setextent = ui_scrollbar_setextent; + range->obj = scrollbar; + } + + if(f) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = userdata; + event->callback = f; + event->value = 0; + XtAddCallback( + scrollbar, + XmNvalueChangedCallback, + (XtCallbackProc)ui_scrollbar_callback, + event); + } + + return scrollbar; +} + +UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) { + return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata); +} + +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) { + return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata); +} + +void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.intval = event->value; + event->callback(&e, event->userdata); +} + +double ui_scrollbar_get(UiRange *range) { + int intval; + XtVaGetValues( + range->obj, + XmNvalue, + &intval, + NULL); + double value = (double)intval / 10; + range->value = value; + return value; +} + +void ui_scrollbar_set(UiRange *range, double value) { + XtVaSetValues( + range->obj, + XmNvalue, + (int)(value * 10), + NULL); + range->value = value; +} + +void ui_scrollbar_setrange(UiRange *range, double min, double max) { + XtVaSetValues( + range->obj, + XmNminimum, + (int)(min * 10), + XmNmaximum, + (int)(max * 10), + NULL); + range->min = min; + range->max = max; +} + +void ui_scrollbar_setextent(UiRange *range, double extent) { + XtVaSetValues( + range->obj, + XmNsliderSize, + (int)(extent * 10), + NULL); + range->extent = extent; +} diff --git a/ui/motif/range.h b/ui/motif/range.h new file mode 100644 index 0000000..1c6da04 --- /dev/null +++ b/ui/motif/range.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RANGE_H +#define RANGE_H + +#include "toolkit.h" +#include "../ui/range.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata); +double ui_scrollbar_get(UiRange *range); +void ui_scrollbar_set(UiRange *range, double value); +void ui_scrollbar_setrange(UiRange *range, double min, double max); +void ui_scrollbar_setextent(UiRange *range, double extent); + + +#ifdef __cplusplus +} +#endif + +#endif /* RANGE_H */ + diff --git a/ui/motif/stock.c b/ui/motif/stock.c new file mode 100644 index 0000000..1747cf6 --- /dev/null +++ b/ui/motif/stock.c @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "stock.h" +#include "../ui/properties.h" +#include + +static UcxMap *stock_items; + +void ui_stock_init() { + stock_items = ucx_map_new(64); + + ui_add_stock_item(UI_STOCK_NEW, "New", "CtrlN", "Ctrl+N", NULL); + ui_add_stock_item(UI_STOCK_OPEN, "Open", "CtrlO", "Ctrl+O", NULL); + ui_add_stock_item(UI_STOCK_SAVE, "Save", "CtrlS", "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", "CtrlW", "Ctrl+W", NULL); + ui_add_stock_item(UI_STOCK_UNDO, "Undo", "CtrlZ", "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", "CtrlX", "Ctrl+X", NULL); + ui_add_stock_item(UI_STOCK_COPY, "Copy", "CtrlC", "Ctrl+C", NULL); + ui_add_stock_item(UI_STOCK_PASTE, "Paste", "CtrlV", "Ctrl+V", NULL); + ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL); +} + +void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) { + UiStockItem *i = malloc(sizeof(UiStockItem)); + i->label = label; + i->accelerator = accelerator; + i->accelerator_label = accelerator_label; + // TODO: icon + + ucx_map_cstr_put(stock_items, id, i); +} + +UiStockItem* ui_get_stock_item(char *id) { + UiStockItem *item = ucx_map_cstr_get(stock_items, id); + if(item) { + char *label = uistr_n(id); + if(label) { + item->label = label; + } + } + return item; +} diff --git a/ui/motif/stock.h b/ui/motif/stock.h new file mode 100644 index 0000000..03e643f --- /dev/null +++ b/ui/motif/stock.h @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STOCK_H +#define STOCK_H + +#include "../ui/stock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiStockItem { + char *label; + char *accelerator; + char *accelerator_label; + // TODO: icon +} UiStockItem; + +void ui_stock_init(); + +void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon); + +UiStockItem* ui_get_stock_item(char *id); + +#ifdef __cplusplus +} +#endif + +#endif /* STOCK_H */ + diff --git a/ui/motif/text.c b/ui/motif/text.c new file mode 100644 index 0000000..a4835c3 --- /dev/null +++ b/ui/motif/text.c @@ -0,0 +1,488 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#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 32) { + return 1; + } + } + + return 0; +} + +void ui_free_textbuf_op(UiTextBufOp *op) { + if(op->text) { + free(op->text); + } + free(op); +} + + +void ui_text_undo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + if(mgr->cur) { + UiTextBufOp *op = mgr->cur->data; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + XmTextReplace(value->obj, op->start, op->end, ""); + break; + } + case UI_TEXTBUF_DELETE: { + XmTextInsert(value->obj, op->start, op->text); + break; + } + } + mgr->event = 1; + mgr->cur = mgr->cur->prev; + } +} + +void ui_text_redo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + UcxList *elm = NULL; + if(mgr->cur) { + if(mgr->cur->next) { + elm = mgr->cur->next; + } + } else if(mgr->begin) { + elm = mgr->begin; + } + + if(elm) { + UiTextBufOp *op = elm->data; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + XmTextInsert(value->obj, op->start, op->text); + break; + } + case UI_TEXTBUF_DELETE: { + XmTextReplace(value->obj, op->start, op->end, ""); + break; + } + } + mgr->event = 1; + mgr->cur = elm; + } +} + + +/* ------------------------- textfield ------------------------- */ + +static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) { + UiContainer *ct = uic_get_current_container(obj); + int n = 0; + Arg args[16]; + XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT); + n++; + if(width > 0) { + XtSetArg(args[n], XmNcolumns, width / 2 + 1); + n++; + } + if(frameless) { + XtSetArg(args[n], XmNshadowThickness, 0); + n++; + } + if(password) { + // TODO + } + + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget textfield = XmCreateText(parent, "text_field", args, n); + ct->add(ct, textfield); + XtManageChild(textfield); + + // bind value + if(value) { + if(value->value.ptr) { + XmTextSetString(textfield, value->value.ptr); + value->value.free(value->value.ptr); + } + + value->set = ui_textfield_set; + value->get = ui_textfield_get; + value->value.ptr = NULL; + value->obj = textfield; + } + + return textfield; +} + +static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING); + if(var) { + UiString *value = var->value; + return ui_textfield(obj, value); + } else { + // TODO: error + } + return NULL; +} + +UIWIDGET ui_textfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, FALSE, FALSE, value); +} + +UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, FALSE, FALSE, varname); +} + +UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) { + return create_textfield(obj, width, FALSE, FALSE, value); +} + +UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) { + return create_textfield_nv(obj, width, FALSE, FALSE, varname); +} + +UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, TRUE, FALSE, value); +} + +UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, TRUE, FALSE, varname); +} + +UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) { + return create_textfield(obj, 0, FALSE, TRUE, value); +} + +UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) { + return create_textfield_nv(obj, 0, FALSE, TRUE, varname); +} + +UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) { + return create_textfield(obj, width, FALSE, TRUE, value); +} + +UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) { + return create_textfield_nv(obj, width, FALSE, TRUE, varname); +} + + +char* ui_textfield_get(UiString *str) { + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + char *value = XmTextGetString(str->obj); + str->value.ptr = value; + str->value.free = (ui_freefunc)XtFree; + return value; +} + +void ui_textfield_set(UiString *str, char *value) { + XmTextSetString(str->obj, value); + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + str->value.ptr = NULL; +} + diff --git a/ui/motif/text.h b/ui/motif/text.h new file mode 100644 index 0000000..0f44f95 --- /dev/null +++ b/ui/motif/text.h @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "../ui/text.h" +#include "toolkit.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_TEXTBUF_INSERT 0 +#define UI_TEXTBUF_DELETE 1 +typedef struct UiTextBufOp { + int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE + int start; + int end; + int len; + char *text; +} UiTextBufOp; + +typedef struct UiUndoMgr { + UcxList *begin; + UcxList *cur; + int length; + int event; +} UiUndoMgr; + +typedef struct UiTextArea { + UiContext *ctx; + int last_selection_state; +} UiTextArea; + +char* ui_textarea_get(UiText *text); +void ui_textarea_set(UiText *text, char *str); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); + +UiUndoMgr* ui_create_undomgr(); +void ui_text_selection_callback( + Widget widget, + UiTextArea *textarea, + XtPointer data); +void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data); +int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); +void ui_free_textbuf_op(UiTextBufOp *op); + +char* ui_textfield_get(UiString *str); +void ui_textfield_set(UiString *str, char *value); + +#ifdef __cplusplus +} +#endif + +#endif /* TEXT_H */ + diff --git a/ui/motif/toolbar.c b/ui/motif/toolbar.c new file mode 100644 index 0000000..3456dd3 --- /dev/null +++ b/ui/motif/toolbar.c @@ -0,0 +1,366 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolbar.h" +#include "button.h" +#include "stock.h" +#include "list.h" +#include +#include "../common/context.h" + +static UcxMap *toolbar_items; +static UcxList *defaults; + +void ui_toolbar_init() { + toolbar_items = ucx_map_new(16); +} + +void ui_toolitem(char *name, char *label, ui_callback f, void *userdata) { + UiToolItem *item = malloc(sizeof(UiToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget; + item->label = label; + item->image = NULL; + item->callback = f; + item->userdata = userdata; + item->groups = NULL; + item->isimportant = FALSE; + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) { + ui_toolitem_stgr(name, stockid, f, userdata, -1); +} + +void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) { + UiStToolItem *item = malloc(sizeof(UiStToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget; + item->stockid = stockid; + item->callback = f; + item->userdata = userdata; + item->groups = NULL; + item->isimportant = FALSE; + + // add groups + va_list ap; + va_start(ap, userdata); + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + va_end(ap); + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) { + // TODO + + UiToolItem *item = malloc(sizeof(UiToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget; + item->label = label; + item->image = img; + item->callback = f; + item->userdata = udata; + item->groups = NULL; + item->isimportant = FALSE; + + ucx_map_cstr_put(toolbar_items, name, item); +} + + +void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) { + // TODO + + UiStToolItem *item = malloc(sizeof(UiStToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_toggle_widget; + item->stockid = stockid; + item->callback = f; + item->userdata = udata; + item->groups = NULL; + item->isimportant = FALSE; + + // add groups + va_list ap; + va_start(ap, udata); + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + va_end(ap); + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolitem_toggle_imggr(char *name, char *label, char *img, ui_callback f, void *udata, ...) { + // TODO + + UiToolItem *item = malloc(sizeof(UiToolItem)); + item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget; + item->label = label; + item->image = img; + item->callback = f; + item->userdata = udata; + item->groups = NULL; + item->isimportant = FALSE; + + // add groups + va_list ap; + va_start(ap, udata); + int group; + while((group = va_arg(ap, int)) != -1) { + item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group); + } + va_end(ap); + + ucx_map_cstr_put(toolbar_items, name, item); +} + +void ui_toolbar_combobox( + char *name, + UiList *list, + ui_getvaluefunc getvalue, + ui_callback f, + void *udata) +{ + UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox)); + cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox; + cb->list = list; + cb->getvalue = getvalue; + cb->callback = f; + cb->userdata = udata; + + ucx_map_cstr_put(toolbar_items, name, cb); +} + +void ui_toolbar_combobox_str( + char *name, + UiList *list, + ui_callback f, + void *udata) +{ + ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata); +} + +void ui_toolbar_combobox_nv( + char *name, + char *listname, + ui_getvaluefunc getvalue, + ui_callback f, + void *udata) +{ + UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV)); + cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv; + cb->listname = listname; + cb->getvalue = getvalue; + cb->callback = f; + cb->userdata = udata; + + ucx_map_cstr_put(toolbar_items, name, cb); +} + + +void ui_toolbar_add_default(char *name) { + char *s = strdup(name); + defaults = ucx_list_append(defaults, s); +} + +Widget ui_create_toolbar(UiObject *obj, Widget parent) { + if(!defaults) { + return NULL; + } + + Arg args[8]; + XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT); + XtSetArg(args[1], XmNshadowThickness, 1); + XtSetArg(args[2], XmNtopAttachment, XmATTACH_FORM); + XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM); + XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM); + Widget frame = XmCreateFrame(parent, "toolbar_frame", args, 5); + + XtSetArg(args[0], XmNorientation, XmHORIZONTAL); + XtSetArg(args[1], XmNpacking, XmPACK_TIGHT); + XtSetArg(args[2], XmNspacing, 1); + Widget toolbar = XmCreateRowColumn (frame, "toolbar", args, 3); + + UCX_FOREACH(elm, defaults) { + UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data); + if(item) { + item->add_to(toolbar, item, obj); + } else if(!strcmp(elm->data, "@separator")) { + // TODO + } else { + fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data); + } + } + + XtManageChild(toolbar); + XtManageChild(frame); + + return frame; +} + +void add_toolitem_widget(Widget parent, UiToolItem *item, UiObject *obj) { + Arg args[4]; + + XmString label = XmStringCreateLocalized(item->label); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNshadowThickness, 1); + XtSetArg(args[2], XmNtraversalOn, FALSE); + Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3); + + XmStringFree(label); + + if(item->callback) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = item->userdata; + event->callback = item->callback; + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + event); + } + + XtManageChild(button); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups); + } +} + +void add_toolitem_st_widget(Widget parent, UiStToolItem *item, UiObject *obj) { + Arg args[8]; + + UiStockItem *stock_item = ui_get_stock_item(item->stockid); + + XmString label = XmStringCreateLocalized(stock_item->label); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNshadowThickness, 1); + XtSetArg(args[2], XmNtraversalOn, FALSE); + Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3); + + XmStringFree(label); + + if(item->callback) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = item->userdata; + event->callback = item->callback; + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + event); + } + + XtManageChild(button); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups); + } +} + +void add_toolitem_toggle_widget(Widget parent, UiToolItem *item, UiObject *obj) { + Arg args[8]; + + XmString label = XmStringCreateLocalized(item->label); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNshadowThickness, 1); + XtSetArg(args[2], XmNtraversalOn, FALSE); + XtSetArg(args[3], XmNindicatorOn, XmINDICATOR_NONE); + Widget button = XmCreateToggleButton(parent, "toolbar_toggle_button", args, 4); + + XmStringFree(label); + + if(item->callback) { + UiEventData *event = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiEventData)); + event->obj = obj; + event->userdata = item->userdata; + event->callback = item->callback; + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)ui_toggle_button_callback, + event); + } + + XtManageChild(button); + + if(item->groups) { + uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups); + } +} + +void add_toolitem_st_toggle_widget(Widget parent, UiStToolItem *item, UiObject *obj) { + +} + +void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj) { + UiListView *listview = ucx_mempool_malloc( + obj->ctx->mempool, + sizeof(UiListView)); + + UiVar *var = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiVar)); + var->value = item->list; + var->type = UI_VAR_SPECIAL; + + Arg args[8]; + XtSetArg(args[0], XmNshadowThickness, 1); + XtSetArg(args[1], XmNindicatorOn, XmINDICATOR_NONE); + XtSetArg(args[2], XmNtraversalOn, FALSE); + XtSetArg(args[3], XmNwidth, 120); + Widget combobox = XmCreateDropDownList(tb, "toolbar_combobox", args, 4); + XtManageChild(combobox); + listview->widget = combobox; + listview->list = var; + listview->getvalue = item->getvalue; + + ui_listview_update(NULL, listview); + + if(item->callback) { + // TODO: + + } +} + +void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj) { + +} diff --git a/ui/motif/toolbar.h b/ui/motif/toolbar.h new file mode 100644 index 0000000..22d0a5d --- /dev/null +++ b/ui/motif/toolbar.h @@ -0,0 +1,105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLBAR_H +#define TOOLBAR_H + +#include "../ui/toolbar.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolItemI UiToolItemI; +typedef struct UiToolItem UiToolItem; +typedef struct UiStToolItem UiStToolItem; + +typedef struct UiToolbarComboBox UiToolbarComboBox; +typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV; + +typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*); + +struct UiToolItemI { + ui_toolbar_add_f add_to; +}; + +struct UiToolItem { + UiToolItemI item; + char *label; + void *image; + ui_callback callback; + void *userdata; + UcxList *groups; + Boolean isimportant; +}; + +struct UiStToolItem { + UiToolItemI item; + char *stockid; + ui_callback callback; + void *userdata; + UcxList *groups; + Boolean isimportant; +}; + +struct UiToolbarComboBox { + UiToolItemI item; + UiList *list; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +struct UiToolbarComboBoxNV { + UiToolItemI item; + char *listname; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +void ui_toolbar_init(); + +Widget ui_create_toolbar(UiObject *obj, Widget parent); + +void add_toolitem_widget(Widget tb, UiToolItem *item, UiObject *obj); +void add_toolitem_st_widget(Widget tb, UiStToolItem *item, UiObject *obj); +void add_toolitem_toggle_widget(Widget tb, UiToolItem *item, UiObject *obj); +void add_toolitem_st_toggle_widget(Widget tb, UiStToolItem *item, UiObject *obj); + +void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj); +void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLBAR_H */ + diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c new file mode 100644 index 0000000..9757f64 --- /dev/null +++ b/ui/motif/toolkit.c @@ -0,0 +1,301 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "toolbar.h" +#include "stock.h" +#include "../common/document.h" +#include "../common/properties.h" +#include + +static XtAppContext app; +static Display *display; +static Widget active_window; +static char *application_name; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +void *open_data; +static ui_callback exit_func; +void *exit_data; + +static ui_callback appclose_fnc; +static void *appclose_udata; + +static int is_toplevel_realized = 0; + +int event_pipe[2]; + + +static String fallback[] = { + //"*fontList: -dt-interface system-medium-r-normal-s*utf*:", + "*text_area*renderTable: f1", + "*f1*fontType: FONT_IS_XFT", + "*f1*fontName: Monospace", + "*f1*fontSize: 11", + "*renderTable: rt", + "*rt*fontType: FONT_IS_XFT", + "*rt*fontName: Sans", + "*rt*fontSize: 11", + NULL +}; + +void input_proc(XtPointer data, int *source, XtInputId *iid) { + void *ptr; + read(event_pipe[0], &ptr, sizeof(void*)); +} + +void ui_init(char *appname, int argc, char **argv) { + application_name = appname; + + XtToolkitInitialize(); + XtSetLanguageProc(NULL, NULL, NULL); + app = XtCreateApplicationContext(); + XtAppSetFallbackResources(app, fallback); + + display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv); + char **missing = NULL; + int nm = 0; + char *def = NULL; + XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def); + + uic_docmgr_init(); + ui_toolbar_init(); + ui_stock_init(); + + uic_load_app_properties(); + + if(pipe(event_pipe)) { + fprintf(stderr, "UiError: Cannot create event pipe\n"); + exit(-1); + } + XtAppAddInput( + app, + event_pipe[0], + (XtPointer)XtInputReadMask, + input_proc, + NULL); +} + +char* ui_appname() { + return application_name; +} + +Display* ui_get_display() { + return display; +} + +void ui_onstartup(ui_callback f, void *userdata) { + startup_func = f; + startup_data = userdata; +} + +void ui_onopen(ui_callback f, void *userdata) { + open_func = f; + open_data = userdata; +} + +void ui_onexit(ui_callback f, void *userdata) { + exit_func = f; + exit_data = userdata; +} + +void ui_main() { + if(startup_func) { + startup_func(NULL, startup_data); + } + XtAppMainLoop(app); + if(exit_func) { + exit_func(NULL, exit_data); + } + uic_store_app_properties(); +} + +void ui_exit_mainloop() { + XtAppSetExitFlag(app); +} + +void ui_secondary_event_loop(int *loop) { + while(*loop && !XtAppGetExitFlag(app)) { + XEvent event; + XtAppNextEvent(app, &event); + XtDispatchEvent(&event); + } +} + +void ui_show(UiObject *obj) { + uic_check_group_widgets(obj->ctx); + XtRealizeWidget(obj->widget); + ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if +} + +// implemented in window.c +//void ui_close(UiObject *obj) + +void ui_set_enabled(UIWIDGET widget, int enabled) { + XtSetSensitive(widget, enabled); +} + +void ui_set_show_all(UIWIDGET widget, int value) { + if(!value) { + XtUnmanageChild(widget); + } +} + +void ui_set_visible(UIWIDGET widget, int visible) { + if(visible) { + XtManageChild(widget); + } else { + XtUnmanageChild(widget); + } +} + +static Boolean ui_job_finished(void *data) { + printf("WorkProc\n"); + UiJob *job = data; + + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + + job->finish_callback(&event, job->finish_data); + free(job); + return TRUE; +} + +static void* ui_jobthread(void *data) { + UiJob *job = data; + int result = job->job_func(job->job_data); + if(!result) { + printf("XtAppAddWorkProc\n"); + write(event_pipe[1], &job, sizeof(void*)); // hack + XtAppAddWorkProc(app, ui_job_finished, job); + + } +} + +void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { + UiJob *job = malloc(sizeof(UiJob)); + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + pthread_t pid; + pthread_create(&pid, NULL, ui_jobthread, job); +} + +void ui_clipboard_set(char *str) { + printf("copy: {%s}\n", str); + int length = strlen(str) + 1; + + Display *dp = XtDisplayOfObject(active_window); + Window window = XtWindowOfObject(active_window); + + XmString label = XmStringCreateLocalized("toolkit_clipboard"); + long id = 0; + + while(XmClipboardStartCopy( + dp, + window, + label, + CurrentTime, + NULL, + NULL, + &id) == ClipboardLocked); + XmStringFree(label); + + while(XmClipboardCopy( + dp, + window, + id, + "STRING", + str, + length, + 1, + NULL) == ClipboardLocked); + + while(XmClipboardEndCopy(dp, window, id) == ClipboardLocked); +} + +char* ui_clipboard_get() { + Display *dp = XtDisplayOfObject(active_window); + Window window = XtWindowOfObject(active_window); + + long id; + size_t size = 128; + char *buf = malloc(size); + + int r; + for(;;) { + r = XmClipboardRetrieve(dp, window, "STRING", buf, size, NULL, &id); + if(r == ClipboardSuccess) { + break; + } else if(r == ClipboardTruncate) { + size *= 2; + buf = realloc(buf, size); + } else if(r == ClipboardNoData) { + free(buf); + buf = NULL; + break; + } + } + + return buf; +} + +void ui_set_active_window(Widget w) { + active_window = w; +} + +Widget ui_get_active_window() { + return active_window; +} + +void ui_window_dark_theme(Display *dp, Window window) { + Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False); + Atom type = XInternAtom(dp, "UTF8_STRING", False); + XChangeProperty( + dp, + window, + atom, + type, + 8, + PropModeReplace, + (const unsigned char*)"dark", + 4); +} diff --git a/ui/motif/toolkit.h b/ui/motif/toolkit.h new file mode 100644 index 0000000..b14cbd9 --- /dev/null +++ b/ui/motif/toolkit.h @@ -0,0 +1,74 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLKIT_H +#define TOOLKIT_H + +#include +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +Display* ui_get_display(); + +typedef struct UiEventData { + UiObject *obj; + ui_callback callback; + void *userdata; + int value; +} UiEventData; + +typedef struct UiJob { + UiObject *obj; + ui_threadfunc job_func; + void *job_data; + ui_callback finish_callback; + void *finish_data; +} UiJob; + +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; + +void ui_exit_mainloop(); + +void ui_set_active_window(Widget w); +Widget ui_get_active_window(); + +void ui_secondary_event_loop(int *loop); +void ui_window_dark_theme(Display *dp, Window window); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLKIT_H */ + diff --git a/ui/motif/tree.c b/ui/motif/tree.c new file mode 100644 index 0000000..0620e0f --- /dev/null +++ b/ui/motif/tree.c @@ -0,0 +1,325 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "tree.h" + +#include "container.h" +#include "../common/object.h" +#include "../common/context.h" +#include + +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;icolumns;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;icolumns;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;ivar->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;icolumns;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;icolumns-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;icount;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;icount;i++) { + if(s1->rows[i] != s2->rows[i]) { + return FALSE; + } + } + return TRUE; +} diff --git a/ui/motif/tree.h b/ui/motif/tree.h new file mode 100644 index 0000000..68e5b06 --- /dev/null +++ b/ui/motif/tree.h @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *userdata; + UiListSelection *last_selection; +} UiTreeEventData; + +typedef struct UiTableView { + Widget widget; + UiVar *var; + UiModel *model; +} UiTableView; + +void ui_table_update(UiEvent *event, UiTableView *view); +int ui_add_icon_gadget(Widget container, UiModel *model, void *data); +char* ui_type_to_string(UiModelType type, void *data, Boolean *free); + +void ui_table_action_callback( + Widget widget, + UiTreeEventData *event, + XmContainerSelectCallbackStruct *sel); +void ui_table_select_callback( + Widget widget, + UiTreeEventData *event, + XmContainerSelectCallbackStruct *sel); + +UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs); + +Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2); + + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff --git a/ui/motif/window.c b/ui/motif/window.c new file mode 100644 index 0000000..041ab7f --- /dev/null +++ b/ui/motif/window.c @@ -0,0 +1,208 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "toolkit.h" +#include "menu.h" +#include "toolbar.h" +#include "container.h" +#include "../ui/window.h" +#include "../common/context.h" + +static int nwindows = 0; + +static int window_default_width = 600; +static int window_default_height = 500; + +static void window_close_handler(Widget window, void *udata, void *cdata) { + UiObject *obj = udata; + UiEvent ev; + ev.window = obj->window; + ev.document = obj->ctx->document; + ev.obj = obj; + ev.eventdata = NULL; + ev.intval = 0; + + if(obj->ctx->close_callback) { + obj->ctx->close_callback(&ev, obj->ctx->close_data); + } + // TODO: free UiObject + + nwindows--; + if(nwindows == 0) { + ui_exit_mainloop(); + } +} + +static UiObject* create_window(char *title, void *window_data, UiBool simple) { + UcxMempool *mp = ucx_mempool_new(256); + UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->window = window_data; + + Arg args[16]; + int n = 0; + + XtSetArg(args[0], XmNtitle, title); + //XtSetArg(args[1], XmNbaseWidth, window_default_width); + //XtSetArg(args[2], XmNbaseHeight, window_default_height); + XtSetArg(args[1], XmNminWidth, 100); + XtSetArg(args[2], XmNminHeight, 50); + XtSetArg(args[3], XmNwidth, window_default_width); + XtSetArg(args[4], XmNheight, window_default_height); + + Widget toplevel = XtAppCreateShell( + "Test123", + "abc", + //applicationShellWidgetClass, + vendorShellWidgetClass, + ui_get_display(), + args, + 5); + + Atom wm_delete_window; + wm_delete_window = XmInternAtom( + XtDisplay(toplevel), + "WM_DELETE_WINDOW", + 0); + XmAddWMProtocolCallback( + toplevel, + wm_delete_window, + window_close_handler, + obj); + + // TODO: use callback + ui_set_active_window(toplevel); + + Widget window = XtVaCreateManagedWidget( + title, + xmMainWindowWidgetClass, + toplevel, + NULL); + obj->widget = window; + Widget form = XtVaCreateManagedWidget( + "window_form", + xmFormWidgetClass, + window, + NULL); + Widget toolbar = NULL; + + if(!simple) { + ui_create_menubar(obj); + toolbar = ui_create_toolbar(obj, form); + } + + // window content + XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT); + XtSetArg(args[1], XmNshadowThickness, 0); + XtSetArg(args[2], XmNleftAttachment, XmATTACH_FORM); + XtSetArg(args[3], XmNrightAttachment, XmATTACH_FORM); + XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM); + if(toolbar) { + XtSetArg(args[5], XmNtopAttachment, XmATTACH_WIDGET); + XtSetArg(args[6], XmNtopWidget, toolbar); + n = 7; + } else { + XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM); + n = 6; + } + Widget frame = XmCreateFrame(form, "content_frame", args, n); + XtManageChild(frame); + + Widget content_form = XmCreateForm(frame, "content_form", NULL, 0); + XtManageChild(content_form); + obj->container = ui_box_container(obj, content_form, 0, 0, UI_BOX_VERTICAL); + + XtManageChild(form); + + obj->widget = toplevel; + nwindows++; + return obj; +} + +UiObject* ui_window(char *title, void *window_data) { + return create_window(title, window_data, FALSE); +} + +UiObject* ui_simplewindow(char *title, void *window_data) { + return create_window(title, window_data, TRUE); +} + +void ui_close(UiObject *obj) { + XtDestroyWidget(obj->widget); + window_close_handler(obj->widget, obj, NULL); +} + +typedef struct FileDialogData { + int running; + char *file; +} FileDialogData; + +static void filedialog_select( + Widget widget, + FileDialogData *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + char *path = NULL; + XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &path); + data->running = 0; + data->file = strdup(path); + XtFree(path); + XtUnmanageChild(widget); +} + +static void filedialog_cancel( + Widget widget, + FileDialogData *data, + XmFileSelectionBoxCallbackStruct *selection) + +{ + data->running = 0; + XtUnmanageChild(widget); +} + +char* ui_openfiledialog(UiObject *obj) { + Widget dialog = XmCreateFileSelectionDialog(obj->widget, "openfiledialog", NULL, 0); + XtManageChild(dialog); + + FileDialogData data; + data.running = 1; + data.file = NULL; + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, &data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, &data); + + ui_secondary_event_loop(&data.running); + return data.file; +} + +char* ui_savefiledialog(UiObject *obj) { + return ui_openfiledialog(obj); +} diff --git a/ui/ui/button.h b/ui/ui/button.h new file mode 100644 index 0000000..cbf8fe8 --- /dev/null +++ b/ui/ui/button.h @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_BUTTON_H +#define UI_BUTTON_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data); + +UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value); +UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname); + +UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup); +UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_BUTTON_H */ + diff --git a/ui/ui/container.h b/ui/ui/container.h new file mode 100644 index 0000000..423342a --- /dev/null +++ b/ui/ui/container.h @@ -0,0 +1,92 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_CONTAINER_H +#define UI_CONTAINER_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_VBOX(obj) for(ui_vbox(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_HBOX(obj) for(ui_hbox(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_VBOX_SP(obj, margin, spacing) for(ui_vbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_HBOX_SP(obj, margin, spacing) for(ui_hbox_sp(obj,margin,spacing);ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_GRID(obj) for(ui_grid(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define UI_GRID_SP(obj, margin, columnspacing, rowspacing) for(ui_grid_sp(obj,margin,columnspacing,rowspacing);ui_container_finish(obj);ui_container_begin_close(obj)) + +void ui_end(UiObject *obj); + +UIWIDGET ui_vbox(UiObject *obj); +UIWIDGET ui_hbox(UiObject *obj); +UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing); +UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing); + +UIWIDGET ui_grid(UiObject *obj); +UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing); + +UIWIDGET ui_scrolledwindow(UiObject *obj); + +UIWIDGET ui_sidebar(UiObject *obj); + +UIWIDGET ui_hsplitpane(UiObject *obj, int max); +UIWIDGET ui_vsplitpane(UiObject *obj, int max); + +UIWIDGET ui_tabview(UiObject *obj); +void ui_tab(UiObject *obj, char *title); +void ui_select_tab(UIWIDGET tabview, int tab); + +// box container layout functions +void ui_layout_fill(UiObject *obj, UiBool fill); +// grid container layout functions +void ui_layout_hexpand(UiObject *obj, UiBool expand); +void ui_layout_vexpand(UiObject *obj, UiBool expand); +void ui_layout_width(UiObject *obj, int width); +void ui_layout_gridwidth(UiObject *obj, int width); +void ui_newline(UiObject *obj); + + +UiTabbedPane* ui_tabbed_document_view(UiObject *obj); + +UiObject* ui_document_tab(UiTabbedPane *view); + + +/* used for macro */ +void ui_container_begin_close(UiObject *obj); +int ui_container_finish(UiObject *obj); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_CONTAINER_H */ + diff --git a/ui/ui/display.h b/ui/ui/display.h new file mode 100644 index 0000000..0673488 --- /dev/null +++ b/ui/ui/display.h @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * display widgets without user input + */ + +#ifndef UI_DISPLAY_H +#define UI_DISPLAY_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* label widgets */ +UIWIDGET ui_label(UiObject *obj, char *label); +UIWIDGET ui_llabel(UiObject *obj, char *label); +UIWIDGET ui_rlabel(UiObject *obj, char *label); +UIWIDGET ui_space(UiObject *obj); +UIWIDGET ui_separator(UiObject *obj); + +/* progress bar */ +UIWIDGET ui_progressbar(UiObject *obj, UiDouble *value); +UIWIDGET ui_progressbar_nv(UiObject *obj, char *varname); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_DISPLAY_H */ + diff --git a/ui/ui/dnd.h b/ui/ui/dnd.h new file mode 100644 index 0000000..71efd36 --- /dev/null +++ b/ui/ui/dnd.h @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_DND_H +#define UI_DND_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_DND_FILE_TARGET "XdndDirectSave0" + +void ui_selection_settext(UiSelection *sel, char *str, int len); +void ui_selection_seturis(UiSelection *sel, char **uris, int nelm); + +char* ui_selection_gettext(UiSelection *sel); +char** ui_selection_geturis(UiSelection *sel, size_t *nelm); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_DND_H */ + diff --git a/ui/ui/entry.h b/ui/ui/entry.h new file mode 100644 index 0000000..5d63351 --- /dev/null +++ b/ui/ui/entry.h @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_ENTRY_H +#define UI_ENTRY_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i); +UIWIDGET ui_spinnerf(UiObject *obj, double step, int digits, UiDouble *d); +UIWIDGET ui_spinnerr(UiObject *obj, UiRange *r); + +UIWIDGET ui_spinner_nv(UiObject *obj, int step, char *varname); +UIWIDGET ui_spinnerf_nv(UiObject *obj, double step, int digits, char *varname); +UIWIDGET ui_spinnerr_nv(UiObject *obj, char *varname); + +void ui_spinner_setrange(UIWIDGET spinner, double min, double max); +void ui_spinner_setdigits(UIWIDGET spinner, int digits); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_ENTRY_H */ + diff --git a/ui/ui/graphics.h b/ui/ui/graphics.h new file mode 100644 index 0000000..6eb0daf --- /dev/null +++ b/ui/ui/graphics.h @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_GRAPHICS_H +#define UI_GRAPHICS_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiGraphics UiGraphics; +typedef struct UiTextLayout UiTextLayout; + +typedef void(*ui_drawfunc)(UiEvent*, UiGraphics*, void*); + +struct UiGraphics { + int width; + int height; +}; + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata); +void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u); +void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height); +void ui_drawingarea_redraw(UIWIDGET drawingarea); + +// text layout +UiTextLayout* ui_text(UiGraphics *g); +void ui_text_free(UiTextLayout *text); +void ui_text_setstring(UiTextLayout *layout, char *str); +void ui_text_setstringl(UiTextLayout *layout, char *str, int len); +void ui_text_setfont(UiTextLayout *layout, char *font, int size); +void ui_text_getsize(UiTextLayout *layout, int *width, int *height); +void ui_text_setwidth(UiTextLayout *layout, int width); + +// drawing functions +void ui_graphics_color(UiGraphics *g, int red, int green, int blue); +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2); +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill); +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_GRAPHICS_H */ + diff --git a/ui/ui/image.h b/ui/ui/image.h new file mode 100644 index 0000000..efd6f70 --- /dev/null +++ b/ui/ui/image.h @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_IMAGE_H +#define UI_IMAGE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UiIcon* ui_icon(const char *name, int size); +UiIcon* ui_icon_unscaled(const char *name, int size); +void ui_free_icon(UiIcon *icon); + +UiImage* ui_icon_image(UiIcon *icon); +UiImage* ui_image(const char *filename); +UiImage* ui_named_image(const char *filename, const char *name); +UiImage* ui_load_image_from_path(const char *path, const char *name); +void ui_free_image(UiImage *img); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_IMAGE_H */ + diff --git a/ui/ui/menu.h b/ui/ui/menu.h new file mode 100644 index 0000000..a472839 --- /dev/null +++ b/ui/ui/menu.h @@ -0,0 +1,74 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_MENU_H +#define UI_MENU_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * application menu functions + */ +void ui_menu(char *label); +void ui_submenu(char *label); +void ui_submenu_end(); + +void ui_menuitem(char *label, ui_callback f, void *userdata); +void ui_menuitem_st(char *stockid, ui_callback f, void *userdata); +void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...); +void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...); + +void ui_menuseparator(); + +void ui_checkitem(char *label, ui_callback f, void *userdata); +void ui_checkitem_nv(char *label, char *vname); + +void ui_menuitem_list(UiList *items, ui_callback f, void *userdata); + +/* + * widget menu functions + */ +UIMENU ui_contextmenu(UiObject *obj); +UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget); +void ui_contextmenu_popup(UIMENU menu); + +void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata); +void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata); +void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...); +void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_MENU_H */ + diff --git a/ui/ui/properties.h b/ui/ui/properties.h new file mode 100644 index 0000000..2675812 --- /dev/null +++ b/ui/ui/properties.h @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_PROPERTIES_H +#define UI_PROPERTIES_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UcxMap UiProperties; + +char* ui_getappdir(); +char* ui_configfile(char *name); + +char* ui_get_property(char *name); +void ui_set_property(char *name, char *value); + +void ui_set_default_property(char *name, char *value); + +void ui_locales_dir(char *path); +void ui_pixmaps_dir(char *path); + +void ui_load_lang(char *locale); +void ui_load_lang_def(char *locale, char *default_locale); + +char* uistr(char *name); +char* uistr_n(char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_PROPERTIES_H */ + diff --git a/ui/ui/range.h b/ui/ui/range.h new file mode 100644 index 0000000..28ebb3f --- /dev/null +++ b/ui/ui/range.h @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_RANGE_H +#define UI_RANGE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); + + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_RANGE_H */ + diff --git a/ui/ui/stock.h b/ui/ui/stock.h new file mode 100644 index 0000000..ab2d13d --- /dev/null +++ b/ui/ui/stock.h @@ -0,0 +1,89 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_STOCK_H +#define UI_STOCK_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// motif stock ids +#if UI_MOTIF || UI_COCOA || UI_QT4 || UI_QT5 + +#define UI_STOCK_NEW "ui.stock.New" +#define UI_STOCK_OPEN "ui.stock.Open" +#define UI_STOCK_SAVE "ui.stock.Save" +#define UI_STOCK_SAVE_AS "ui.stock.SaveAs" +#define UI_STOCK_REVERT_TO_SAVED "ui.stock.RevertToSaved" +#define UI_STOCK_GO_BACK "ui.stock.GoBack" +#define UI_STOCK_GO_FORWARD "ui.stock.GoForward" +#define UI_STOCK_ADD "ui.stock.Add" +#define UI_STOCK_CLOSE "ui.stock.Close" + +#define UI_STOCK_UNDO "ui.stock.Undo" +#define UI_STOCK_REDO "ui.stock.Redo" +#define UI_STOCK_CUT "ui.stock.Cut" +#define UI_STOCK_COPY "ui.stock.Copy" +#define UI_STOCK_PASTE "ui.stock.Paste" +#define UI_STOCK_DELETE "ui.stock.Delete" + +#endif + +#if UI_GTK2 || UI_GTK3 + +#define UI_STOCK_NEW GTK_STOCK_NEW +#define UI_STOCK_OPEN GTK_STOCK_OPEN +#define UI_STOCK_SAVE GTK_STOCK_SAVE +#define UI_STOCK_SAVE_AS GTK_STOCK_SAVE_AS +#define UI_STOCK_REVERT_TO_SAVED GTK_STOCK_REVERT_TO_SAVED +#define UI_STOCK_UNDO GTK_STOCK_UNDO +#define UI_STOCK_REDO GTK_STOCK_REDO +#define UI_STOCK_GO_BACK GTK_STOCK_GO_BACK +#define UI_STOCK_GO_FORWARD GTK_STOCK_GO_FORWARD +#define UI_STOCK_ADD GTK_STOCK_ADD +#define UI_STOCK_CLOSE GTK_STOCK_CLOSE + +#define UI_STOCK_UNDO GTK_STOCK_UNDO +#define UI_STOCK_REDO GTK_STOCK_REDO +#define UI_STOCK_CUT GTK_STOCK_CUT +#define UI_STOCK_COPY GTK_STOCK_COPY +#define UI_STOCK_PASTE GTK_STOCK_PASTE +#define UI_STOCK_DELETE GTK_STOCK_DELETE + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_STOCK_H */ + diff --git a/ui/ui/text.h b/ui/ui/text.h new file mode 100644 index 0000000..7df2ce3 --- /dev/null +++ b/ui/ui/text.h @@ -0,0 +1,66 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TEXT_H +#define UI_TEXT_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_textarea(UiObject *obj, UiText *value); +UIWIDGET ui_textarea_nv(UiObject *obj, char *varname); + +UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea); + +void ui_text_undo(UiText *value); +void ui_text_redo(UiText *value); + +UIWIDGET ui_textfield(UiObject *obj, UiString *value); +UIWIDGET ui_textfield_nv(UiObject *obj, char *varname); + +UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value); +UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname); + +UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value); +UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname); + +UIWIDGET ui_passwordfield(UiObject *obj, UiString *value); +UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname); +UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value); +UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TEXT_H */ + diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h new file mode 100644 index 0000000..f5b364d --- /dev/null +++ b/ui/ui/toolbar.h @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TOOLBAR_H +#define UI_TOOLBAR_H + +#include "toolkit.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_toolitem(char *name, char *label, ui_callback f, void *udata); +void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata); +void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *udata); +void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...); +void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...); +void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata); + +void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i); +void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i); +void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar); +void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar); + +void ui_toolbar_combobox(char *name, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata); +void ui_toolbar_combobox_str(char *name, UiList *list, ui_callback f, void *udata); +void ui_toolbar_combobox_nv(char *name, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata); + +void ui_toolbar_add_default(char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TOOLBAR_H */ + diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h new file mode 100644 index 0000000..d967d45 --- /dev/null +++ b/ui/ui/toolkit.h @@ -0,0 +1,373 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TOOLKIT_H +#define UI_TOOLKIT_H + +#include + +#ifdef UI_COCOA + +#ifdef __OBJC__ +#import +#define UIWIDGET NSView* +#define UIMENU NSMenu* +#else +typedef void* UIWIDGET; +typedef void* UIMENU; +#endif + +#elif UI_GTK2 || UI_GTK3 + +#include +#define UIWIDGET GtkWidget* +#define UIMENU GtkMenu* +#define UI_GTK + +#elif UI_MOTIF + +#include +#define UIWIDGET Widget +#define UIMENU Widget + +#elif defined(UI_QT4) || defined(UI_QT5) +#ifdef __cplusplus +#include +#include +#include +#define UIWIDGET QWidget* +#define UIMENU QMenu* +#else /* __cplusplus */ +#define UIWIDGET void* +#define UIMENU void* +#endif + +#elif UI_WPF +#define UIWIDGET void* +#define UIMENU void* +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_GROUP_SELECTION 20000 + +/* public types */ +typedef int UiBool; + +typedef struct UiObject UiObject; +typedef struct UiEvent UiEvent; +typedef struct UiMouseEvent UiMouseEvent; +typedef struct UiObserver UiObserver; + +typedef struct UiInteger UiInteger; +typedef struct UiDouble UiDouble; +typedef struct UiString UiString; +typedef struct UiText UiText; +typedef struct UiList UiList; +typedef struct UiRange UiRange; + +typedef struct UiStr UiStr; + +/* begin opaque types */ +typedef struct UiContext UiContext; +typedef struct UiContainer UiContainer; + +typedef struct UiIcon UiIcon; +typedef struct UiImage UiImage; + +typedef struct UiSelection UiSelection; +/* end opaque types */ + +typedef struct UiTabbedPane UiTabbedPane; + +enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 }; + + + +typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */ + +typedef void*(*ui_getvaluefunc)(void*, int); + +typedef int(*ui_threadfunc)(void*); + +typedef void(*ui_freefunc)(void*); + +typedef void(*ui_enablefunc)(void*, UiBool); + +struct UiObject { + /* + * native widget + */ + UIWIDGET widget; + + /* + * user window data + */ + void *window; + + /* + * window context + */ + UiContext *ctx; + + /* + * container interface + */ + UiContainer *container; + + /* + * next container object + */ + UiObject *next; +}; + +struct UiTabbedPane { + /* + * native widget + */ + UIWIDGET widget; + + /* + * current document + */ + void *document; + + /* + * parent context + */ + UiContext *ctx; +}; + +struct UiEvent { + UiObject *obj; + void *document; + void *window; + void *eventdata; + int intval; +}; + +struct UiMouseEvent { + int x; + int y; + enum UiMouseEventType type; + int button; +}; + +struct UiObserver { + ui_callback callback; + void *data; + UiObserver *next; +}; + +struct UiStr { + char *ptr; + void (*free)(void *v); +}; + +struct UiInteger { + int64_t (*get)(UiInteger*); + void (*set)(UiInteger*, int64_t); + void *obj; + + int64_t value; + UiObserver *observers; +}; + +struct UiDouble { + double (*get)(UiDouble*); + void (*set)(UiDouble*, double); + void *obj; + + double value; + UiObserver *observers; +}; + +struct UiString { + char* (*get)(UiString*); + void (*set)(UiString*, char*); + void *obj; + + UiStr value; + UiObserver *observers; +}; + +struct UiText { + void (*set)(UiText*, char*); + char* (*get)(UiText*); + char* (*getsubstr)(UiText*, int, int); /* text, begin, end */ + void (*insert)(UiText*, int, char*); + void (*setposition)(UiText*,int); + int (*position)(UiText*); + void (*selection)(UiText*, int*, int*); /* text, begin, end */ + int (*length)(UiText*); + void (*remove)(UiText*, int, int); /* text, begin, end */ + UiStr value; + int pos; + void *obj; + void *undomgr; + // TODO: replacefunc, ... + UiObserver *observers; +}; + +/* + * abstract list + */ +struct UiList { + /* get the first element */ + void*(*first)(UiList *list); + /* get the next element */ + void*(*next)(UiList *list); + /* get the nth element */ + void*(*get)(UiList *list, int i); + /* get the number of elements */ + int(*count)(UiList *list); + /* iterator changes after first() next() and get() */ + void *iter; + /* private - implementation dependent */ + void *data; + + /* binding function */ + void (*update)(UiList *list, int i); + /* binding object */ + void *obj; + + /* list of observers */ + UiObserver *observers; +}; + +struct UiRange { + double (*get)(UiRange *range); + void (*set)(UiRange *range, double value); + void (*setrange)(UiRange *range, double min, double max); + void (*setextent)(UiRange *range, double extent); + double value; + double min; + double max; + double extent; + void *obj; + /* list of observers */ + UiObserver *observers; +}; + + +void ui_init(char *appname, int argc, char **argv); +char* ui_appname(); + +UiContext* ui_global_context(void); + +void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata); + +void ui_onstartup(ui_callback f, void *userdata); +void ui_onopen(ui_callback f, void *userdata); +void ui_onexit(ui_callback f, void *userdata); + +void ui_main(); +void ui_show(UiObject *obj); +void ui_close(UiObject *obj); + +void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd); + +void* ui_document_new(size_t size); +void ui_document_destroy(void *doc); + +void ui_set_document(UiObject *obj, void *document); // deprecated +void ui_detach_document(UiObject *obj); // deprecated +void* ui_get_document(UiObject *obj); // deprecated +void ui_set_subdocument(void *document, void *sub); // deprecated +void ui_detach_subdocument(void *document, void *sub); // deprecated +void* ui_get_subdocument(void *document); // deprecated + +UiContext* ui_document_context(void *doc); + +void ui_attach_document(UiContext *ctx, void *document); +void ui_detach_document2(UiContext *ctx, void *document); + +void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); + +void ui_set_group(UiContext *ctx, int group); +void ui_unset_group(UiContext *ctx, int group); +int* ui_active_groups(UiContext *ctx, int *ngroups); + +void* ui_malloc(UiContext *ctx, size_t size); +void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize); +void ui_free(UiContext *ctx, void *ptr); +void* ui_realloc(UiContext *ctx, void *ptr, size_t size); + +// types + +UiInteger* ui_int_new(UiContext *ctx, char *name); +UiDouble* ui_double_new(UiContext *ctx, char *name); +UiString* ui_string_new(UiContext *ctx, char *name); +UiText* ui_text_new(UiContext *ctx, char *name); +UiRange* ui_range_new(UiContext *ctx, char *name); + +UiObserver* ui_observer_new(ui_callback f, void *data); +UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer); +UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data); +void ui_notify(UiObserver *observer, void *data); +void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data); +void ui_notify_evt(UiObserver *observer, UiEvent *event); + + +UiList* ui_list_new(UiContext *ctx, char *name); +void* ui_list_first(UiList *list); +void* ui_list_next(UiList *list); +void* ui_list_get(UiList *list, int i); +int ui_list_count(UiList *list); +void ui_list_append(UiList *list, void *data); +void ui_list_prepend(UiList *list, void *data); +void ui_list_clear(UiList *list); +void ui_list_addobsv(UiList *list, ui_callback f, void *data); +void ui_list_notify(UiList *list); + +void ui_clipboard_set(char *str); +char* ui_clipboard_get(); + +void ui_add_image(char *imgname, char *filename); // TODO: remove? + +// general widget functions +void ui_set_enabled(UIWIDGET widget, int enabled); +void ui_set_show_all(UIWIDGET widget, int value); +void ui_set_visible(UIWIDGET widget, int visible); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TOOLKIT_H */ + diff --git a/ui/ui/tree.h b/ui/ui/tree.h new file mode 100644 index 0000000..1bb603d --- /dev/null +++ b/ui/ui/tree.h @@ -0,0 +1,135 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TREE_H +#define UI_TREE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiModel UiModel; +typedef struct UiListCallbacks UiListCallbacks; +typedef struct UiListSelection UiListSelection; + +typedef enum UiModelType { + UI_STRING = 0, + UI_INTEGER, + UI_ICON, + UI_ICON_TEXT, +} UiModelType; + +struct UiModel { + /* + * number of columns + */ + int columns; + + /* + * array of column types + * array length is the number of columns + */ + UiModelType *types; + + /* + * array of column titles + * array length is the number of columns + */ + char **titles; + + /* + * function for translating model data to view data + * first argument is the pointer returned by UiList->get or UiTree->get + * second argument is the column index + * TODO: return + */ + void*(*getvalue)(void*, int); + + UiBool(*candrop)(UiEvent*, UiSelection*, UiList*, int); + void(*drop)(UiEvent*, UiSelection*, UiList*, int); + UiBool(*candrag)(UiEvent*, UiList*, int); + void(*data_get)(UiEvent*, UiSelection*, UiList*, int); + void(*data_delete)(UiEvent*, UiList*, int); +}; + +struct UiListCallbacks { + /* + * selection callback + */ + ui_callback activate; + + /* + * cursor callback + */ + ui_callback selection; + + /* + * userdata for all callbacks + */ + void *userdata; +}; + +struct UiListSelection { + /* + * number of selected items + */ + int count; + + /* + * indices of selected rows + */ + int *rows; +}; + +UiModel* ui_model(UiContext *ctx, ...); +void ui_model_free(UiContext *ctx, UiModel *mi); + +UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata); +UIWIDGET ui_listview_nv(UiObject *obj, char *listname, ui_getvaluefunc getvalue, ui_callback f, void *udata); + +UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb); +UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb); + +void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...); +void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm); +void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...); +void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm); + +UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata); +UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TREE_H */ + diff --git a/ui/ui/ui.h b/ui/ui/ui.h new file mode 100644 index 0000000..a7f7030 --- /dev/null +++ b/ui/ui/ui.h @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_H +#define UI_H + +#include "toolkit.h" +#include "container.h" +#include "menu.h" +#include "toolbar.h" +#include "window.h" +#include "stock.h" +#include "button.h" +#include "text.h" +#include "properties.h" +#include "tree.h" +#include "graphics.h" +#include "entry.h" +#include "range.h" +#include "image.h" +#include "display.h" +#include "dnd.h" + +#endif /* UI_H */ + diff --git a/ui/ui/window.h b/ui/ui/window.h new file mode 100644 index 0000000..93bebe9 --- /dev/null +++ b/ui/ui/window.h @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_WINDOW_H +#define UI_WINDOW_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UiObject* ui_window(char *title, void *window_data); +UiObject* ui_simplewindow(char *title, void *window_data); + +char* ui_openfiledialog(UiObject *obj); +char* ui_savefiledialog(UiObject *obj); + +#ifdef __cplusplus +} +#endif + +#endif /* WINDOW_H */ +