From 376e4e51dcdeb3fabe7031c5f6388ac17ed0b030 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Tue, 7 Jan 2025 22:51:08 +0100 Subject: [PATCH] add basic app structure --- configure | 370 ++-- libidav/Makefile | 23 +- libidav/config.c | 1033 ++++++++++ libidav/config.h | 218 ++ libidav/crypto.c | 64 +- libidav/crypto.h | 6 +- libidav/davqlexec.c | 625 +++--- libidav/davqlexec.h | 21 +- libidav/davqlparser.c | 375 ++-- libidav/davqlparser.h | 31 +- libidav/methods.c | 695 +++---- libidav/methods.h | 50 +- libidav/pwdstore.c | 587 ++++++ libidav/pwdstore.h | 218 ++ libidav/resource.c | 754 +++++-- libidav/resource.h | 77 +- libidav/session.c | 397 ++-- libidav/session.h | 32 +- libidav/utils.c | 481 +++-- libidav/utils.h | 64 +- libidav/versioning.c | 58 +- libidav/webdav.c | 281 ++- libidav/webdav.h | 136 +- libidav/xml.c | 125 +- libidav/xml.h | 2 +- make/cc.mk | 14 + make/clang.mk | 9 +- make/configure.vm | 309 ++- make/gcc.mk | 9 +- make/mingw.mk | 46 - make/project.xml | 20 +- make/suncc.mk | 9 +- make/uwproj.xsd | 31 +- make/windows.mk | 42 - mizucp/Makefile | 2 +- mizucp/main.c | 17 +- mizucp/main.h | 2 +- mizucp/srvctrl.c | 14 +- mizucp/srvctrl.h | 4 +- mizunara/Makefile | 7 +- mizunara/application.c | 41 + mizunara/application.h | 67 + ui/motif/dnd.c => mizunara/filebrowser.c | 21 +- ui/motif/range.h => mizunara/filebrowser.h | 17 +- mizunara/main.c | 24 +- ui/motif/dnd.h => mizunara/main.h | 10 +- mizunara/menu.c | 32 +- mizunara/menu.h | 10 +- mizunara/window.c | 48 + ui/motif/label.h => mizunara/window.h | 16 +- ucx/Makefile | 32 +- ucx/allocator.c | 167 +- ucx/array_list.c | 1041 ++++++++++ ucx/buffer.c | 507 +++-- ucx/compare.c | 277 +++ ucx/cx/allocator.h | 411 ++++ ucx/cx/array_list.h | 732 +++++++ ucx/cx/buffer.h | 680 +++++++ ucx/cx/collection.h | 181 ++ ucx/cx/common.h | 376 ++++ ucx/cx/common.h.orig | 138 ++ ucx/cx/compare.h | 529 +++++ ucx/cx/hash_key.h | 148 ++ ucx/cx/hash_map.h | 136 ++ ucx/cx/iterator.h | 332 +++ ucx/cx/json.h | 1357 +++++++++++++ ucx/cx/linked_list.h | 545 +++++ ucx/cx/list.h | 905 +++++++++ ucx/cx/map.h | 778 +++++++ ucx/cx/mempool.h | 158 ++ ucx/cx/printf.h | 395 ++++ ucx/cx/properties.h | 644 ++++++ ucx/cx/streams.h | 135 ++ ucx/cx/string.h | 1554 ++++++++++++++ ucx/cx/test.h | 338 ++++ ucx/cx/tree.h | 1405 +++++++++++++ ucx/cx/utils.h | 194 ++ ucx/hash_key.c | 112 ++ ucx/hash_map.c | 498 +++++ ucx/iterator.c | 136 ++ ucx/json.c | 1212 +++++++++++ ucx/linked_list.c | 1154 +++++++++++ ucx/list.c | 732 +++---- ucx/map.c | 509 +---- ucx/mempool.c | 330 +-- ucx/printf.c | 194 ++ ucx/properties.c | 498 +++-- ui/motif/list.h => ucx/streams.c | 81 +- ucx/string.c | 1479 +++++++++----- ucx/szmul.c | 50 + ucx/tree.c | 1054 ++++++++++ ucx/ucx/allocator.h | 206 -- ucx/ucx/array.h | 460 ----- ucx/ucx/avl.h | 353 ---- ucx/ucx/buffer.h | 339 ---- ucx/ucx/list.h | 512 ----- ucx/ucx/logging.h | 253 --- ucx/ucx/map.h | 549 ----- ucx/ucx/mempool.h | 209 -- ucx/ucx/stack.h | 240 --- ucx/ucx/string.h | 1201 ----------- ucx/ucx/test.h | 241 --- ucx/ucx/ucx.h | 204 -- ucx/ucx/utils.h | 508 ----- ui/Makefile | 4 +- ui/{motif/label.c => common/condvar.c} | 70 +- ui/common/condvar.h | 53 + ui/common/context.c | 302 ++- ui/common/context.h | 43 +- ui/common/document.c | 41 +- ui/common/menu.c | 331 +++ ui/{motif => common}/menu.h | 110 +- ui/common/object.c | 68 +- ui/common/object.h | 9 +- ui/common/objs.mk | 6 + ui/common/properties.c | 112 +- ui/common/properties.h | 6 +- ui/common/threadpool.c | 195 ++ ui/{motif/toolkit.h => common/threadpool.h} | 69 +- ui/common/toolbar.c | 148 ++ ui/{motif/graphics.h => common/toolbar.h} | 176 +- ui/common/types.c | 266 ++- ui/common/types.h | 7 +- ui/common/ucx_properties.c | 263 +++ .../common/ucx_properties.h | 20 +- ui/gtk/button.c | 451 ++++- ui/gtk/button.h | 41 +- ui/gtk/container.c | 1054 +++++++--- ui/gtk/container.h | 84 +- ui/gtk/display.c | 209 +- ui/gtk/display.h | 5 + ui/gtk/dnd.c | 204 +- ui/gtk/dnd.h | 25 + ui/gtk/draw_cairo.c | 33 +- ui/gtk/entry.c | 127 +- ui/gtk/entry.h | 1 - ui/gtk/graphics.c | 21 +- ui/gtk/headerbar.c | 174 ++ ui/gtk/headerbar.h | 93 + ui/gtk/icon.c | 208 ++ ui/{motif/stock.h => gtk/icon.h} | 39 +- ui/gtk/image.c | 163 +- ui/gtk/image.h | 30 +- ui/gtk/list.c | 1790 +++++++++++++++++ ui/gtk/list.h | 175 ++ ui/gtk/menu.c | 587 +++--- ui/gtk/menu.h | 117 +- ui/gtk/objs.mk | 5 +- ui/gtk/range.c | 6 +- ui/gtk/text.c | 717 +++++-- ui/gtk/text.h | 78 +- ui/gtk/toolbar.c | 550 ++--- ui/gtk/toolbar.h | 46 +- ui/gtk/toolkit.c | 274 ++- ui/gtk/toolkit.h | 134 +- ui/gtk/window.c | 834 +++++++- ui/motif/Makefile | 33 - ui/motif/button.c | 207 -- ui/motif/container.c | 805 -------- ui/motif/container.h | 160 -- ui/motif/graphics.c | 283 --- ui/motif/image.c | 40 - ui/motif/image.h | 31 - ui/motif/list.c | 207 -- ui/motif/menu.c | 626 ------ ui/motif/objs.mk | 49 - ui/motif/range.c | 138 -- ui/motif/stock.c | 76 - ui/motif/text.c | 488 ----- ui/motif/text.h | 88 - ui/motif/toolbar.c | 366 ---- ui/motif/toolbar.h | 105 - ui/motif/toolkit.c | 301 --- ui/motif/tree.c | 325 --- ui/motif/tree.h | 76 - ui/motif/window.c | 208 -- ui/ui/button.h | 60 +- ui/ui/container.h | 274 ++- ui/ui/display.h | 98 +- ui/ui/dnd.h | 13 +- ui/ui/entry.h | 34 +- ui/{motif/button.h => ui/icons.h} | 76 +- ui/ui/image.h | 31 +- ui/ui/menu.h | 82 +- ui/ui/properties.h | 12 +- ui/ui/text.h | 108 +- ui/ui/toolbar.h | 66 +- ui/ui/toolkit.h | 365 +++- ui/ui/tree.h | 156 +- ui/ui/ui.h | 1 + ui/ui/window.h | 58 +- 191 files changed, 35376 insertions(+), 16197 deletions(-) create mode 100644 libidav/config.c create mode 100644 libidav/config.h create mode 100644 libidav/pwdstore.c create mode 100644 libidav/pwdstore.h create mode 100644 make/cc.mk delete mode 100644 make/mingw.mk delete mode 100644 make/windows.mk create mode 100644 mizunara/application.c create mode 100644 mizunara/application.h rename ui/motif/dnd.c => mizunara/filebrowser.c (79%) rename ui/motif/range.h => mizunara/filebrowser.h (75%) rename ui/motif/dnd.h => mizunara/main.h (92%) create mode 100644 mizunara/window.c rename ui/motif/label.h => mizunara/window.h (86%) create mode 100644 ucx/array_list.c create mode 100644 ucx/compare.c create mode 100644 ucx/cx/allocator.h create mode 100644 ucx/cx/array_list.h create mode 100644 ucx/cx/buffer.h create mode 100644 ucx/cx/collection.h create mode 100644 ucx/cx/common.h create mode 100644 ucx/cx/common.h.orig create mode 100644 ucx/cx/compare.h create mode 100644 ucx/cx/hash_key.h create mode 100644 ucx/cx/hash_map.h create mode 100644 ucx/cx/iterator.h create mode 100644 ucx/cx/json.h create mode 100644 ucx/cx/linked_list.h create mode 100644 ucx/cx/list.h create mode 100644 ucx/cx/map.h create mode 100644 ucx/cx/mempool.h create mode 100644 ucx/cx/printf.h create mode 100644 ucx/cx/properties.h create mode 100644 ucx/cx/streams.h create mode 100644 ucx/cx/string.h create mode 100644 ucx/cx/test.h create mode 100644 ucx/cx/tree.h create mode 100644 ucx/cx/utils.h create mode 100644 ucx/hash_key.c create mode 100644 ucx/hash_map.c create mode 100644 ucx/iterator.c create mode 100644 ucx/json.c create mode 100644 ucx/linked_list.c create mode 100644 ucx/printf.c rename ui/motif/list.h => ucx/streams.c (51%) create mode 100644 ucx/szmul.c create mode 100644 ucx/tree.c delete mode 100644 ucx/ucx/allocator.h delete mode 100644 ucx/ucx/array.h delete mode 100644 ucx/ucx/avl.h delete mode 100644 ucx/ucx/buffer.h delete mode 100644 ucx/ucx/list.h delete mode 100644 ucx/ucx/logging.h delete mode 100644 ucx/ucx/map.h delete mode 100644 ucx/ucx/mempool.h delete mode 100644 ucx/ucx/stack.h delete mode 100644 ucx/ucx/string.h delete mode 100644 ucx/ucx/test.h delete mode 100644 ucx/ucx/ucx.h delete mode 100644 ucx/ucx/utils.h rename ui/{motif/label.c => common/condvar.c} (57%) create mode 100644 ui/common/condvar.h create mode 100644 ui/common/menu.c rename ui/{motif => common}/menu.h (55%) create mode 100644 ui/common/threadpool.c rename ui/{motif/toolkit.h => common/threadpool.h} (58%) create mode 100644 ui/common/toolbar.c rename ui/{motif/graphics.h => common/toolbar.h} (51%) create mode 100644 ui/common/ucx_properties.c rename ucx/ucx/properties.h => ui/common/ucx_properties.h (91%) create mode 100644 ui/gtk/headerbar.c create mode 100644 ui/gtk/headerbar.h create mode 100644 ui/gtk/icon.c rename ui/{motif/stock.h => gtk/icon.h} (74%) create mode 100644 ui/gtk/list.c create mode 100644 ui/gtk/list.h delete mode 100644 ui/motif/Makefile delete mode 100644 ui/motif/button.c delete mode 100644 ui/motif/container.c delete mode 100644 ui/motif/container.h delete mode 100644 ui/motif/graphics.c delete mode 100644 ui/motif/image.c delete mode 100644 ui/motif/image.h delete mode 100644 ui/motif/list.c delete mode 100644 ui/motif/menu.c delete mode 100644 ui/motif/objs.mk delete mode 100644 ui/motif/range.c delete mode 100644 ui/motif/stock.c delete mode 100644 ui/motif/text.c delete mode 100644 ui/motif/text.h delete mode 100644 ui/motif/toolbar.c delete mode 100644 ui/motif/toolbar.h delete mode 100644 ui/motif/toolkit.c delete mode 100644 ui/motif/tree.c delete mode 100644 ui/motif/tree.h delete mode 100644 ui/motif/window.c rename ui/{motif/button.h => ui/icons.h} (50%) diff --git a/configure b/configure index f2a69f8..c8bd057 100755 --- a/configure +++ b/configure @@ -1,47 +1,40 @@ #!/bin/sh +# create temporary directory +TEMP_DIR=".tmp-`uname -n`" +rm -Rf "$TEMP_DIR" +if mkdir -p "$TEMP_DIR"; then + : +else + echo "Cannot create tmp dir $TEMP_DIR" + echo "Abort" + exit 1 +fi +touch "$TEMP_DIR/options" +touch "$TEMP_DIR/features" -# some utility functions -isplatform() -{ - for p in $PLATFORM - do - if [ "$p" = "$1" ]; then - return 0 - fi - done - return 1 -} -notisplatform() -{ - for p in $PLATFORM - do - if [ "$p" = "$1" ]; then - return 1 - fi - done - return 0 -} -istoolchain() -{ - for t in $TOOLCHAIN - do - if [ "$t" = "$1" ]; then - return 0 - fi - done - return 1 -} -notistoolchain() -{ - for t in $TOOLCHAIN - do - if [ "$t" = "$1" ]; then - return 1 - fi - done - return 0 -} +# define standard variables +# also define standard prefix (this is where we will search for config.site) +prefix=/usr +exec_prefix= +bindir= +sbindir= +libdir= +libexecdir= +datarootdir= +datadir= +sysconfdir= +sharedstatedir= +localstatedir= +runstatedir= +includedir= +infodir= +localedir= +mandir= + +# custom variables + +# features # clean abort abort_configure() @@ -50,36 +43,6 @@ abort_configure() exit 1 } -# Test for availability of pkg-config -PKG_CONFIG=`command -v pkg-config` -: ${PKG_CONFIG:="false"} - -# Simple uname based platform detection -# $PLATFORM is used for platform dependent dependency selection -OS=`uname -s` -OS_VERSION=`uname -r` -printf "detect platform... " -if [ "$OS" = "SunOS" ]; then - PLATFORM="solaris sunos unix svr4" -elif [ "$OS" = "Linux" ]; then - PLATFORM="linux unix" -elif [ "$OS" = "FreeBSD" ]; then - PLATFORM="freebsd bsd unix" -elif [ "$OS" = "OpenBSD" ]; then - PLATFORM="openbsd bsd unix" -elif [ "$OS" = "NetBSD" ]; then - PLATFORM="netbsd bsd unix" -elif [ "$OS" = "Darwin" ]; then - PLATFORM="macos osx bsd unix" -elif echo "$OS" | grep -i "MINGW" > /dev/null; then - PLATFORM="windows mingw" -fi -: ${PLATFORM:="unix"} - -PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -` -echo "$PLATFORM_NAME" - - # help text printhelp() { @@ -87,7 +50,7 @@ printhelp() cat << __EOF__ Installation directories: --prefix=PREFIX path prefix for architecture-independent files - [$prefix] + [/usr] --exec-prefix=EPREFIX path prefix for architecture-dependent files [PREFIX] @@ -106,52 +69,14 @@ Installation directories: --mandir=DIR man documentation [DATAROOTDIR/man] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] -Build Types: ---debug add extra compile flags for debug builds ---release add extra compile flags for release builds - Options: - --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif) + --debug add extra compile flags for debug builds + --release add extra compile flags for release builds + --toolkit=(libadwaita|gtk4|gtk3|gtk2|cocoa) __EOF__ } -# create temporary directory -TEMP_DIR=".tmp-`uname -n`" -rm -Rf "$TEMP_DIR" -if mkdir -p "$TEMP_DIR"; then - : -else - echo "Cannot create tmp dir $TEMP_DIR" - echo "Abort" - exit 1 -fi -touch "$TEMP_DIR/options" -touch "$TEMP_DIR/features" - -# define standard variables -# also define standard prefix (this is where we will search for config.site) -prefix=/usr -exec_prefix= -bindir= -sbindir= -libdir= -libexecdir= -datarootdir= -datadir= -sysconfdir= -sharedstatedir= -localstatedir= -runstatedir= -includedir= -infodir= -localedir= -mandir= - -# custom variables - -# features - # # parse arguments # @@ -174,11 +99,10 @@ do "--infodir="*) infodir=${ARG#--infodir=} ;; "--mandir"*) mandir=${ARG#--mandir} ;; "--localedir"*) localedir=${ARG#--localedir} ;; - "--help"*) printhelp; abort_configure ;; - "--debug") BUILD_TYPE="debug" ;; - "--release") BUILD_TYPE="release" ;; + "--help"*) printhelp; abort_configure ;; + "--debug") BUILD_TYPE="debug" ;; + "--release") BUILD_TYPE="release" ;; "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;; - "--toolkit") echo "option '$ARG' needs a value:"; echo " $ARG=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)"; abort_configure ;; "-"*) echo "unknown option: $ARG"; abort_configure ;; esac done @@ -220,6 +144,76 @@ elif [ -f "$prefix/etc/config.site" ]; then echo ok fi +# Test for availability of pkg-config +PKG_CONFIG=`command -v pkg-config` +: ${PKG_CONFIG:="false"} + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +OS=`uname -s` +OS_VERSION=`uname -r` +printf "detect platform... " +if [ "$OS" = "SunOS" ]; then + PLATFORM="solaris sunos unix svr4" +elif [ "$OS" = "Linux" ]; then + PLATFORM="linux unix" +elif [ "$OS" = "FreeBSD" ]; then + PLATFORM="freebsd bsd unix" +elif [ "$OS" = "OpenBSD" ]; then + PLATFORM="openbsd bsd unix" +elif [ "$OS" = "NetBSD" ]; then + PLATFORM="netbsd bsd unix" +elif [ "$OS" = "Darwin" ]; then + PLATFORM="macos osx bsd unix" +elif echo "$OS" | grep -i "MINGW" > /dev/null; then + PLATFORM="windows mingw" +fi +: ${PLATFORM:="unix"} + +PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -` +echo "$PLATFORM_NAME" + +isplatform() +{ + for p in $PLATFORM + do + if [ "$p" = "$1" ]; then + return 0 + fi + done + return 1 +} +notisplatform() +{ + for p in $PLATFORM + do + if [ "$p" = "$1" ]; then + return 1 + fi + done + return 0 +} +istoolchain() +{ + for t in $TOOLCHAIN + do + if [ "$t" = "$1" ]; then + return 0 + fi + done + return 1 +} +notistoolchain() +{ + for t in $TOOLCHAIN + do + if [ "$t" = "$1" ]; then + return 1 + fi + done + return 0 +} + # generate vars.mk cat > "$TEMP_DIR/vars.mk" << __EOF__ @@ -279,32 +273,6 @@ print_check_msg() fi } -dependency_error_gtk2legacy() -{ - print_check_msg "$dep_checked_gtk2legacy" "checking for gtk2legacy... " - # dependency gtk2legacy - while true - do - if [ -z "$PKG_CONFIG" ]; then - break - fi - if test_pkg_config "gtk+-2.0" "" "" "" ; then - TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`" - TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`" - else - break - fi - TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY" - TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread" - print_check_msg "$dep_checked_gtk2legacy" "yes\n" - dep_checked_gtk2legacy=1 - return 1 - done - - print_check_msg "$dep_checked_gtk2legacy" "no\n" - dep_checked_gtk2legacy=1 - return 0 -} dependency_error_curl() { print_check_msg "$dep_checked_curl" "checking for curl... " @@ -674,25 +642,6 @@ dependency_error_cocoa() dep_checked_cocoa=1 return 0 } -dependency_error_winui() -{ - print_check_msg "$dep_checked_winui" "checking for winui... " - # dependency winui platform="windows" - while true - do - if notisplatform "windows"; then - break - fi - TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WINUI" - print_check_msg "$dep_checked_winui" "yes\n" - dep_checked_winui=1 - return 1 - done - - print_check_msg "$dep_checked_winui" "no\n" - dep_checked_winui=1 - return 0 -} # start collecting dependency information echo > "$TEMP_DIR/flags.mk" @@ -700,9 +649,9 @@ echo > "$TEMP_DIR/flags.mk" DEPENDENCIES_FAILED= ERROR=0 # unnamed dependencies -TEMP_CFLAGS="$CFLAGS" -TEMP_CXXFLAGS="$CXXFLAGS" -TEMP_LDFLAGS="$LDFLAGS" +TEMP_CFLAGS= +TEMP_CXXFLAGS= +TEMP_LDFLAGS= while true do while true @@ -843,62 +792,17 @@ GTKOBJ = draw_cairo.o __EOF__ return 0 } -checkopt_toolkit_gtk2legacy() -{ - VERR=0 - if dependency_error_gtk2legacy ; then - VERR=1 - fi - if [ $VERR -ne 0 ]; then - return 1 - fi - cat >> "$TEMP_DIR/make.mk" << __EOF__ -TOOLKIT = gtk -GTKOBJ = draw_gdk.o -__EOF__ - return 0 -} -checkopt_toolkit_qt5() -{ - VERR=0 - if dependency_error_qt5 ; then - VERR=1 - fi - if [ $VERR -ne 0 ]; then - return 1 - fi - cat >> "$TEMP_DIR/make.mk" << __EOF__ -TOOLKIT = qt -LD = $(CXX) -__EOF__ - return 0 -} -checkopt_toolkit_qt4() +checkopt_toolkit_cocoa() { VERR=0 - if dependency_error_qt4 ; then + if dependency_error_cocoa ; then VERR=1 fi if [ $VERR -ne 0 ]; then return 1 fi cat >> "$TEMP_DIR/make.mk" << __EOF__ -TOOLKIT = qt -LD = $(CXX) -__EOF__ - return 0 -} -checkopt_toolkit_motif() -{ - VERR=0 - if dependency_error_motif ; then - VERR=1 - fi - if [ $VERR -ne 0 ]; then - return 1 - fi - cat >> "$TEMP_DIR/make.mk" << __EOF__ -TOOLKIT = motif +TOOLKIT = cocoa __EOF__ return 0 } @@ -974,13 +878,6 @@ if [ -z "$OPT_TOOLKIT" ]; then ERROR=1 while true do - if isplatform "windows"; then - if checkopt_toolkit_winui ; then - echo " toolkit: winui" >> "$TEMP_DIR/options" - ERROR=0 - break - fi - fi if isplatform "macos"; then if checkopt_toolkit_cocoa ; then echo " toolkit: cocoa" >> "$TEMP_DIR/options" @@ -1062,43 +959,14 @@ else ERROR=1 DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED" fi - elif [ "$OPT_TOOLKIT" = "gtk2legacy" ]; then + elif [ "$OPT_TOOLKIT" = "cocoa" ]; then echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options - if checkopt_toolkit_gtk2legacy ; then - : - else - ERROR=1 - DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED" - fi - elif [ "$OPT_TOOLKIT" = "qt5" ]; then - echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options - if checkopt_toolkit_qt5 ; then - : - else - ERROR=1 - DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED" - fi - elif [ "$OPT_TOOLKIT" = "qt4" ]; then - echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options - if checkopt_toolkit_qt4 ; then - : - else - ERROR=1 - DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED" - fi - elif [ "$OPT_TOOLKIT" = "motif" ]; then - echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options - if checkopt_toolkit_motif ; then + if checkopt_toolkit_cocoa ; then : else ERROR=1 DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED" fi - else - echo - echo "Invalid option value - usage:" - echo " --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)" - abort_configure fi fi diff --git a/libidav/Makefile b/libidav/Makefile index 659d0c5..83cf2d6 100644 --- a/libidav/Makefile +++ b/libidav/Makefile @@ -1,7 +1,7 @@ # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. # -# Copyright 2019 Olaf Wintermann. All rights reserved. +# 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: @@ -26,7 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT=../ include ../config.mk # list of source files @@ -40,14 +39,22 @@ SRC += davqlexec.c SRC += crypto.c SRC += xml.c SRC += versioning.c +SRC += config.c +SRC += pwdstore.c -OBJ = $(SRC:%.c=../build/libidav/%.$(OBJ_EXT)) +OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT)) -all: ../build/ucx ../build/lib/libidav.$(LIB_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/lib/libidav$(LIB_EXT): $(OBJ) + $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ) -../build/libidav/%.$(OBJ_EXT): %.c - $(CC) $(CFLAGS) $(DAV_CFLAGS) -c -I../ucx -o $@ $< +../build/libidav/%$(OBJ_EXT): %.c + $(CC) -I../ucx $(CFLAGS) $(DAV_CFLAGS) -c -o $@ $< + +../build/ucx: + test -d '$@' + +cppcheck: $(SRC) + $(CPPCHECK) $(CPPCHECK_CONFIG) -I../ucx $(CPPCHECK_FLAGS) $+ 2>> ../$(CPPCHECK_LOG) diff --git a/libidav/config.c b/libidav/config.c new file mode 100644 index 0000000..7b0abbc --- /dev/null +++ b/libidav/config.c @@ -0,0 +1,1033 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) +#define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) + +#define print_error(lineno, ...) \ + do {\ + fprintf(stderr, "Error (config.xml line %u): ", lineno); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "Abort.\n"); \ + } while(0); +#define print_warning(lineno, ...) \ + do {\ + fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ + fprintf(stderr, __VA_ARGS__); \ + } while(0); + +#ifdef _WIN32 +#define ENV_HOME getenv("USERPROFILE") +#else +#define ENV_HOME getenv("HOME") +#endif /* _WIN32 */ + + +static int load_repository( + DavConfig *config, + DavCfgRepository **list_begin, + DavCfgRepository **list_end, + xmlNode *reponode); +static int load_key( + DavConfig *config, + DavCfgKey **list_begin, + DavCfgKey **list_end, + xmlNode *keynode); +static int load_proxy( + DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type); +static int load_namespace( + DavConfig *config, + DavCfgNamespace **list_begin, + DavCfgNamespace **list_end, + xmlNode *node); +static int load_secretstore(DavConfig *config, xmlNode *node); + + +int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node) { + str->node = node; + char *value = util_xml_get_text(node); + if(value) { + str->value = cx_strdup_a(config->mp->allocator, cx_str(value)); + return 0; + } else { + str->value = (cxmutstr){NULL, 0}; + return 1; + } +} + +void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node) { + cbool->node = node; + char *value = util_xml_get_text(node); + cbool->value = util_getboolean(value); +} + +static void set_xml_content(xmlNode *node, const char *content) { + xmlNode *child = node->children; + while(child) { + xmlNode *next = child->next; + xmlUnlinkNode(child); + xmlFreeNode(child); + child = next; + } + + if(content) { + xmlChar *encoded = xmlEncodeSpecialChars(node->doc, (const xmlChar*)content); + if(encoded) { + xmlNodeSetContent(node, encoded); + xmlFree(encoded); + } + } +} + +void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) { + if(str->value.ptr) { + cxFree(config->mp->allocator, str->value.ptr); + } + if(new_value.ptr) { + str->value = cx_strdup_a(config->mp->allocator, new_value); + } else { + str->value = cx_mutstrn(NULL, 0); + } + + if(!str->node) { + str->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, str->node); + } + set_xml_content(str->node, new_value.ptr); +} + +void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename) { + const char *content = new_value ? "true" : "false"; + cbool->value = new_value; + if(!cbool->node) { + cbool->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cbool->node); + } + set_xml_content(cbool->node, content); +} + +void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename) { + char content[32]; + snprintf(content, 32, "%" PRId64, new_value); + cint->value = new_value; + if(!cint->node) { + cint->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cint->node); + } + set_xml_content(cint->node, content); +} + +void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename) { + char content[32]; + snprintf(content, 32, "%" PRIu64, new_value); + cint->value = new_value; + if(!cint->node) { + cint->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cint->node); + } + set_xml_content(cint->node, content); +} + +void dav_cfg_string_remove(CfgString *str) { + if(str->node) { + xmlUnlinkNode(str->node); + xmlFreeNode(str->node); + str->node = NULL; + } +} + +void dav_cfg_bool_remove(CfgBool *cbool) { + if(cbool->node) { + xmlUnlinkNode(cbool->node); + xmlFreeNode(cbool->node); + cbool->node = NULL; + } +} + +void dav_cfg_int_remove(CfgInt *cint) { + if(cint->node) { + xmlUnlinkNode(cint->node); + xmlFreeNode(cint->node); + cint->node = NULL; + } +} + +void dav_cfg_uint_remove(CfgUInt *cint) { + if(cint->node) { + xmlUnlinkNode(cint->node); + xmlFreeNode(cint->node); + cint->node = NULL; + } +} + + +DavConfig* dav_config_new(xmlDoc *doc) { + CxMempool *cfg_mp = cxMempoolCreate(128, NULL); + DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig)); + memset(config, 0, sizeof(DavConfig)); + config->mp = cfg_mp; + + if(!doc) { + doc = xmlNewDoc(BAD_CAST "1.0"); + xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); + xmlDocSetRootElement(doc, root); + } + config->doc = doc; + + return config; +} + +DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) { + xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0); + if(!doc) { + if(error) { + *error = DAV_CONFIG_ERROR_XML; + } + return NULL; + } + + DavConfig *config = dav_config_new(doc); + CxMempool *cfg_mp = config->mp; + cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc); + + DavCfgRepository *repos_begin = NULL; + DavCfgRepository *repos_end = NULL; + DavCfgKey *keys_begin = NULL; + DavCfgKey *keys_end = NULL; + DavCfgNamespace *namespaces_begin = NULL; + DavCfgNamespace *namespaces_end = NULL; + + xmlNode *xml_root = xmlDocGetRootElement(doc); + xmlNode *node = xml_root->children; + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "repository")) { + ret = load_repository(config, &repos_begin, &repos_end, node); + } else if(xstreq(node->name, "key")) { + ret = load_key(config, &keys_begin, &keys_end, node); + } else if (xstreq(node->name, "http-proxy")) { + config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); + ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY); + } else if (xstreq(node->name, "https-proxy")) { + config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); + ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY); + } else if (xstreq(node->name, "namespace")) { + ret = load_namespace(config, &namespaces_begin, &namespaces_end, node); + } else if (xstreq(node->name, "secretstore")) { + ret = load_secretstore(config, node); + } else { + fprintf(stderr, "Unknown config element: %s\n", node->name); + ret = 1; + } + } + node = node->next; + } + + config->repositories = repos_begin; + config->keys = keys_begin; + config->namespaces = namespaces_begin; + + if(ret != 0 && error) { + *error = ret; + cxMempoolFree(cfg_mp); + } + + return config; +} + +void dav_config_free(DavConfig *config) { + cxMempoolFree(config->mp); +} + +CxBuffer* dav_config2buf(DavConfig *config) { + xmlChar* xmlText = NULL; + int textLen = 0; + xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen, 1); + + if(!xmlText) { + return NULL; + } + + CxBuffer *buf = cxBufferCreate(NULL, textLen, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + cxBufferWrite(xmlText, 1, textLen, buf); + xmlFree(xmlText); + return buf; +} + + +static int repo_add_config( + DavConfig *config, + DavCfgRepository *repo, + xmlNode* node) +{ + unsigned short lineno = node->line; + char *key = (char*)node->name; + char *value = util_xml_get_text(node); + + /* every key needs a value */ + if(!value) { + /* TODO: maybe this should only be reported, if the key is valid + * But this makes the code very ugly. + */ + print_error(lineno, "missing value for config element: %s\n", key); + return 1; + } + + if(xstreq(key, "name")) { + dav_cfg_string_set_node_value(config, &repo->name, node); + } else if(xstreq(key, "url")) { + dav_cfg_string_set_node_value(config, &repo->url, node); + } else if(xstreq(key, "user")) { + dav_cfg_string_set_node_value(config, &repo->user, node); + } else if(xstreq(key, "password")) { + dav_cfg_string_set_node_value(config, &repo->password, node); + } else if(xstreq(key, "stored-user")) { + dav_cfg_string_set_node_value(config, &repo->stored_user, node); + } else if(xstreq(key, "default-key")) { + dav_cfg_string_set_node_value(config, &repo->default_key, node); + } else if(xstreq(key, "full-encryption")) { + dav_cfg_bool_set_node_value(config, &repo->full_encryption, node); + } else if(xstreq(key, "content-encryption")) { + dav_cfg_bool_set_node_value(config, &repo->content_encryption, node); + } else if(xstreq(key, "decrypt-content")) { + dav_cfg_bool_set_node_value(config, &repo->decrypt_content, node); + } else if(xstreq(key, "decrypt-name")) { + dav_cfg_bool_set_node_value(config, &repo->decrypt_name, node); + } else if(xstreq(key, "cert")) { + dav_cfg_string_set_node_value(config, &repo->cert, node); + } else if(xstreq(key, "verification")) { + dav_cfg_bool_set_node_value(config, &repo->verification, node); + } else if(xstreq(key, "ssl-version")) { + repo->ssl_version.node = node; + int ssl_version = dav_str2ssl_version((const char*)value); + if(ssl_version == -1) { + print_warning(lineno, "unknown ssl version: %s\n", value); + repo->ssl_version.value = CURL_SSLVERSION_DEFAULT; + } else { + repo->ssl_version.value = ssl_version; + } + } else if(xstreq(key, "authmethods")) { + repo->authmethods.node = node; + repo->authmethods.value = CURLAUTH_NONE; + const char *delims = " \t\r\n"; + char *meths = strdup(value); + char *meth = strtok(meths, delims); + while (meth) { + if(xstrEQ(meth, "basic")) { + repo->authmethods.value |= CURLAUTH_BASIC; + } else if(xstrEQ(meth, "digest")) { + repo->authmethods.value |= CURLAUTH_DIGEST; + } else if(xstrEQ(meth, "negotiate")) { + repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE; + } else if(xstrEQ(meth, "ntlm")) { + repo->authmethods.value |= CURLAUTH_NTLM; + } else if(xstrEQ(meth, "any")) { + repo->authmethods.value = CURLAUTH_ANY; + } else if(xstrEQ(meth, "none")) { + /* skip */ + } else { + print_warning(lineno, + "unknown authentication method: %s\n", meth); + } + meth = strtok(NULL, delims); + } + free(meths); + } else { + print_error(lineno, "unkown repository config element: %s\n", key); + return 1; + } + return 0; +} + +int dav_str2ssl_version(const char *value) { + if(xstrEQ(value, "TLSv1")) { + return CURL_SSLVERSION_TLSv1; + } else if(xstrEQ(value, "SSLv2")) { + return CURL_SSLVERSION_SSLv2; + } else if(xstrEQ(value, "SSLv3")) { + return CURL_SSLVERSION_SSLv3; + } +#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034 + else if(xstrEQ(value, "TLSv1.0")) { + return CURL_SSLVERSION_TLSv1_0; + } else if(xstrEQ(value, "TLSv1.1")) { + return CURL_SSLVERSION_TLSv1_1; + } else if(xstrEQ(value, "TLSv1.2")) { + return CURL_SSLVERSION_TLSv1_2; + } +#endif +#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052 + else if(xstrEQ(value, "TLSv1.3")) { + return CURL_SSLVERSION_TLSv1_3; + } +#endif + return -1; +} + +static int load_repository( + DavConfig *config, + DavCfgRepository **list_begin, + DavCfgRepository **list_end, + xmlNode *reponode) +{ + DavCfgRepository *repo = dav_repository_new(config); + repo->node = reponode; + + // add repo config from child nodes + xmlNode *node = reponode->children; + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + ret = repo_add_config(config, repo, node); + } + node = node->next; + } + + // success: add repo to the configuration, error: free repo + if(ret) { + return 1; + } else { + cx_linked_list_add( + (void**)list_begin, + (void**)list_end, + offsetof(DavCfgRepository, prev), + offsetof(DavCfgRepository, next), + repo); + } + + return 0; +} + +static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) { + xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t"); + xmlAddChild(node, text1); + + cxmutstr ctn = cx_strdup(cx_strcast(content)); + xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr); + free(ctn.ptr); + + xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n"); + xmlAddChild(node, text2); + + return newNode; +} + +void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) { + if(repo->node) { + fprintf(stderr, "Error: dav_config_add_repository: node already exists\n"); + return; + } + + xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository"); + xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(repoNode, rtext1); + repo->node = repoNode; + if(repo->name.value.ptr) { + repo->name.node = addXmlNode(repoNode, "name", repo->name.value); + } + if(repo->url.value.ptr) { + repo->url.node = addXmlNode(repoNode, "url", repo->url.value); + } + if(repo->user.value.ptr) { + repo->user.node = addXmlNode(repoNode, "user", repo->user.value); + } + if(repo->password.value.ptr) { + repo->password.node = addXmlNode(repoNode, "password", repo->password.value); + } + + if(repo->stored_user.value.ptr) { + repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value); + } + if(repo->default_key.value.ptr) { + repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value); + } + if(repo->cert.value.ptr) { + repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value); + } + + // TODO: implement booleans + + // indent closing tag + xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t"); + xmlAddChild(repoNode, rtext2); + + // add repository to internal list + DavCfgRepository **list_begin = &config->repositories; + cx_linked_list_add( + (void**)list_begin, + NULL, + offsetof(DavCfgRepository, prev), + offsetof(DavCfgRepository, next), + repo); + + // add repository element to the xml document + xmlNode *xml_root = xmlDocGetRootElement(config->doc); + + xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t"); + xmlAddChild(xml_root, text1); + + xmlAddChild(xml_root, repoNode); + + xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(xml_root, text2); +} + +DavCfgRepository* dav_repository_new(DavConfig *config) { + DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository)); + memset(repo, 0, sizeof(DavCfgRepository)); + repo->decrypt_name.value = false; + repo->decrypt_content.value = true; + repo->decrypt_properties.value = false; + repo->verification.value = true; + repo->ssl_version.value = CURL_SSLVERSION_DEFAULT; + repo->authmethods.value = CURLAUTH_BASIC; + return repo; +} + +void dav_repository_free(DavConfig *config, DavCfgRepository *repo) { + // TODO +} + +void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) { + if(repo->prev) { + repo->prev->next = repo->next; + } + if(repo->next) { + repo->next->prev = repo->prev; + } + + if(repo->node) { + // TODO: remove newline after repo node + + xmlUnlinkNode(repo->node); + xmlFreeNode(repo->node); + } +} + +int dav_repository_get_flags(DavCfgRepository *repo) { + int flags = 0; + + DavBool encrypt_content = FALSE; + DavBool encrypt_name = FALSE; + DavBool encrypt_properties = FALSE; + DavBool decrypt_content = FALSE; + DavBool decrypt_name = FALSE; + DavBool decrypt_properties = FALSE; + if(repo->full_encryption.value) { + encrypt_content = TRUE; + encrypt_name = TRUE; + encrypt_properties = TRUE; + decrypt_content = TRUE; + decrypt_name = TRUE; + decrypt_properties = TRUE; + } else if(repo->content_encryption.value) { + encrypt_content = TRUE; + decrypt_content = TRUE; + } + + if(decrypt_content) { + flags |= DAV_SESSION_DECRYPT_CONTENT; + } + if(decrypt_name) { + flags |= DAV_SESSION_DECRYPT_NAME; + } + if(decrypt_properties) { + flags |= DAV_SESSION_DECRYPT_PROPERTIES; + } + if(encrypt_content) { + flags |= DAV_SESSION_ENCRYPT_CONTENT; + } + if(encrypt_name) { + flags |= DAV_SESSION_ENCRYPT_NAME; + } + if(encrypt_properties) { + flags |= DAV_SESSION_ENCRYPT_PROPERTIES; + } + return flags; +} + +void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) { + if(repo->url.value.ptr) { + cxFree(config->mp->allocator, repo->url.value.ptr); + } + repo->url.value = cx_strdup_a(config->mp->allocator, newurl); +} + +void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) { + const CxAllocator *a = config->mp->allocator; + if(user.length > 0) { + repo->user.value = cx_strdup_a(a, user); + } + if(password.length > 0) { + char *pwenc = util_base64encode(password.ptr, password.length); + repo->password.value = cx_strdup_a(a, cx_str(pwenc)); + free(pwenc); + } +} + +cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) { + cxmutstr pw = { NULL, 0 }; + if(repo->password.value.ptr) { + pw = cx_mutstr(util_base64decode(repo->password.value.ptr)); + } + return pw; +} + + +void dav_config_add_key(DavConfig *config, DavCfgKey *key) { + cx_linked_list_add( + (void**)&config->keys, + NULL, + offsetof(DavCfgKey, prev), + offsetof(DavCfgKey, next), + key); + + if(key->node) { + fprintf(stderr, "Error: dav_config_add_key: node already exists\n"); + return; + } + + xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key"); + xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(keyNode, rtext1); + key->node = keyNode; + + if(key->name.value.ptr) { + key->name.node = addXmlNode(keyNode, "name", key->name.value); + } + const char *type = dav_config_keytype_str(key->type); + if(type) { + key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type)); + } + if(key->file.value.ptr) { + key->file.node = addXmlNode(keyNode, "file", key->file.value); + } + + // indent closing tag + xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t"); + xmlAddChild(keyNode, rtext2); + + // add key element to the xml document + xmlNode *xml_root = xmlDocGetRootElement(config->doc); + + xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t"); + xmlAddChild(xml_root, text1); + + xmlAddChild(xml_root, keyNode); + + xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(xml_root, text2); +} + +DavCfgKey* dav_key_new(DavConfig *config) { + DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey)); + memset(key, 0, sizeof(DavCfgKey)); + key->type = DAV_KEY_TYPE_AES256; + return key; +} + +void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) { + cx_linked_list_remove( + (void**)&config->keys, + NULL, + offsetof(DavCfgKey, prev), + offsetof(DavCfgKey, next), + key); + if(key->node) { + // TODO: remove newline after key node + + xmlUnlinkNode(key->node); + xmlFreeNode(key->node); + } +} + + +static int load_key( + DavConfig *config, + DavCfgKey **list_begin, + DavCfgKey **list_end, + xmlNode *keynode) +{ + xmlNode *node = keynode->children; + DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey)); + memset(key, 0, sizeof(DavCfgKey)); + key->type = DAV_KEY_TYPE_AES256; + key->node = keynode; + + int error = 0; + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "name")) { + dav_cfg_string_set_node_value(config, &key->name, node); + } else if(xstreq(node->name, "file")) { + dav_cfg_string_set_node_value(config, &key->file, node); + } else if(xstreq(node->name, "type")) { + const char *value = util_xml_get_text(node); + key->type_node = node; + if(!strcmp(value, "aes128")) { + key->type = DAV_KEY_TYPE_AES128; + } else if(!strcmp(value, "aes256")) { + key->type = DAV_KEY_TYPE_AES256; + } else { + print_error(node->line, "unknown key type %s\n", value); + error = 1; + } + } else { + key->unknown_elements++; + } + + } + node = node->next; + } + + if(!key->name.value.ptr) { + error = 1; + } + + if(!error) { + error = 0; + size_t expected_length = 0; + if(key->type == DAV_KEY_TYPE_AES128) { + expected_length = 16; + } + if(key->type == DAV_KEY_TYPE_AES256) { + expected_length = 32; + } + /* + if(key->length < expected_length) { + print_error(keynode->line, "key %s is too small (%zu < %zu)\n", + key->name, + key->length, + expected_length); + error = 1; + } + + // add key to context + if(!error) { + cxMapPut(keys, cx_hash_key_str(key->name), key); + dav_context_add_key(context, key); + } + */ + } + + // cleanup + if(error) { + return 1; + } else { + // add key to the configuration + cx_linked_list_add( + (void**)list_begin, + (void**)list_end, + offsetof(DavCfgKey, prev), + offsetof(DavCfgKey, next), + key); + + return 0; + } +} + +static int load_proxy( + DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type) +{ + const char *stype; + if(type == DAV_HTTPS_PROXY) { + stype = "https"; + } else if(type == DAV_HTTP_PROXY) { + stype = "http"; + } else { + fprintf(stderr, "unknown proxy type\n"); + return 1; + } + + if(!proxy) { + // no xml error - so report this directly via fprintf + fprintf(stderr, "no memory reserved for %s proxy.\n", stype); + return 1; + } + + xmlNode *node = proxynode->children; + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + int reportmissingvalue = 0; + if(xstreq(node->name, "url")) { + reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->url, node); + } else if(xstreq(node->name, "user")) { + reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->user, node); + } else if(xstreq(node->name, "password")) { + reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->password, node); + } else if(xstreq(node->name, "no")) { + reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->noproxy, node); + } else { + proxy->unknown_elements++; + } + + if (reportmissingvalue) { + print_error(node->line, + "missing value for proxy configuration element: %s\n", + node->name); + ret = 1; + break; + } + } + node = node->next; + } + + if(!ret && !proxy->url.value.ptr) { + print_error(proxynode->line, "missing url for %s proxy.\n", stype); + return 1; + } + + return ret; +} + +static char* get_attr_content(xmlNode *node) { + // TODO: remove code duplication (util_xml_get_text) + while(node) { + if(node->type == XML_TEXT_NODE) { + return (char*)node->content; + } + node = node->next; + } + return NULL; +} + +static int load_namespace( + DavConfig *config, + DavCfgNamespace **list_begin, + DavCfgNamespace **list_end, + xmlNode *node) +{ + const char *prefix = NULL; + const char *uri = NULL; + xmlAttr *attr = node->properties; + while(attr) { + if(attr->type == XML_ATTRIBUTE_NODE) { + char *value = get_attr_content(attr->children); + if(!value) { + print_error( + node->line, + "missing value for attribute %s\n", (char*)attr->name); + return 1; + } + if(xstreq(attr->name, "prefix")) { + prefix = value; + } else if(xstreq(attr->name, "uri")) { + uri = value; + } else { + print_error( + node->line, + "unexpected attribute %s\n", (char*)attr->name); + return 1; + } + } + attr = attr->next; + } + + if(!prefix) { + print_error(node->line, "missing prefix attribute\n"); + return 1; + } + if(!uri) { + print_error(node->line, "missing uri attribute\n"); + return 1; + } + + DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace)); + memset(ns, 0, sizeof(DavCfgNamespace)); + ns->node = node; + ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix)); + ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri)); + cx_linked_list_add( + (void**)list_begin, + (void**)list_end, + offsetof(DavCfgNamespace, prev), + offsetof(DavCfgNamespace, next), + ns); + + return 0; +} + +static int load_secretstore(DavConfig *config, xmlNode *node) { + // currently only one secretstore is supported + + if(config->secretstore) { + return 1; + } + + config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore)); + + node = node->children; + int error = 0; + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "unlock-command")) { + dav_cfg_string_set_node_value(config, &config->secretstore->unlock_cmd, node); + } else if(xstreq(node->name, "lock-command")) { + dav_cfg_string_set_node_value(config, &config->secretstore->lock_cmd, node); + } + } + node = node->next; + } + + return error; +} + + + + + +DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) { + if(!config) { + return NULL; + } + DavCfgRepository *repo = config->repositories; + while(repo) { + if(!cx_strcmp(cx_strcast(repo->name.value), name)) { + return repo; + } + repo = repo->next; + } + return NULL; +} + +DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) { + cxmutstr p; + DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p); + *path = p.ptr; + return repo; +} + +DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) { + path->ptr = NULL; + path->length = 0; + + int s; + if(cx_strprefix(url, CX_STR("http://"))) { + s = 7; + } else if(cx_strprefix(url, CX_STR("https://"))) { + s = 8; + } else { + s = 1; + } + + // split URL into repository and path + cxstring r = cx_strsubs(url, s); + cxstring p = cx_strchr(r, '/'); + r = cx_strsubsl(url, 0, url.length-p.length); + if(p.length == 0) { + p = cx_strn("/", 1); + } + + DavCfgRepository *repo = dav_config_get_repository(config, r); + if(repo) { + *path = cx_strdup(p); + } else { + // TODO: who is responsible for freeing this repository? + // how can the callee know, if he has to call free()? + repo = dav_repository_new(config); + repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR("")); + if(url.ptr[url.length-1] == '/') { + repo->url.value = cx_strdup_a(config->mp->allocator, url); + *path = cx_strdup(CX_STR("/")); + } else if (cx_strchr(url, '/').length > 0) { + // TODO: fix the following workaround after + // fixing the inconsistent behavior of util_url_*() + cxstring repo_url = util_url_base_s(url); + repo->url.value = cx_strdup_a(config->mp->allocator, repo_url); + *path = cx_strdup(util_url_path_s(url)); + } else { + repo->url.value = cx_strdup(url); + *path = cx_strdup(CX_STR("/")); + } + } + + return repo; +} + +int dav_config_keytype(DavCfgKeyType type) { + switch(type) { + default: break; + case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256; + case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128; + } + return 0; +} + +const char* dav_config_keytype_str(DavCfgKeyType type) { + switch(type) { + default: break; + case DAV_KEY_TYPE_AES256: return "aes256"; + case DAV_KEY_TYPE_AES128: return "aes128"; + } + return NULL; +} + +int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) { + for(DavCfgKey *key=config->keys;key;key=key->next) { + char *file = cx_strdup_m(key->file.value).ptr; + cxmutstr keycontent = loadkey(file); + free(file); + + // TODO: check key length + + if(!keycontent.ptr) { + return 1; + } + + DavKey *davkey = calloc(1, sizeof(DavKey)); + davkey->name = cx_strdup_m(key->name.value).ptr; + davkey->type = dav_config_keytype(key->type); + davkey->data = keycontent.ptr; + davkey->length = keycontent.length; + + dav_context_add_key(ctx, davkey); + } + return 0; +} + +int dav_config_register_namespaces(DavConfig *config, DavContext *ctx) { + DavCfgNamespace *ns = config->namespaces; + while(ns) { + dav_add_namespace(ctx, ns->prefix.ptr, ns->uri.ptr); + ns = ns->next; + } + return 0; +} diff --git a/libidav/config.h b/libidav/config.h new file mode 100644 index 0000000..f85c097 --- /dev/null +++ b/libidav/config.h @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBIDAV_CONFIG_H +#define LIBIDAV_CONFIG_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavConfig DavConfig; +typedef struct DavCfgRepository DavCfgRepository; +typedef struct DavCfgProxy DavCfgProxy; +typedef struct DavCfgKey DavCfgKey; +typedef struct DavCfgNamespace DavCfgNamespace; +typedef struct DavCfgSecretStore DavCfgSecretStore; + +typedef struct CfgString CfgString; +typedef struct CfgInt CfgInt; +typedef struct CfgUInt CfgUInt; +typedef struct CfgBool CfgBool; + +typedef enum dav_cfg_key_type DavCfgKeyType; + +typedef cxmutstr (*dav_loadkeyfile_func)(const char *filename); + +#define DAV_HTTP_PROXY 1 +#define DAV_HTTPS_PROXY 2 + +enum dav_cfg_key_type { + DAV_KEY_TYPE_AES256 = 0, + DAV_KEY_TYPE_AES128, + DAV_KEY_TYPE_UNKNOWN +}; + +struct DavConfig { + CxMempool *mp; + + DavCfgRepository *repositories; + DavCfgKey *keys; + DavCfgNamespace *namespaces; + DavCfgProxy *http_proxy; + DavCfgProxy *https_proxy; + DavCfgSecretStore *secretstore; + + xmlDoc *doc; +}; + +struct CfgString { + cxmutstr value; + xmlNode *node; +}; + +struct CfgInt { + int64_t value; + xmlNode *node; +}; + +struct CfgUInt { + uint64_t value; + xmlNode *node; +}; + +struct CfgBool { + bool value; + xmlNode *node; +}; + + +struct DavCfgRepository { + xmlNode *node; + + CfgString name; + CfgString url; + CfgString user; + CfgString password; + CfgString stored_user; + CfgString default_key; + CfgString cert; + CfgBool verification; + + CfgBool full_encryption; + CfgBool content_encryption; + CfgBool decrypt_content; + CfgBool decrypt_name; + CfgBool decrypt_properties; + + CfgInt ssl_version; + CfgUInt authmethods; + + int unknown_elements; + + DavCfgRepository *prev; + DavCfgRepository *next; +}; + +struct DavCfgProxy { + CfgString url; + CfgString user; + CfgString password; + CfgString noproxy; + + int unknown_elements; +}; + +struct DavCfgKey { + xmlNode *node; + + CfgString name; + CfgString file; + DavCfgKeyType type; + xmlNode *type_node; + + DavCfgKey *prev; + DavCfgKey *next; + + int unknown_elements; +}; + +struct DavCfgNamespace { + xmlNode *node; + cxmutstr prefix; + cxmutstr uri; + + DavCfgNamespace *prev; + DavCfgNamespace *next; +}; + +struct DavCfgSecretStore { + CfgString unlock_cmd; + CfgString lock_cmd; +}; + +enum DavConfigError { + DAV_CONFIG_ERROR_XML = 0 +}; + +DavConfig* dav_config_new(xmlDoc *doc); + +DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error); + +void dav_config_free(DavConfig *config); + +CxBuffer* dav_config2buf(DavConfig *config); + +void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo); + +DavCfgRepository* dav_repository_new(DavConfig *config); +void dav_repository_free(DavConfig *config, DavCfgRepository *repo); +void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo); +int dav_repository_get_flags(DavCfgRepository *repo); +void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl); +void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password); +cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo); + +void dav_config_add_key(DavConfig *config, DavCfgKey *key); + +DavCfgKey* dav_key_new(DavConfig *config); +void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key); + +int dav_str2ssl_version(const char *str); + +int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node); +void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node); + +void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename); +void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename); +void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename); +void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename); + +void dav_cfg_string_remove(CfgString *str); +void dav_cfg_bool_remove(CfgBool *cbool); +void dav_cfg_int_remove(CfgInt *cint); +void dav_cfg_uint_remove(CfgUInt *cint); + +DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name); +DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path); +DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path); + +int dav_config_keytype(DavCfgKeyType type); +const char* dav_config_keytype_str(DavCfgKeyType type); +int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey); + +int dav_config_register_namespaces(DavConfig *config, DavContext *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIDAV_CONFIG_H */ + diff --git a/libidav/crypto.c b/libidav/crypto.c index e9d25c3..d52e845 100644 --- a/libidav/crypto.c +++ b/libidav/crypto.c @@ -29,8 +29,12 @@ #include #include #include -#include #include + +#ifndef _WIN32 +#include +#endif + #include "utils.h" #include "crypto.h" @@ -99,8 +103,8 @@ void aes_decrypter_init(AESDecrypter *dec) { 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; + size_t m = 16 - dec->ivpos; + size_t cp = m > len ? len : m; memcpy(dec->ivtmp + dec->ivpos, buf, cp); dec->ivpos += cp; if(dec->ivpos >= 16) { @@ -208,12 +212,15 @@ size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { memcpy(out, enc->iv, ivl); } EVP_EncryptUpdate(enc->ctx, out + ivl, &outlen, in, in_len); + // I think we don't need this + /* if(in_len != len) { int newoutlen = 16; EVP_EncryptFinal_ex(enc->ctx, out + ivl + outlen, &newoutlen); outlen += newoutlen; enc->end = 1; } + */ } else { out = malloc(16); EVP_EncryptFinal_ex(enc->ctx, out, &outlen); @@ -641,12 +648,15 @@ size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { CCCryptorStatus status; size_t avail = outlen; status = CCCryptorUpdate(enc->ctx, in, in_len, out + ivl, avail, &outlen); + // TODO: check if this still works + /* if(in_len != len) { size_t newoutlen = 16; status = CCCryptorFinal(enc->ctx, out + ivl + outlen, 16, &newoutlen); outlen += newoutlen; enc->end = 1; } + */ } else { out = malloc(32); CCCryptorStatus status; @@ -1083,10 +1093,11 @@ size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { // ready to decrypt the message ULONG outlen = clen + 32; - unsigned char *out = malloc(outlen); // decrypt if(clen > 0) { + unsigned char* out = malloc(outlen); + ULONG enc_len = 0; ULONG status = BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &enc_len, 0); if(status > 0) { @@ -1096,13 +1107,14 @@ size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { 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); } - // 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; @@ -1189,7 +1201,7 @@ size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { copy_iv_len = len > copy_iv_len ? copy_iv_len : len; memcpy(buf, enc->iv, copy_iv_len); - buf += copy_iv_len; + (char*)buf += copy_iv_len; len -= copy_iv_len; nread = copy_iv_len; @@ -1481,26 +1493,26 @@ DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, -UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) { - UcxBuffer *encbuf = ucx_buffer_new( - NULL, - in->size+16, - UCX_BUFFER_AUTOEXTEND); +CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key) { + CxBuffer *encbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + if(!encbuf) { + return NULL; + } AESEncrypter *enc = aes_encrypter_new( key, in, - (dav_read_func)ucx_buffer_read, + (dav_read_func)cxBufferRead, NULL); if(!enc) { - ucx_buffer_free(encbuf); + cxBufferFree(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); + cxBufferWrite(buf, 1, r, encbuf); } aes_encrypter_close(enc); @@ -1508,15 +1520,19 @@ UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) { return encbuf; } -UcxBuffer* aes_decrypt_buffer(UcxBuffer *in, DavKey *key) { - UcxBuffer *decbuf = ucx_buffer_new( - NULL, - in->size, - UCX_BUFFER_AUTOEXTEND); +CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key) { + CxBuffer *decbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + if(!decbuf) { + return NULL; + } AESDecrypter *dec = aes_decrypter_new( key, decbuf, - (dav_write_func)ucx_buffer_write); + (dav_write_func)cxBufferWrite); + if(!dec) { + cxBufferFree(decbuf); + return NULL; + } aes_write(in->space, 1, in->size, dec); aes_decrypter_shutdown(dec); diff --git a/libidav/crypto.h b/libidav/crypto.h index 7ea6597..b058488 100644 --- a/libidav/crypto.h +++ b/libidav/crypto.h @@ -30,7 +30,7 @@ #define DAV_CRYPTO_H #include "webdav.h" -#include +#include #ifdef __APPLE__ /* macos */ @@ -155,8 +155,8 @@ 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); +CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key); +CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key); #ifdef __cplusplus } diff --git a/libidav/davqlexec.c b/libidav/davqlexec.c index 9eb6ec5..4af5816 100644 --- a/libidav/davqlexec.c +++ b/libidav/davqlexec.c @@ -31,8 +31,12 @@ #include #include -#include -#include +#include +#include +#include +#include +#include + #include "davqlexec.h" #include "utils.h" #include "methods.h" @@ -46,9 +50,16 @@ DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap) { } args->first = NULL; + if(!st->args) { + args->first = NULL; + args->current = NULL; + return args; + } + DavQLArg *cur = NULL; - UCX_FOREACH(elm, st->args) { - intptr_t type = (intptr_t)elm->data; + CxIterator i = cxListIterator(st->args); + cx_foreach(void*, data, i) { + intptr_t type = (intptr_t)data; DavQLArg *arg = calloc(1, sizeof(DavQLArg)); if(!arg) { dav_ql_free_arglist(args); @@ -167,8 +178,9 @@ DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap) { 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); +cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error) { + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, a, CX_BUFFER_AUTO_EXTEND); int placeholder = 0; for(int i=0;ispace, buf->size)); - ucx_buffer_free(buf); - return ret; + return cx_mutstrn(buf.space, buf.size-1); } -static int fl_add_properties(DavSession *sn, UcxMempool *mp, UcxMap *map, DavQLExpression *expression) { +static int fl_add_properties(DavSession *sn, const CxAllocator *a, CxMap *map, DavQLExpression *expression) { if(!expression) { return 0; } if(expression->type == DAVQL_IDENTIFIER) { - DavProperty *property = ucx_mempool_malloc(mp, sizeof(DavProperty)); + DavProperty *property = cxMalloc(a, sizeof(DavProperty)); char *name; DavNamespace *ns = dav_get_property_namespace( sn->context, - sstrdup_a(mp->allocator, expression->srctext).ptr, + cx_strdup_a(a, expression->srctext).ptr, &name); if(!ns) { return -1; @@ -251,16 +263,16 @@ static int fl_add_properties(DavSession *sn, UcxMempool *mp, UcxMap *map, DavQLE property->name = name; property->value = NULL; - ucx_map_sstr_put(map, expression->srctext, property); + cxMapPut(map, cx_hash_key(expression->srctext.ptr, expression->srctext.length), property); } if(expression->left) { - if(fl_add_properties(sn, mp, map, expression->left)) { + if(fl_add_properties(sn, a, map, expression->left)) { return -1; } } if(expression->right) { - if(fl_add_properties(sn, mp, map, expression->right)) { + if(fl_add_properties(sn, a, map, expression->right)) { return -1; } } @@ -268,170 +280,170 @@ static int fl_add_properties(DavSession *sn, UcxMempool *mp, UcxMap *map, DavQLE return 0; } -static UcxBuffer* fieldlist2propfindrequest(DavSession *sn, UcxMempool *mp, UcxList *fields, int *isallprop) { - UcxMap *properties = ucx_map_new(32); +static CxBuffer* fieldlist2propfindrequest(DavSession *sn, const CxAllocator *a, CxList *fields, int *isallprop) { + CxMap *properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); *isallprop = 0; - UCX_FOREACH(elm, fields) { - DavQLField *field = elm->data; - if(!sstrcmp(field->name, S("*"))) { - ucx_map_free(properties); + CxIterator i = cxListIterator(fields); + cx_foreach(DavQLField*, field, i) { + if(!cx_strcmp(field->name, CX_STR("*"))) { + cxMapFree(properties); *isallprop = 1; return create_allprop_propfind_request(); - } else if(!sstrcmp(field->name, S("-"))) { - ucx_map_free(properties); + } else if(!cx_strcmp(field->name, CX_STR("-"))) { + cxMapFree(properties); return create_propfind_request(sn, NULL, "propfind", 0); } else { - if(fl_add_properties(sn, mp, properties, field->expr)) { + if(fl_add_properties(sn, a, properties, field->expr)) { // TODO: set error - ucx_map_free(properties); + cxMapFree(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); + i = cxMapIteratorValues(properties); + CxList *list = cxLinkedListCreateSimple(CX_STORE_POINTERS); + cx_foreach(DavProperty*, value, i) { + cxListAdd(list, value); } - UcxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0); - ucx_list_free(list); - ucx_map_free(properties); + CxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0); + cxListFree(list); + cxMapFree(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); +static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, CxList *fields) { + CxMap *new_properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32); DavResourceData *data = (DavResourceData*)res->data; // add basic properties void *value; - 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); + cxmutstr cl_keystr = dav_property_key("DAV:", "getcontentlength"); + CxHashKey cl_key = cx_hash_key(cl_keystr.ptr, cl_keystr.length); + value = cxMapGet(data->properties, cl_key); if(value) { - ucx_map_put(new_properties, cl_key, value); + cxMapPut(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); + cxmutstr cd_keystr = dav_property_key("DAV:", "creationdate"); + CxHashKey cd_key = cx_hash_key(cd_keystr.ptr, cd_keystr.length); + value = cxMapGet(data->properties, cd_key); if(value) { - ucx_map_put(new_properties, cd_key, value); + cxMapPut(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); + cxmutstr lm_keystr = dav_property_key("DAV:", "getlastmodified"); + CxHashKey lm_key = cx_hash_key(lm_keystr.ptr, lm_keystr.length); + value = cxMapGet(data->properties, lm_key); if(value) { - ucx_map_put(new_properties, lm_key, value); + cxMapPut(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); + cxmutstr ct_keystr = dav_property_key("DAV:", "getcontenttype"); + CxHashKey ct_key = cx_hash_key(ct_keystr.ptr, ct_keystr.length); + value = cxMapGet(data->properties, ct_key); if(value) { - ucx_map_put(new_properties, ct_key, value); + cxMapPut(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); + cxmutstr rt_keystr = dav_property_key("DAV:", "resourcetype"); + CxHashKey rt_key = cx_hash_key(rt_keystr.ptr, rt_keystr.length); + value = cxMapGet(data->properties, rt_key); if(value) { - ucx_map_put(new_properties, rt_key, value); + cxMapPut(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); + cxmutstr cn_keystr = dav_property_key(DAV_NS, "crypto-name"); + CxHashKey cn_key = cx_hash_key(cn_keystr.ptr, cn_keystr.length); + value = cxMapGet(data->properties, cn_key); if(value) { - ucx_map_put(new_properties, cn_key, value); + cxMapPut(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); + cxmutstr ck_keystr = dav_property_key(DAV_NS, "crypto-key"); + CxHashKey ck_key = cx_hash_key(ck_keystr.ptr, ck_keystr.length); + value = cxMapGet(data->properties, ck_key); if(value) { - ucx_map_put(new_properties, ck_key, value); + cxMapPut(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); + cxmutstr ch_keystr = dav_property_key(DAV_NS, "crypto-hash"); + CxHashKey ch_key = cx_hash_key(ch_keystr.ptr, ch_keystr.length); + value = cxMapGet(data->properties, ch_key); if(value) { - ucx_map_put(new_properties, ch_key, value); + cxMapPut(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)); + if(fields) { + CxIterator i = cxListIterator(fields); + cx_foreach(DavCompiledField*, field, i) { + DavQLStackObj field_result; + if(!dav_exec_expr(field->code, res, &field_result)) { + cxmutstr str; + str.ptr = NULL; + str.length = 0; + DavXmlNode *node = NULL; + if(field_result.type == 0) { + str = cx_asprintf_a( + sn->mp->allocator, + "%" PRId64, + field_result.data.integer); + } else if(field_result.type == 1) { + if(field_result.data.string) { + str = cx_strdup_a(sn->mp->allocator, cx_strn( + field_result.data.string, + field_result.length)); + } + } else if(field_result.type == 2) { + node = dav_copy_node(field_result.data.node); + } else { + // unknown type + // TODO: error + resource_free_properties(sn, new_properties); + return -1; + } + if(str.ptr) { + node = dav_session_malloc(sn, sizeof(DavXmlNode)); + memset(node, 0, sizeof(DavXmlNode)); + node->type = DAV_XML_TEXT; + node->content = str.ptr; + node->contentlength = str.length; + } + if(node) { + cxmutstr key = dav_property_key(field->ns, field->name); + + DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace)); + namespace->prefix = NULL; + namespace->name = dav_session_strdup(sn, field->ns); + + DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty)); + prop->name = dav_session_strdup(sn, field->name); + prop->ns = namespace; + prop->value = node; + + cxMapPut(new_properties, cx_hash_key(key.ptr, key.length), prop); + free(key.ptr); } - } else 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); + cxMapRemove(data->properties, cl_key); + cxMapRemove(data->properties, cd_key); + cxMapRemove(data->properties, lm_key); + cxMapRemove(data->properties, ct_key); + cxMapRemove(data->properties, rt_key); + cxMapRemove(data->properties, cn_key); + cxMapRemove(data->properties, ck_key); + cxMapRemove(data->properties, ch_key); resource_free_properties(sn, data->properties); data->properties = new_properties; @@ -452,7 +464,7 @@ static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, * execute a davql select statement */ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { - UcxMempool *mp = ucx_mempool_new(128); + CxMempool *mp = cxBasicMempoolCreate(128); DavResult result; result.result = NULL; result.status = 1; @@ -461,173 +473,179 @@ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { if(!args) { return result; } - ucx_mempool_reg_destr(mp, args, (ucx_destructor)dav_ql_free_arglist); + cxMempoolRegister(mp, args, (cx_destructor_func)dav_ql_free_arglist); int isallprop; - UcxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp, st->fields, &isallprop); + CxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp->allocator, st->fields, &isallprop); if(!rqbuf) { - ucx_mempool_destroy(mp); + cxMempoolFree(mp); return result; } - ucx_mempool_reg_destr(mp, rqbuf, (ucx_destructor)ucx_buffer_free); + cxMempoolRegister(mp, rqbuf, (cx_destructor_func)cxBufferFree); // 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( + CxList *cfieldlist = cxLinkedListCreate(mp->allocator, NULL, CX_STORE_POINTERS); + if(st->fields) { + CxIterator i = cxListIterator(st->fields); + cx_foreach(DavQLField*, field, i) { + if(cx_strcmp(field->name, CX_STR("*")) && cx_strcmp(field->name, CX_STR("-"))) { + // compile field expression + CxBuffer *code = dav_compile_expr( + sn->context, + mp->allocator, + field->expr, + args); + if(!code) { + // TODO: set error string + return result; + } + DavCompiledField *cfield = cxMalloc( + mp->allocator, + sizeof(DavCompiledField)); + + char *ns; + char *name; + dav_get_property_namespace_str( sn->context, - 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); - } + cx_strdup_a(mp->allocator, field->name).ptr, + &ns, + &name); + if(!ns || !name) { + // TODO: set error string + return result; + } + cfield->ns = ns; + cfield->name = name; + cfield->code = code; + cxListAdd(cfieldlist, cfield); + } + } } // get path string davqlerror_t error; - sstr_t path = dav_format_string(mp->allocator, st->path, args, &error); + cxmutstr path = dav_format_string(mp->allocator, st->path, args, &error); if(error) { // TODO: cleanup - ucx_mempool_destroy(mp); + cxMempoolFree(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); + CxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args); if(st->where && !where) { // TODO: cleanup - ucx_mempool_destroy(mp); + cxMempoolFree(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); + CxList *ordercr = NULL; + if(st->orderby) { + ordercr = cxLinkedListCreate(mp->allocator, NULL, sizeof(DavOrderCriterion)); + CxIterator i = cxListIterator(st->orderby); + cx_foreach(DavQLOrderCriterion*, oc, i) { + DavQLExpression *column = oc->column; + //printf("%.*s %s\n", column->srctext.length, column->srctext.ptr, oc->descending ? "desc" : "asc"); + if(column->type == DAVQL_IDENTIFIER) { + // TODO: remove code duplication (add_cmd) + davqlresprop_t resprop; + cxstring propertyname = cx_strchr(column->srctext, ':'); + if(propertyname.length > 0) { + char *ns; + char *name; + dav_get_property_namespace_str( + sn->context, + cx_strdup_a(mp->allocator, column->srctext).ptr, + &ns, + &name); + if(ns && name) { + DavOrderCriterion cr; + cr.type = 1; + cxmutstr keystr = dav_property_key_a(mp->allocator, ns, name); + cr.column.property = cx_hash_key(keystr.ptr, keystr.length); + cr.descending = oc->descending; + cxListAdd(ordercr, &cr); + } else { + // error + // TODO: cleanup + cxMempoolFree(mp); + return result; + } + } else if(dav_identifier2resprop(column->srctext, &resprop)) { + DavOrderCriterion cr; + cr.type = 0; + cr.column.resprop = resprop; + cr.descending = oc->descending; + cxListAdd(ordercr, &cr); } else { // error // TODO: cleanup - ucx_mempool_destroy(mp); + cxMempoolFree(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 if(column->type == DAVQL_NUMBER) { + // TODO: implement + fprintf(stderr, "order by number not supported\n"); + return result; } else { - // error + // something is broken // TODO: cleanup - ucx_mempool_destroy(mp); + cxMempoolFree(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 + CxList *stack = cxLinkedListCreateSimple(sizeof(DavQLRes)); // 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); + DavQLRes res; + res.resource = selroot; + res.depth = 0; + cxListInsert(stack, 0, &res); // reuseable response buffer - UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, mp->allocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(!rpbuf) { // TODO: cleanup - ucx_mempool_destroy(mp); + cxMempoolFree(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; + while(cxListSize(stack) > 0) { + DavQLRes *sr_ptr = cxListAt(stack, 0); // get first element from the stack + DavResource *root = sr_ptr->resource; + int res_depth = sr_ptr->depth; + cxListRemove(stack, 0); // remove first element - util_set_url(sn, dav_resource_get_href(sr->resource)); + util_set_url(sn, dav_resource_get_href(root)); CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); long http_status = 0; curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); - //printf("rpbuf: %s %s\n%.*s\n\n", sr->resource->path, sr->resource->href, rpbuf->pos, rpbuf->space); + //printf("rpbuf: %s\n%.*s\n\n", root->href, (int)rpbuf->size, rpbuf->space); + //fflush(stdout); if(ret == CURLE_OK && http_status == 207) { // in case of an redirect we have to adjust resource->href dav_set_effective_href(sn, root); // propfind request successful, now parse the response - char *url = "http://url/"; + char *url = "http://url/"; PropfindParser *parser = create_propfind_parser(rpbuf, url); - // TODO: test if parser is null + if(!parser) { + result.status = -1; + break; + } + ResponseTag response; int r; while((r = get_propfind_response(parser, &response)) != 0) { @@ -668,7 +686,7 @@ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { result.result = NULL; result.status = -1; dav_resource_free_all(selroot); - ucx_list_free(stack); + cxListFree(stack); break; } } else { @@ -685,14 +703,12 @@ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { //resource_add_child(root, child); resource_add_ordered_child(root, child, ordercr); if(child->iscollection && - (depth < 0 || depth > sr->depth+1)) + (depth < 0 || depth > res_depth+1)) { - DavQLRes *rs = ucx_mempool_malloc( - mp, - sizeof(DavQLRes)); - rs->resource = child; - rs->depth = sr->depth + 1; - stack = ucx_list_prepend(stack, rs); + DavQLRes rs; + rs.resource = child; + rs.depth = res_depth + 1; + cxListInsert(stack, 0, &rs); } } else { dav_resource_free(child); @@ -713,10 +729,10 @@ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { } // reset response buffer - ucx_buffer_seek(rpbuf, SEEK_SET, 0); + cxBufferSeek(rpbuf, SEEK_SET, 0); } - ucx_mempool_destroy(mp); + cxMempoolFree(mp); return result; } @@ -734,22 +750,22 @@ static int count_func_args(DavQLExpression *expr) { return count; } -int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop) { - if(!sstrcmp(src, S("name"))) { +int dav_identifier2resprop(cxstring src, davqlresprop_t *prop) { + if(!cx_strcmp(src, CX_STR("name"))) { *prop = DAVQL_RES_NAME; - } else if(!sstrcmp(src, S("path"))) { + } else if(!cx_strcmp(src, CX_STR("path"))) { *prop = DAVQL_RES_PATH; - } else if(!sstrcmp(src, S("href"))) { + } else if(!cx_strcmp(src, CX_STR("href"))) { *prop = DAVQL_RES_HREF; - } else if(!sstrcmp(src, S("contentlength"))) { + } else if(!cx_strcmp(src, CX_STR("contentlength"))) { *prop = DAVQL_RES_CONTENTLENGTH; - } else if(!sstrcmp(src, S("contenttype"))) { + } else if(!cx_strcmp(src, CX_STR("contenttype"))) { *prop = DAVQL_RES_CONTENTTYPE; - } else if(!sstrcmp(src, S("creationdate"))) { + } else if(!cx_strcmp(src, CX_STR("creationdate"))) { *prop = DAVQL_RES_CREATIONDATE; - } else if(!sstrcmp(src, S("lastmodified"))) { + } else if(!cx_strcmp(src, CX_STR("lastmodified"))) { *prop = DAVQL_RES_LASTMODIFIED; - } else if(!sstrcmp(src, S("iscollection"))) { + } else if(!cx_strcmp(src, CX_STR("iscollection"))) { *prop = DAVQL_RES_ISCOLLECTION; } else { return 0; @@ -757,7 +773,7 @@ int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop) { return 1; } -static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) { +static int add_cmd(DavContext *ctx, const CxAllocator *a, CxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) { if(!expr) { return 0; } @@ -767,7 +783,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr memset(&cmd, 0, sizeof(DavQLCmd)); davqlerror_t error; - sstr_t src = expr->srctext; + cxstring src = expr->srctext; switch(expr->type) { default: break; case DAVQL_NUMBER: { @@ -775,7 +791,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr 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); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); } else { // error return -1; @@ -786,14 +802,14 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr 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); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_TIMESTAMP: { if(src.ptr[0] == '%') { cmd.type = DAVQL_CMD_TIMESTAMP; cmd.data.timestamp = dav_ql_getarg_time(ap); - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); } else { // error return -1; @@ -801,7 +817,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr break; } case DAVQL_IDENTIFIER: { - sstr_t propertyname = sstrchr(src, ':'); + cxstring propertyname = cx_strchr(src, ':'); cmd.type = DAVQL_CMD_RES_IDENTIFIER; if(propertyname.length > 0) { cmd.type = DAVQL_CMD_PROP_IDENTIFIER; @@ -809,7 +825,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr char *name; dav_get_property_namespace_str( ctx, - sstrdup_a(a, src).ptr, + cx_strdup_a(a, src).ptr, &ns, &name); if(ns && name) { @@ -820,10 +836,10 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr return -1; } } else if(!dav_identifier2resprop(src, &cmd.data.resprop)) { - if(!sstrcmp(src, S("true"))) { + if(!cx_strcmp(src, CX_STR("true"))) { cmd.type = DAVQL_CMD_INT; cmd.data.integer = 1; - } else if(!sstrcmp(src, S("false"))) { + } else if(!cx_strcmp(src, CX_STR("false"))) { cmd.type = DAVQL_CMD_INT; cmd.data.integer = 0; } else { @@ -831,7 +847,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr return -1; } } - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_UNARY: { @@ -844,12 +860,12 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr } case DAVQL_SUB: { cmd.type = DAVQL_CMD_OP_UNARY_SUB; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_NEG: { cmd.type = DAVQL_CMD_OP_UNARY_NEG; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } default: break; @@ -890,7 +906,7 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr } default: break; } - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LOGICAL: { @@ -903,12 +919,12 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr 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); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LAND: { cmd.type = DAVQL_CMD_OP_LOGICAL_AND; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LOR: { @@ -916,61 +932,61 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr cmd.type = DAVQL_CMD_OP_LOGICAL_OR_L; DavQLCmd *or_l = (DavQLCmd*)(bcode->space + bcode->pos); - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); int nright = add_cmd(ctx, a, bcode, expr->right, ap); or_l->data.integer = nright + 1; cmd.type = DAVQL_CMD_OP_LOGICAL_OR; cmd.data.integer = 0; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&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); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_EQ: { cmd.type = DAVQL_CMD_OP_EQ; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_NEQ: { cmd.type = DAVQL_CMD_OP_NEQ; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LT: { cmd.type = DAVQL_CMD_OP_LT; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_GT: { cmd.type = DAVQL_CMD_OP_GT; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LE: { cmd.type = DAVQL_CMD_OP_LE; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_GE: { cmd.type = DAVQL_CMD_OP_GE; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_LIKE: { cmd.type = DAVQL_CMD_OP_LIKE; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } case DAVQL_UNLIKE: { cmd.type = DAVQL_CMD_OP_UNLIKE; - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); break; } default: break; @@ -991,12 +1007,12 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr // numargs cmd.type = DAVQL_CMD_INT; cmd.data.integer = count_func_args(expr); - ucx_buffer_write(&cmd, sizeof(cmd), 1, bcode); + cxBufferWrite(&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); + cxBufferWrite(&cmd, sizeof(cmd), 1, bcode); numcmd = 2; numcmd += nright; @@ -1016,14 +1032,14 @@ static int add_cmd(DavContext *ctx, UcxAllocator *a, UcxBuffer *bcode, DavQLExpr return numcmd; } -UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap) { - UcxBuffer *bcode = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); +CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap) { + CxBuffer *bcode = cxBufferCreate(NULL, 512, a, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(!bcode) { return NULL; } if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) { - ucx_buffer_free(bcode); + cxBufferFree(bcode); return NULL; } @@ -1031,53 +1047,56 @@ UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *l } 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); + cxmutstr s1m = obj1.type == 1 ? + cx_mutstrn(obj1.data.string, obj1.length) : + cx_asprintf("%" PRId64, obj1.data.integer); + cxmutstr s2m = obj1.type == 1 ? + cx_mutstrn(obj2.data.string, obj2.length) : + cx_asprintf("%" PRId64, obj2.data.integer); + + cxstring s1 = cx_strcast(s1m); + cxstring s2 = cx_strcast(s2m); int res = 0; switch(cmd) { case DAVQL_CMD_OP_EQ: { - res = sstrcmp(s1, s2) == 0; + res = cx_strcmp(s1, s2) == 0; break; } case DAVQL_CMD_OP_NEQ: { - res = sstrcmp(s1, s2) != 0; + res = cx_strcmp(s1, s2) != 0; break; } case DAVQL_CMD_OP_LT: { - res = sstrcmp(s1, s2) < 0; + res = cx_strcmp(s1, s2) < 0; break; } case DAVQL_CMD_OP_GT: { - res = sstrcmp(s1, s2) > 0; + res = cx_strcmp(s1, s2) > 0; break; } case DAVQL_CMD_OP_LE: { - res = sstrcmp(s1, s2) <= 0; + res = cx_strcmp(s1, s2) <= 0; break; } case DAVQL_CMD_OP_GE: { - res = sstrcmp(s1, s2) >= 0; + res = cx_strcmp(s1, s2) >= 0; break; } default: break; } if(obj1.type == 0) { - free(s1.ptr); + free(s1m.ptr); } if(obj2.type == 0) { - free(s2.ptr); + free(s2m.ptr); } return res; } -int dav_exec_expr(UcxBuffer *bcode, DavResource *res, DavQLStackObj *result) { +int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result) { if(!bcode) { result->type = 0; result->length = 0; diff --git a/libidav/davqlexec.h b/libidav/davqlexec.h index 11a6258..48511fc 100644 --- a/libidav/davqlexec.h +++ b/libidav/davqlexec.h @@ -32,7 +32,7 @@ #include "davqlparser.h" #include "webdav.h" -#include +#include #ifdef __cplusplus extern "C" { @@ -66,7 +66,8 @@ struct DavQLArgList { typedef enum { DAVQL_OK = 0, DAVQL_UNSUPPORTED_FORMATCHAR, - DAVQL_UNKNOWN_FORMATCHAR + DAVQL_UNKNOWN_FORMATCHAR, + DAVQL_OOM } davqlerror_t; typedef enum { @@ -116,7 +117,7 @@ struct DavQLCmd { davqlcmdtype_t type; union DavQLCmdData { int64_t integer; - sstr_t string; + cxmutstr string; time_t timestamp; davqlresprop_t resprop; DavPropName property; @@ -142,14 +143,14 @@ struct DavQLRes { typedef struct DavCompiledField { char *ns; char *name; - UcxBuffer *code; + CxBuffer *code; } DavCompiledField; typedef struct DavOrderCriterion { int type; // 0: resprop, 1: property union DavQLColumn { davqlresprop_t resprop; - UcxKey property; + CxHashKey property; } column; _Bool descending; } DavOrderCriterion; @@ -165,16 +166,16 @@ 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); +CxBuffer* dav_path_string(cxmutstr src, DavQLArgList *args, davqlerror_t *error); +cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error); DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap); -int dav_identifier2resprop(sstr_t src, davqlresprop_t *prop); +int dav_identifier2resprop(cxstring src, davqlresprop_t *prop); -UcxBuffer* dav_compile_expr(DavContext *ctx, UcxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap); +CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap); -int dav_exec_expr(UcxBuffer *bcode, DavResource *res, DavQLStackObj *result); +int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result); diff --git a/libidav/davqlparser.c b/libidav/davqlparser.c index 38f5694..22fa594 100644 --- a/libidav/davqlparser.c +++ b/libidav/davqlparser.c @@ -27,7 +27,9 @@ */ #include "davqlparser.h" -#include +#include +#include +#include #include #include #include @@ -93,9 +95,9 @@ static const char* _map_operator(davqloperator_t op) { 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)); + CxIterator i = cxListIterator(stmt->fields); + cx_foreach(DavQLField *, f, i) { + printf("%.*s, ", (int)f->name.length, f->name.ptr); } printf("\b\b \b\b\n"); } @@ -103,10 +105,10 @@ static void dav_debug_ql_fnames_print(DavQLStatement *stmt) { static void dav_debug_ql_stmt_print(DavQLStatement *stmt) { // Basic information - size_t fieldcount = ucx_list_size(stmt->fields); + size_t fieldcount = stmt->fields ? cxListSize(stmt->fields) : 0; int specialfield = 0; - if (stmt->fields) { - DavQLField* firstfield = (DavQLField*)stmt->fields->data; + if (fieldcount > 0) { + DavQLField* firstfield = (DavQLField*)cxListAt(stmt->fields, 0); if (firstfield->expr->type == DAVQL_IDENTIFIER) { switch (firstfield->expr->srctext.ptr[0]) { case '*': specialfield = 1; break; @@ -118,14 +120,14 @@ static void dav_debug_ql_stmt_print(DavQLStatement *stmt) { fieldcount--; } printf("Statement: %.*s\nType: %s\nField count: %zu %s\n", - sfmtarg(stmt->srctext), + (int)stmt->srctext.length, stmt->srctext.ptr, _map_querytype(stmt->type), fieldcount, _map_specialfield(specialfield)); dav_debug_ql_fnames_print(stmt); printf("Path: %.*s\nHas where clause: %s\n", - sfmtarg(stmt->path), + (int)stmt->path.length, stmt->path.ptr, stmt->where ? "yes" : "no"); // WITH attributes @@ -140,11 +142,11 @@ static void dav_debug_ql_stmt_print(DavQLStatement *stmt) { // 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), + CxIterator i = cxListIterator(stmt->orderby); + cx_foreach(DavQLOrderCriterion*, critdata, i) { + printf("%.*s %s%s", (int)critdata->column->srctext.length, critdata->column->srctext.ptr, critdata->descending ? "desc" : "asc", - crit->next ? ", " : "\n"); + i.index+1 < cxListSize(stmt->orderby) ? ", " : "\n"); } } else { printf("nothing\n"); @@ -168,7 +170,7 @@ static int dav_debug_ql_expr_selected(DavQLExpression *expr) { static void dav_debug_ql_expr_print(DavQLExpression *expr) { if (dav_debug_ql_expr_selected(expr)) { - sstr_t empty = ST("(empty)"); + cxstring empty = CX_STR("(empty)"); printf( "Text: %.*s\nType: %s\nOperator: %s\n", sfmtarg(expr->srctext), @@ -280,7 +282,7 @@ void dav_debug_statement(DavQLStatement *stmt) { } DavQLExpression *examineexpr = NULL; - UcxList *examineelem = NULL; + CxList *examineelem = NULL; int examineclause = 0; while(1) { @@ -294,8 +296,8 @@ void dav_debug_statement(DavQLStatement *stmt) { case DQLD_CMD_F: examineclause = DQLD_CMD_F; examineelem = stmt->fields; - if (stmt->fields) { - DavQLField* field = ((DavQLField*)stmt->fields->data); + if (stmt->fields && cxListSize(stmt->fields) > 0) { + DavQLField* field = cxListAt(stmt->fields, 0); examineexpr = field->expr; dav_debug_ql_field_print(field); } else { @@ -310,14 +312,16 @@ void dav_debug_statement(DavQLStatement *stmt) { case DQLD_CMD_O: examineclause = DQLD_CMD_O; examineelem = stmt->orderby; - examineexpr = stmt->orderby ? - ((DavQLOrderCriterion*)stmt->orderby->data)->column : NULL; + examineexpr = stmt->orderby && cxListSize(stmt->orderby) > 0 ? + ((DavQLOrderCriterion*)cxListAt(stmt->orderby, 0))->column : NULL; dav_debug_ql_expr_print(examineexpr); break; case DQLD_CMD_N: case DQLD_CMD_P: + printf("TODO: port code to ucx 3\n"); + /* if (examineelem) { - UcxList *newelem = (cmd == DQLD_CMD_N ? + CxList *newelem = (cmd == DQLD_CMD_N ? examineelem->next : examineelem->prev); if (newelem) { examineelem = newelem; @@ -338,6 +342,7 @@ void dav_debug_statement(DavQLStatement *stmt) { } else { printf("Currently not examining an expression list.\n"); } + */ break; case DQLD_CMD_L: if (dav_debug_ql_expr_selected(examineexpr)) { @@ -411,28 +416,28 @@ void dav_debug_statement(DavQLStatement *stmt) { #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) +#define token_sstr(token) ((token)->value) static void dav_error_in_context(int errorcode, const char *errormsg, - DavQLStatement *stmt, UcxList *token) { + DavQLStatement *stmt, DavQLToken *token) { // we try to achieve two things: get as many information as possible // and recover the concrete source string (and not the token strings) - sstr_t emptystring = ST(""); - sstr_t prev = token->prev ? (token->prev->prev ? + cxstring emptystring = CX_STR(""); + cxstring 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 ? + cxstring tokenstr = token_sstr(token); + cxstring next = token->next ? (token->next->next ? token_sstr(token->next->next) : token_sstr(token->next)) : emptystring; int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr; - char *pn = tokenstr.ptr + tokenstr.length; + const char *pn = tokenstr.ptr + tokenstr.length; int ln = next.ptr+next.length - pn; stmt->errorcode = errorcode; - stmt->errormessage = ucx_sprintf(errormsg, + stmt->errormessage = cx_asprintf(errormsg, lp, prev.ptr, sfmtarg(tokenstr), ln, pn).ptr; @@ -448,30 +453,18 @@ static void dav_error_in_context(int errorcode, const char *errormsg, #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") + cxstring keywords[] ={CX_STR("select"), CX_STR("set"), CX_STR("from"), CX_STR("at"), CX_STR("as"), + CX_STR("where"), CX_STR("anywhere"), CX_STR("like"), CX_STR("unlike"), CX_STR("and"), + CX_STR("or"), CX_STR("not"), CX_STR("xor"), CX_STR("with"), CX_STR("infinity"), + CX_STR("order"), CX_STR("by"), CX_STR("asc"), CX_STR("desc") }; - for (int i = 0 ; i < sizeof(keywords)/sizeof(sstr_t) ; i++) { - if (!sstrcasecmp(token->value, keywords[i])) { + for (int i = 0 ; i < sizeof(keywords)/sizeof(cxstring) ; i++) { + if (!cx_strcasecmp(token->value, keywords[i])) { return 1; } } @@ -479,18 +472,45 @@ static _Bool iskeyword(DavQLToken *token) { } static _Bool islongoperator(DavQLToken *token) { - sstr_t operators[] = {ST("and"), ST("or"), ST("not"), ST("xor"), - ST("like"), ST("unlike") + cxstring operators[] = {CX_STR("and"), CX_STR("or"), CX_STR("not"), CX_STR("xor"), + CX_STR("like"), CX_STR("unlike") }; - for (int i = 0 ; i < sizeof(operators)/sizeof(sstr_t) ; i++) { - if (!sstrcasecmp(token->value, operators[i])) { + for (int i = 0 ; i < sizeof(operators)/sizeof(cxstring) ; i++) { + if (!cx_strcasecmp(token->value, operators[i])) { return 1; } } return 0; } -static UcxList* dav_parse_add_token(UcxList *tokenlist, DavQLToken *token) { +static int dav_stmt_add_field(DavQLStatement *stmt, DavQLField *field) { + if(!stmt->fields) { + stmt->fields = cxLinkedListCreateSimple(CX_STORE_POINTERS); + if(!stmt->fields) { + stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; + return 1; + } + } + + if(cxListAdd(stmt->fields, field)) { + stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; + return 1; + } + + return 0; +} + + +static void tokenlist_free(DavQLToken *tokenlist) { + DavQLToken *token = tokenlist; + while(token) { + DavQLToken *next = token->next; + free(token); + token = next; + } +} + +static int dav_parse_add_token(DavQLToken **begin, DavQLToken **end, DavQLToken *token) { // determine token class (order of if-statements is very important!) char firstchar = token->value.ptr[0]; @@ -541,24 +561,22 @@ static UcxList* dav_parse_add_token(UcxList *tokenlist, DavQLToken *token) { } } - - UcxList *ret = ucx_list_append(tokenlist, token); - if (ret) { - return ret; - } else { - ucx_list_free(tokenlist); - return NULL; - } + cx_linked_list_add((void**)begin, (void**)end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token); + return 0; } -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; + + +static DavQLToken* dav_parse_tokenize(cxstring src) { +#define alloc_token() do {token = calloc(1, sizeof(DavQLToken));\ + if(!token) {tokenlist_free(tokens_begin); return NULL;}} while(0) +#define add_token() if(dav_parse_add_token(&tokens_begin, &tokens_end, token)) return NULL; + + DavQLToken *tokens_begin = NULL; + DavQLToken *tokens_end = NULL; DavQLToken *token = NULL; + char insequence = '\0'; for (size_t i = 0 ; i < src.length ; i++) { // quoted strings / identifiers are a single token @@ -628,14 +646,10 @@ static UcxList* dav_parse_tokenize(sstr_t src) { 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; - } + token->value = CX_STR(""); + + cx_linked_list_add((void**)&tokens_begin, (void**)&tokens_end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token); + return tokens_begin; #undef alloc_token #undef add_token } @@ -661,18 +675,17 @@ 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)) + (token->tokenclass == expectedclass)) #define tokenvalue_is(token, expectedvalue) (token && \ - !sstrcasecmp(((DavQLToken*)(token)->data)->value, S(expectedvalue))) + !cx_strcasecmp(token->value, cx_str(expectedvalue))) -typedef int(*exprparser_f)(DavQLStatement*,UcxList*,DavQLExpression*); +typedef int(*exprparser_f)(DavQLStatement*,DavQLToken*,DavQLExpression*); -static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* token, +static int dav_parse_binary_expr(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv, exprparser_f parseR) { @@ -692,7 +705,7 @@ static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* token, return 0; } total_consumed += consumed; - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); char *op; if (token_is(token, DAVQL_TOKEN_OPERATOR) && @@ -731,15 +744,20 @@ static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* token, return total_consumed; } -static void dav_add_fmt_args(DavQLStatement *stmt, sstr_t str) { +static void fmt_args_add(DavQLStatement *stmt, void *data) { + if(!stmt->args) { + stmt->args = cxLinkedListCreateSimple(CX_STORE_POINTERS); + } + cxListAdd(stmt->args, data); +} + +static void dav_add_fmt_args(DavQLStatement *stmt, cxstring str) { int placeholder = 0; for (size_t i=0;iargs = ucx_list_append( - stmt->args, - (void*)(intptr_t)c); + fmt_args_add(stmt, (void*)(intptr_t)c); } placeholder = 0; } else if (c == '%') { @@ -748,7 +766,7 @@ static void dav_add_fmt_args(DavQLStatement *stmt, sstr_t str) { } } -static int dav_parse_literal(DavQLStatement* stmt, UcxList* token, +static int dav_parse_literal(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { expr->srctext = token_sstr(token); @@ -772,7 +790,7 @@ static int dav_parse_literal(DavQLStatement* stmt, UcxList* token, return 0; } // add fmtspec type to query arg list - stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)expr->srctext.ptr[1]); + fmt_args_add(stmt, (void*)(intptr_t)expr->srctext.ptr[1]); } else { return 0; } @@ -781,10 +799,10 @@ static int dav_parse_literal(DavQLStatement* stmt, UcxList* token, } // forward declaration -static int dav_parse_expression(DavQLStatement* stmt, UcxList* token, +static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr); -static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token, +static int dav_parse_arglist(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { expr->srctext.ptr = token_sstr(token).ptr; @@ -796,7 +814,7 @@ static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token, // RULE: Expression, {",", Expression}; DavQLExpression *arglist = expr; DavQLExpression arg; - char *lastchar = expr->srctext.ptr; + const char *lastchar = expr->srctext.ptr; int consumed; do { memset(&arg, 0, sizeof(DavQLExpression)); @@ -804,7 +822,7 @@ static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token, if (consumed) { lastchar = arg.srctext.ptr + arg.srctext.length; total_consumed += consumed; - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); // look ahead for a comma if (token_is(token, DAVQL_TOKEN_COMMA)) { total_consumed++; @@ -837,7 +855,7 @@ static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token, return total_consumed; } -static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token, +static int dav_parse_funccall(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { // RULE: Identifier, "(", ArgumentList, ")"; @@ -861,7 +879,7 @@ static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token, return 2; } if (argtokens) { - token = ucx_list_get(token, argtokens); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), argtokens); dqlsec_malloc(stmt, expr->right, DavQLExpression); memcpy(expr->right, &arg, sizeof(DavQLExpression)); } else { @@ -881,10 +899,10 @@ static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token, } } -static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token, +static int dav_parse_unary_expr(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { - UcxList *firsttoken = token; // save for srctext recovery + DavQLToken *firsttoken = token; // save for srctext recovery DavQLExpression* atom = expr; int total_consumed = 0; @@ -923,7 +941,7 @@ static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token, _error_invalid_expr, stmt, token); return 0; } - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); total_consumed += consumed; if (token_is(token, DAVQL_TOKEN_CLOSEP)) { token = token->next; total_consumed++; @@ -951,8 +969,8 @@ static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token, // 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)); + cxstring lasttoken = + token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed-1)); expr->srctext.length = lasttoken.ptr - expr->srctext.ptr + lasttoken.length; } else { @@ -964,7 +982,7 @@ static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token, return total_consumed; } -static int dav_parse_bitexpr(DavQLStatement* stmt, UcxList* token, +static int dav_parse_bitexpr(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { return dav_parse_binary_expr(stmt, token, expr, @@ -973,7 +991,7 @@ static int dav_parse_bitexpr(DavQLStatement* stmt, UcxList* token, dav_parse_bitexpr); } -static int dav_parse_multexpr(DavQLStatement* stmt, UcxList* token, +static int dav_parse_multexpr(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { return dav_parse_binary_expr(stmt, token, expr, @@ -982,7 +1000,7 @@ static int dav_parse_multexpr(DavQLStatement* stmt, UcxList* token, dav_parse_multexpr); } -static int dav_parse_expression(DavQLStatement* stmt, UcxList* token, +static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token, DavQLExpression* expr) { return dav_parse_binary_expr(stmt, token, expr, @@ -991,7 +1009,7 @@ static int dav_parse_expression(DavQLStatement* stmt, UcxList* token, dav_parse_expression); } -static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token, +static int dav_parse_named_field(DavQLStatement *stmt, DavQLToken *token, DavQLField *field) { int total_consumed = 0, consumed; @@ -1010,7 +1028,7 @@ static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token, return 0; } - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); total_consumed += consumed; if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) { @@ -1034,13 +1052,16 @@ static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token, } } -static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { +static int dav_parse_fieldlist(DavQLStatement *stmt, DavQLToken *token) { // RULE: "-" if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) { DavQLField *field; dqlsec_malloc(stmt, field, DavQLField); - dqlsec_list_append_or_free(stmt, stmt->fields, field); + if(dav_stmt_add_field(stmt, field)) { + free(field); + return 0; + } dqlsec_mallocz(stmt, field->expr, DavQLExpression); field->expr->type = DAVQL_IDENTIFIER; field->expr->srctext = field->name = token_sstr(token); @@ -1051,7 +1072,10 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { 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); + if(dav_stmt_add_field(stmt, field)) { + free(field); + return 0; + } dqlsec_mallocz(stmt, field->expr, DavQLExpression); field->expr->type = DAVQL_IDENTIFIER; field->expr->srctext = field->name = token_sstr(token); @@ -1060,7 +1084,7 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { int consumed = 1; do { - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); total_consumed += consumed; if (token_is(token, DAVQL_TOKEN_COMMA)) { @@ -1068,10 +1092,13 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { 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); + DavQLField *add_field; + dqlsec_malloc(stmt, add_field, DavQLField); + memcpy(add_field, &localfield, sizeof(DavQLField)); + if(dav_stmt_add_field(stmt, add_field)) { + free(add_field); + return 0; + } } } else { consumed = 0; @@ -1092,8 +1119,11 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { 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); + if(dav_stmt_add_field(stmt, field)) { + free(field); + return 0; + } + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); total_consumed += consumed; } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER) // look ahead, if the field is JUST the identifier @@ -1105,7 +1135,10 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { 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); + if(dav_stmt_add_field(stmt, field)) { + free(field); + return 0; + } consumed = 1; total_consumed++; @@ -1137,10 +1170,10 @@ static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) { } // forward declaration -static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, +static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token, DavQLExpression *expr); -static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token, +static int dav_parse_bool_prim(DavQLStatement *stmt, DavQLToken *token, DavQLExpression *expr) { expr->type = DAVQL_LOGICAL; @@ -1154,9 +1187,9 @@ static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token, if (!total_consumed || stmt->errorcode) { return 0; } - token = ucx_list_get(token, total_consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed); - UcxList* optok = token; + DavQLToken* optok = token; // RULE: Expression, (" like " | " unlike "), String if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok, "like") || tokenvalue_is(optok, "unlike"))) { @@ -1248,7 +1281,7 @@ static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token, } } -static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token, +static int dav_parse_bool_expr(DavQLStatement *stmt, DavQLToken *token, DavQLExpression *expr) { // RULE: "not ", LogicalExpression @@ -1264,7 +1297,7 @@ static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token, return 0; } if (consumed) { - sstr_t lasttok = token_sstr(ucx_list_get(token, consumed-1)); + cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed-1)); expr->srctext.length = lasttok.ptr - expr->srctext.ptr + lasttok.length; return consumed + 1; @@ -1278,7 +1311,7 @@ static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token, 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); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); if (token_is(token, DAVQL_TOKEN_CLOSEP)) { token = token->next; @@ -1301,10 +1334,10 @@ static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token, return dav_parse_bool_prim(stmt, token, expr); } -static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, +static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token, DavQLExpression *expr) { - UcxList *firsttoken = token; + DavQLToken *firsttoken = token; int total_consumed = 0; // RULE: BooleanLiteral, [LogicalOperator, LogicalExpression]; @@ -1320,7 +1353,7 @@ static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, return 0; } total_consumed += consumed; - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); if (token_is(token, DAVQL_TOKEN_OPERATOR)) { expr->type = DAVQL_LOGICAL; @@ -1354,7 +1387,7 @@ static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, return 0; } total_consumed += consumed; - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); dqlsec_malloc(stmt, expr->left, DavQLExpression); memcpy(expr->left, &left, sizeof(DavQLExpression)); @@ -1368,20 +1401,20 @@ static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token, // 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)); + cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(firsttoken, 0, offsetof(DavQLToken, next), total_consumed-1)); expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length; } return total_consumed; } -static int dav_parse_where_clause(DavQLStatement *stmt, UcxList *token) { +static int dav_parse_where_clause(DavQLStatement *stmt, DavQLToken *token) { dqlsec_mallocz(stmt, stmt->where, DavQLExpression); return dav_parse_logical_expr(stmt, token, stmt->where); } -static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) { +static int dav_parse_with_clause(DavQLStatement *stmt, DavQLToken *token) { int total_consumed = 0; @@ -1404,7 +1437,7 @@ static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) { if (depthexpr->srctext.ptr[0] == '%') { stmt->depth = DAV_DEPTH_PLACEHOLDER; } else { - sstr_t depthstr = depthexpr->srctext; + cxstring depthstr = depthexpr->srctext; char *conv = malloc(depthstr.length+1); if (!conv) { dav_free_expression(depthexpr); @@ -1436,7 +1469,7 @@ static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) { return total_consumed; } -static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token, +static int dav_parse_order_crit(DavQLStatement *stmt, DavQLToken *token, DavQLOrderCriterion *crit) { // RULE: (Identifier | Number), [" asc"|" desc"]; @@ -1456,7 +1489,7 @@ static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token, dqlsec_malloc(stmt, crit->column, DavQLExpression); memcpy(crit->column, &expr, sizeof(DavQLExpression)); - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); if (token_is(token, DAVQL_TOKEN_KEYWORD) && ( tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) { @@ -1469,12 +1502,19 @@ static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token, } } -static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) { +static int dav_parse_orderby_clause(DavQLStatement *stmt, DavQLToken *token) { int total_consumed = 0, consumed; DavQLOrderCriterion crit; + if(!stmt->orderby) { + stmt->orderby = cxLinkedListCreateSimple(sizeof(DavQLOrderCriterion)); + if(!stmt->orderby) { + return 0; + } + } + // RULE: OrderByCriterion, {",", OrderByCriterion}; do { consumed = dav_parse_order_crit(stmt, token, &crit); @@ -1486,13 +1526,13 @@ static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) { stmt, token); return 0; } - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), 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(cxListAdd(stmt->orderby, &crit)) { + stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; + return 0; + } if (token_is(token, DAVQL_TOKEN_COMMA)) { total_consumed++; @@ -1506,7 +1546,7 @@ static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) { } -static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) { +static int dav_parse_assignments(DavQLStatement *stmt, DavQLToken *token) { // RULE: Assignment, {",", Assignment} int total_consumed = 0, consumed; @@ -1540,11 +1580,14 @@ static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) { dav_free_field(field); return total_consumed; } - token = ucx_list_get(token, consumed); + token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed); total_consumed += consumed; // Add assignment to list and check if there's another one - dqlsec_list_append_or_free(stmt, stmt->fields, field); + if(dav_stmt_add_field(stmt, field)) { + free(field); + return 0; + } consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0; if (consumed) { token = token->next; @@ -1560,7 +1603,7 @@ static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) { return total_consumed; } -static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) { +static int dav_parse_path(DavQLStatement *stmt, DavQLToken *tokens) { if (token_is(tokens, DAVQL_TOKEN_STRING)) { stmt->path = token_sstr(tokens); tokens = tokens->next; @@ -1572,7 +1615,7 @@ static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) { int consumed = 1; while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) && !token_is(tokens, DAVQL_TOKEN_END)) { - sstr_t toksstr = token_sstr(tokens); + cxstring toksstr = token_sstr(tokens); stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length; tokens = tokens->next; consumed++; @@ -1582,7 +1625,7 @@ static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) { tokenvalue_is(tokens, "%s")) { stmt->path = token_sstr(tokens); tokens = tokens->next; - stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)'s'); + fmt_args_add(stmt, (void*)(intptr_t)'s'); return 1; } else { dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, @@ -1596,11 +1639,11 @@ static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) { * @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) { +static void dav_parse_select_statement(DavQLStatement *stmt, DavQLToken *tokens) { stmt->type = DAVQL_SELECT; // Consume field list - tokens = ucx_list_get(tokens, dav_parse_fieldlist(stmt, tokens)); + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_fieldlist(stmt, tokens)); if (stmt->errorcode) { return; } @@ -1616,7 +1659,7 @@ static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { } // Consume path - tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens)); + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens)); if (stmt->errorcode) { return; } @@ -1626,7 +1669,7 @@ static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "with")) { tokens = tokens->next; - tokens = ucx_list_get(tokens, + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_with_clause(stmt, tokens)); } if (stmt->errorcode) { @@ -1637,7 +1680,7 @@ static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "where")) { tokens = tokens->next; - tokens = ucx_list_get(tokens, + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_where_clause(stmt, tokens)); } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "anywhere")) { @@ -1656,7 +1699,7 @@ static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "by")) { tokens = tokens->next; - tokens = ucx_list_get(tokens, + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_orderby_clause(stmt, tokens)); } else { dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN, @@ -1680,11 +1723,11 @@ static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) { } } -static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { +static void dav_parse_set_statement(DavQLStatement *stmt, DavQLToken *tokens) { stmt->type = DAVQL_SET; // Consume assignments - tokens = ucx_list_get(tokens, dav_parse_assignments(stmt, tokens)); + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_assignments(stmt, tokens)); if (stmt->errorcode) { return; } @@ -1700,7 +1743,7 @@ static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { } // Consume path - tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens)); + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens)); if (stmt->errorcode) { return; } @@ -1709,7 +1752,7 @@ static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "with")) { tokens = tokens->next; - tokens = ucx_list_get(tokens, + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_with_clause(stmt, tokens)); } if (stmt->errorcode) { @@ -1720,7 +1763,7 @@ static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "where")) { tokens = tokens->next; - tokens = ucx_list_get(tokens, + tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_where_clause(stmt, tokens)); } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(tokens, "anywhere")) { @@ -1734,7 +1777,7 @@ static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) { } } -DavQLStatement* dav_parse_statement(sstr_t srctext) { +DavQLStatement* dav_parse_statement(cxstring srctext) { DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement)); // if we can't even get enough memory for the statement object or an error @@ -1753,11 +1796,11 @@ DavQLStatement* dav_parse_statement(sstr_t srctext) { stmt->depth = 1; // save trimmed source text - stmt->srctext = sstrtrim(srctext); + stmt->srctext = cx_strtrim(srctext); if (stmt->srctext.length) { // tokenization - UcxList* tokens = dav_parse_tokenize(stmt->srctext); + DavQLToken* tokens = dav_parse_tokenize(stmt->srctext); if (tokens) { // use first token to determine query type @@ -1773,10 +1816,7 @@ DavQLStatement* dav_parse_statement(sstr_t srctext) { } // free token data - UCX_FOREACH(token, tokens) { - free(token->data); - } - ucx_list_free(tokens); + tokenlist_free(tokens); } else { stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; } @@ -1797,10 +1837,10 @@ DavQLStatement* dav_parse_statement(sstr_t srctext) { } void dav_free_statement(DavQLStatement *stmt) { - UCX_FOREACH(expr, stmt->fields) { - dav_free_field(expr->data); + if(stmt->fields) { + cxDefineDestructor(stmt->fields, dav_free_field); + cxListFree(stmt->fields); } - ucx_list_free(stmt->fields); if (stmt->where) { dav_free_expression(stmt->where); @@ -1808,10 +1848,13 @@ void dav_free_statement(DavQLStatement *stmt) { if (stmt->errormessage) { free(stmt->errormessage); } - UCX_FOREACH(crit, stmt->orderby) { - dav_free_order_criterion(crit->data); + + if(stmt->orderby) { + cxDefineDestructor(stmt->orderby, dav_free_order_criterion); + cxListFree(stmt->orderby); + } + if(stmt->args) { + cxListFree(stmt->args); } - ucx_list_free(stmt->orderby); - ucx_list_free(stmt->args); free(stmt); } diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h index fa52910..7640b26 100644 --- a/libidav/davqlparser.h +++ b/libidav/davqlparser.h @@ -34,8 +34,8 @@ extern "C" { #endif #include -#include "ucx/string.h" -#include "ucx/list.h" +#include +#include /** * Enumeration of possible statement types. @@ -74,10 +74,13 @@ typedef enum { DAVQL_LIKE, DAVQL_UNLIKE // comparisons } davqloperator_t; -typedef struct { +typedef struct DavQLToken DavQLToken; +struct DavQLToken { davqltokenclass_t tokenclass; - sstr_t value; -} DavQLToken; + cxstring value; + DavQLToken *prev; + DavQLToken *next; +}; /** * An expression within a DAVQL query. @@ -92,7 +95,7 @@ struct _davqlexpr { * The original expression text. * Contains the literal value, if type is LITERAL. */ - sstr_t srctext; + cxstring srctext; /** * The expression type. */ @@ -139,7 +142,7 @@ typedef struct { *
  • SET: the identifier
  • * */ - sstr_t name; + cxstring name; /** * The field expression. *
      @@ -242,7 +245,7 @@ typedef struct { /** * The original query text. */ - sstr_t srctext; + cxstring srctext; /** * The statement type. */ @@ -258,11 +261,11 @@ typedef struct { /** * The list of DavQLFields. */ - UcxList* fields; + CxList* fields; /** * A string that denotes the queried path. */ - sstr_t path; + cxstring path; /** * Logical expression for selection. * NULL, if there is no where clause. @@ -273,7 +276,7 @@ typedef struct { * This is NULL for SET queries and may be NULL * if the result doesn't need to be sorted. */ - UcxList* orderby; + CxList* orderby; /** * The recursion depth for the statement. * Defaults to 1. @@ -284,7 +287,7 @@ typedef struct { /** * A list of all required arguments */ - UcxList* args; + CxList* args; } DavQLStatement; /** Infinity recursion depth for a DavQLStatement. */ @@ -350,12 +353,12 @@ void dav_debug_statement(DavQLStatement *stmt); * @param stmt the sstr_t containing the statement * @return a DavQLStatement object */ -DavQLStatement* dav_parse_statement(sstr_t stmt); +DavQLStatement* dav_parse_statement(cxstring stmt); /** * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement. */ -#define dav_parse_cstr_statement(stmt) dav_parse_statement(S(stmt)) +#define dav_parse_cstr_statement(stmt) dav_parse_statement(cx_str(stmt)) /** * Frees a DavQLStatement. diff --git a/libidav/methods.c b/libidav/methods.c index e54da5d..a4fe63d 100644 --- a/libidav/methods.c +++ b/libidav/methods.c @@ -36,21 +36,23 @@ #include "session.h" #include "xml.h" -#include +#include +#include +#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; +int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin) { + return cxBufferSeek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK; } /* ----------------------------- PROPFIND ----------------------------- */ CURLcode do_propfind_request( DavSession *sn, - UcxBuffer *request, - UcxBuffer *response) + CxBuffer *request, + CxBuffer *response) { CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND"); @@ -64,14 +66,16 @@ CURLcode do_propfind_request( 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_READFUNCTION, cxBufferRead); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek); + curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_SEEKDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); - UcxMap *respheaders = ucx_map_new(32); + CxMap *respheaders = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); + cxDefineDestructor(respheaders, free); util_capture_header(handle, respheaders); for(int i=0;i the server handled our request and we can stop requesting */ char *msdavexterror; - msdavexterror = ucx_map_cstr_get(respheaders, "x-msdavext_error"); + msdavexterror = cxMapGet(respheaders, cx_hash_key_str("x-msdavext_error")); int iishack = depth == 1 && msdavexterror && !strncmp(msdavexterror, "589831;", 7); @@ -113,71 +117,72 @@ CURLcode do_propfind_request( // deactivate header capturing and free captured map util_capture_header(handle, NULL); - ucx_map_free_content(respheaders, free); - ucx_map_free(respheaders); + cxMapFree(respheaders); return ret; } -UcxBuffer* create_allprop_propfind_request(void) { - UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOFREE); - sstr_t s; +CxBuffer* create_allprop_propfind_request(void) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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; +CxBuffer* create_cryptoprop_propfind_request(void) { + CxBuffer *buf = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; - s = SC("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = SC("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = SC("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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; +CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; int add_crypto_name = 1; int add_crypto_key = 1; int add_crypto_hash = 1; char *crypto_ns = "idav"; - 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; + CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); + if(properties) { + CxIterator i = cxListIterator(properties); + cx_foreach(DavProperty*, p, i) { + if(strcmp(p->ns->name, "DAV:")) { + cxMapPut(namespaces, cx_hash_key_str(p->ns->prefix), p->ns); + } + + // if the properties list contains the idav properties crypto-name + // and crypto-key, mark them as existent + if(!strcmp(p->ns->name, DAV_NS)) { + if(!strcmp(p->name, "crypto-name")) { + add_crypto_name = 0; + crypto_ns = p->ns->prefix; + } else if(!strcmp(p->name, "crypto-key")) { + add_crypto_key = 0; + crypto_ns = p->ns->prefix; + } else if(!strcmp(p->name, "crypto-hash")) { + add_crypto_hash = 0; + crypto_ns = p->ns->prefix; + } } } } @@ -186,126 +191,126 @@ UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *ro 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); + cxMapPut(namespaces, cx_hash_key_str("idav"), &idav_ns); } - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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); + cx_bprintf(buf, "prefix); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("=\""); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(ns->name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\""); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = S(">\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR(">\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); // default properties - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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); + cxBufferPut(buf, '<'); + cxBufferPutString(buf, crypto_ns); + s = CX_STR(":crypto-name />\n"); + cxBufferWrite(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); + cxBufferPut(buf, '<'); + cxBufferPutString(buf, crypto_ns); + s = CX_STR(":crypto-key />\n"); + cxBufferWrite(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); + cxBufferPut(buf, '<'); + cxBufferPutString(buf, crypto_ns); + s = CX_STR(":crypto-hash />\n"); + cxBufferWrite(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); + if(properties) { + CxIterator i = cxListIterator(properties); + cx_foreach(DavProperty*, prop, i) { + s = CX_STR("<"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(prop->ns->prefix); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(":"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(prop->name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(" />\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } } // end - ucx_bprintf(buf, "\n\n", rootelm); + cx_bprintf(buf, "\n\n", rootelm); - ucx_map_free(namespaces); + cxMapFree(namespaces); return buf; } -UcxBuffer* create_basic_propfind_request(void) { - UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); - sstr_t s; +CxBuffer* create_basic_propfind_request(void) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); // end - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); return buf; } -PropfindParser* create_propfind_parser(UcxBuffer *response, char *url) { +PropfindParser* create_propfind_parser(CxBuffer *response, char *url) { PropfindParser *parser = malloc(sizeof(PropfindParser)); if(!parser) { return NULL; @@ -349,11 +354,10 @@ int get_propfind_response(PropfindParser *parser, ResponseTag *result) { 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; + result->properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list xmlNode *node = parser->current->children; while(node) { @@ -380,13 +384,13 @@ int get_propfind_response(PropfindParser *parser, ResponseTag *result) { // error return -1; } - sstr_t status_str = sstr((char*)status_node->content); + cxstring status_str = cx_str((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"))) { + status_str = cx_strsubsl(status_str, 9, 3); + if(!cx_strcmp(status_str, CX_STR("200"))) { ok = 1; } } @@ -398,7 +402,7 @@ int get_propfind_response(PropfindParser *parser, ResponseTag *result) { n = prop_node->children; while(n) { if(n->type == XML_ELEMENT_NODE) { - properties = ucx_list_append(properties, n); + cxListAdd(result->properties, n); if(xstreq(n->name, "resourcetype")) { if(parse_resource_type(n)) { iscollection = TRUE; @@ -421,7 +425,6 @@ int get_propfind_response(PropfindParser *parser, ResponseTag *result) { result->href = util_url_path(href); result->iscollection = iscollection; - result->properties = properties; result->crypto_name = crypto_name; result->crypto_key = crypto_key; @@ -442,27 +445,27 @@ int get_propfind_response(PropfindParser *parser, ResponseTag *result) { void cleanup_response(ResponseTag *result) { if(result) { - ucx_list_free(result->properties); + cxListFree(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 hrefeq(DavSession *sn, const char *href1, const char *href2) { + cxmutstr href_s = cx_mutstr(util_url_decode(sn, href1)); + cxmutstr href_r = cx_mutstr(util_url_decode(sn, href2)); int ret = 0; - if(!sstrcmp(href_s, href_r)) { + if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) { ret = 1; } else if(href_s.length == href_r.length + 1) { if(href_s.ptr[href_s.length-1] == '/') { href_s.length--; - if(!sstrcmp(href_s, href_r)) { + if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) { ret = 1; } } } else if(href_r.length == href_s.length + 1) { if(href_r.ptr[href_r.length-1] == '/') { href_r.length--; - if(!sstrcmp(href_s, href_r)) { + if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) { ret = 1; } } @@ -475,7 +478,7 @@ int hrefeq(DavSession *sn, char *href1, char *href2) { } -DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response) { +DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response) { char *url = NULL; curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url); if(!root) { @@ -523,7 +526,7 @@ DavResource* response2resource(DavSession *sn, ResponseTag *response, char *pare return NULL; } } else { - sstr_t resname = sstr(util_resource_name(response->href)); + cxstring resname = cx_str(util_resource_name(response->href)); int nlen = 0; char *uname = curl_easy_unescape( sn->handle, @@ -555,19 +558,21 @@ void add_properties(DavResource *res, ResponseTag *response) { 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(response->properties) { + CxIterator i = cxListIterator(response->properties); + cx_foreach(xmlNode*, prop, i) { + resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children); - if (decrypt_props && - prop->children && - prop->children->type == XML_TEXT_NODE && - xstreq(prop->ns->href, DAV_NS)) - { - if(xstreq(prop->name, "crypto-prop")) { - crypto_prop = prop; - } else if(xstreq(prop->name, "crypto-key")) { - crypto_key = util_xml_get_text(prop); + if (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); + } } } } @@ -576,7 +581,7 @@ void add_properties(DavResource *res, ResponseTag *response) { 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); + CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content); resource_set_crypto_properties(res, cprops); } } @@ -589,8 +594,8 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { //DavResource *res = resource; DavResource *res = NULL; - char *href = NULL; - UcxList *properties = NULL; // xmlNode list + const char *href = NULL; + CxList *properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list char *crypto_name = NULL; // name set by crypto-name property char *crypto_key = NULL; @@ -607,7 +612,7 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { return 1; } //char *href = (char*)href_node->content; - href = util_url_path((char*)href_node->content); + href = util_url_path((const char*)href_node->content); char *href_s = util_url_decode(resource->session, href); char *href_r = util_url_decode(resource->session, resource->href); @@ -633,13 +638,13 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { sn->error = DAV_ERROR; return 1; } - sstr_t status_str = sstr((char*)status_node->content); + cxstring status_str = cx_str((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"))) { + status_str = cx_strsubsl(status_str, 9, 3); + if(!cx_strcmp(status_str, CX_STR("200"))) { ok = 1; } } @@ -651,12 +656,12 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { n = prop_node->children; while(n) { if(n->type == XML_ELEMENT_NODE) { - properties = ucx_list_append(properties, n); + cxListAdd(properties, n); if(xstreq(n->name, "resourcetype")) { if(parse_resource_type(n)) { iscollection = TRUE; } - } else if(xstreq(n->ns->href, DAV_NS)) { + } else if(n->ns && xstreq(n->ns->href, DAV_NS)) { if(xstreq(n->name, "crypto-name")) { crypto_name = util_xml_get_text(n); } else if(xstreq(n->name, "crypto-key")) { @@ -689,7 +694,7 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { return -1; } } else { - sstr_t resname = sstr(util_resource_name(href)); + cxstring resname = cx_str(util_resource_name(href)); int nlen = 0; char *uname = curl_easy_unescape( sn->handle, @@ -700,8 +705,8 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { curl_free(uname); } - href = dav_session_strdup(sn, href); - res = dav_resource_new_full(sn, resource->path, name, href); + char *href_cp = dav_session_strdup(sn, href); + res = dav_resource_new_full(sn, resource->path, name, href_cp); dav_session_free(sn, name); } @@ -711,8 +716,11 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session); xmlNode *crypto_prop = NULL; - UCX_FOREACH(elm, properties) { - xmlNode *prop = elm->data; + CxIterator i = cxListIterator(properties); + cx_foreach(xmlNode*, prop, i) { + if(!prop->ns) { + continue; + } resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children); if (decrypt_props && @@ -725,13 +733,13 @@ int parse_response_tag(DavResource *resource, xmlNode *node) { } } } - ucx_list_free(properties); + cxListFree(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); + CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content); resource_set_crypto_properties(res, cprops); } } @@ -781,8 +789,8 @@ int parse_resource_type(xmlNode *node) { CURLcode do_proppatch_request( DavSession *sn, char *lock, - UcxBuffer *request, - UcxBuffer *response) + CxBuffer *request, + CxBuffer *response) { CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH"); @@ -792,7 +800,7 @@ CURLcode do_proppatch_request( if(lock) { char *url = NULL; curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url); - char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr; + char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr; headers = curl_slist_append(headers, ltheader); free(ltheader); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); @@ -800,15 +808,16 @@ CURLcode do_proppatch_request( 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_READFUNCTION, cxBufferRead); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek); + curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_SEEKDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); - ucx_buffer_seek(request, 0, SEEK_SET); + cxBufferSeek(request, 0, SEEK_SET); CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL); curl_slist_free_all(headers); @@ -817,168 +826,173 @@ CURLcode do_proppatch_request( 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)); +CxBuffer* create_proppatch_request(DavResourceData *data) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; + + CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); + cxDefineDestructor(namespaces, free); + + { + char prefix[8]; + int pfxnum = 0; + if (data->set && cxListSize(data->set) > 0) { + CxIterator i = cxListIterator(data->set); + cx_foreach(DavProperty*, p, i) { + if (strcmp(p->ns->name, "DAV:")) { + snprintf(prefix, 8, "x%d", pfxnum++); + cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix)); + } + } } - } - 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)); + if (data->remove && cxListSize(data->remove) > 0) { + CxIterator i = cxListIterator(data->remove); + cx_foreach(DavProperty*, p, i) { + if (strcmp(p->ns->name, "DAV:")) { + snprintf(prefix, 8, "x%d", pfxnum++); + cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix)); + } + } } } - s = SC("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); // write root element and namespaces - s = SC("value); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("=\""); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_strn(entry->key->data, entry->key->len); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\""); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = SC(">\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR(">\n"); + cxBufferWrite(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); + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + CxIterator i = cxListIterator(data->set); + cx_foreach(DavProperty*, property, i) { + char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name)); if(!prefix) { prefix = "D"; } // begin tag - s = 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); + s = CX_STR("<"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(prefix); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(":"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(property->name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(">"); + cxBufferWrite(s.ptr, 1, s.length, buf); // content DavXmlNode *content = property->value; if(content->type == DAV_XML_TEXT && !content->next) { - ucx_buffer_write(content->content, 1, content->contentlength, buf); + cxBufferWrite(content->content, 1, content->contentlength, buf); } else { - dav_print_node(buf, (write_func)ucx_buffer_write, namespaces, content); + dav_print_node(buf, (cx_write_func)cxBufferWrite, 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 = CX_STR("name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(">\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = SC("\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n"); + cxBufferWrite(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 = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + CxIterator i = cxListIterator(data->remove); + cx_foreach(DavProperty*, property, i) { + char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name)); - s = 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 = CX_STR("<"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(prefix); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(":"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(property->name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(" />\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = SC("\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = SC("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); // cleanup namespace map - ucx_map_free_content(namespaces, free); - ucx_map_free(namespaces); + cxMapFree(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; +CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); if(DAV_ENCRYPT_NAME(sn)) { - s = S(""); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); char *crname = aes_encrypt(name, strlen(name), key); - ucx_buffer_puts(buf, crname); + cxBufferPutString(buf, crname); free(crname); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(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); + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, key->name); + s = CX_STR("\n"); + cxBufferWrite(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 = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, hash); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); } - s = S("\n\n\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); return buf; } @@ -1003,10 +1017,10 @@ CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, char *ltheader = NULL; if(create) { url = util_parent_path(url); - ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr; + ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr; free(url); } else { - ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr; + ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr; } headers = curl_slist_append(headers, ltheader); free(ltheader); @@ -1014,16 +1028,16 @@ CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, } curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); - UcxBuffer *buf = NULL; + CxBuffer *buf = NULL; if(!read_func) { - buf = ucx_buffer_new(data, length, 0); + buf = cxBufferCreate(data, length, cxDefaultAllocator, 0); buf->size = length; data = buf; - read_func = (dav_read_func)ucx_buffer_read; + read_func = (dav_read_func)cxBufferRead; curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length); } else if(length == 0) { headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); - curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)1); + curl_easy_setopt(handle, CURLOPT_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); @@ -1031,6 +1045,7 @@ CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func); curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, seek_func); + curl_easy_setopt(handle, CURLOPT_SEEKDATA, data); curl_easy_setopt(handle, CURLOPT_READDATA, data); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); @@ -1039,19 +1054,19 @@ CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, CURLcode ret = dav_session_curl_perform(sn, NULL); curl_slist_free_all(headers); if(buf) { - ucx_buffer_free(buf); + cxBufferFree(buf); } return ret; } -CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response) { +CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response) { CURL *handle = sn->handle; struct curl_slist *headers = NULL; if(lock) { char *url = NULL; curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url); - char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr; + char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr; headers = curl_slist_append(headers, ltheader); free(ltheader); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); @@ -1062,7 +1077,7 @@ CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response) { 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_WRITEFUNCTION, cxBufferWrite); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); CURLcode ret = dav_session_curl_perform(sn, NULL); @@ -1077,7 +1092,7 @@ CURLcode do_mkcol_request(DavSession *sn, char *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; + char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr; free(url); headers = curl_slist_append(headers, ltheader); free(ltheader); @@ -1135,12 +1150,12 @@ CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool co if(lock) { char *url = NULL; curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url); - char *ltheader = ucx_sprintf("If: <%s> (<%s>)", url, lock).ptr; + char *ltheader = cx_asprintf("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)); + //cxstring deststr = ucx_sprintf("Destination: %s", dest); + cxmutstr deststr = cx_strcat(2, CX_STR("Destination: "), cx_str(dest)); headers = curl_slist_append(headers, deststr.ptr); if(override) { headers = curl_slist_append(headers, "Overwrite: T"); @@ -1158,26 +1173,26 @@ CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool co } -UcxBuffer* create_lock_request(void) { - UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); - sstr_t s; +CxBuffer* create_lock_request(void) { + CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxstring s; - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n" + s = CX_STR("\n" "\n" "\n" "http://davutils.org/libidav/\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + cxBufferWrite(s.ptr, 1, s.length, buf); - s = S("\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); return buf; } -int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock) { +int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock) { lock->locktoken = NULL; lock->timeout = NULL; @@ -1237,7 +1252,7 @@ int parse_lock_response(DavSession *sn, UcxBuffer *response, LockDiscovery *lock return ret; } -CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout) { +CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout) { CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK"); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); @@ -1247,11 +1262,11 @@ CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response struct curl_slist *headers = NULL; if(timeout != 0) { - sstr_t thdr; + cxmutstr thdr; if(timeout < 0) { - thdr = ucx_sprintf("%s", "Timeout: Infinite"); + thdr = cx_asprintf("%s", "Timeout: Infinite"); } else { - thdr = ucx_sprintf("Timeout: Second-%u", (unsigned int)timeout); + thdr = cx_asprintf("Timeout: Second-%u", (unsigned int)timeout); } headers = curl_slist_append(headers, thdr.ptr); free(thdr.ptr); @@ -1259,12 +1274,13 @@ CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response 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_READFUNCTION, cxBufferRead); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek); curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_SEEKDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL); @@ -1285,7 +1301,7 @@ CURLcode do_unlock_request(DavSession *sn, char *locktoken) { curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); // set lock-token header - sstr_t ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken); + cxmutstr ltheader = cx_asprintf("Lock-Token: <%s>", locktoken); struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); @@ -1305,10 +1321,10 @@ CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) { curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); // set lock-token header - sstr_t ltheader; + cxmutstr ltheader; struct curl_slist *headers = NULL; if(locktoken) { - ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken); + ltheader = cx_asprintf("Lock-Token: <%s>", locktoken); headers = curl_slist_append(NULL, ltheader.ptr); } curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); @@ -1323,17 +1339,18 @@ CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) { } -CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response) { +CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response) { CURL *handle = sn->handle; curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT"); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); - curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read); - curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, dav_buffer_seek); - curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek); + curl_easy_setopt(handle, CURLOPT_READDATA, request); + curl_easy_setopt(handle, CURLOPT_SEEKDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); struct curl_slist *headers = NULL; diff --git a/libidav/methods.h b/libidav/methods.h index 660e738..399ce1c 100644 --- a/libidav/methods.h +++ b/libidav/methods.h @@ -32,7 +32,7 @@ #include "webdav.h" #include "resource.h" -#include +#include #ifdef __cplusplus extern "C" { @@ -48,11 +48,11 @@ struct PropfindParser { }; struct ResponseTag { - char *href; - int iscollection; - UcxList *properties; - char *crypto_name; - char *crypto_key; + const char *href; + int iscollection; + CxList *properties; + const char *crypto_name; + const char *crypto_key; }; struct LockDiscovery { @@ -60,18 +60,18 @@ struct LockDiscovery { char *locktoken; }; -int dav_buffer_seek(UcxBuffer *b, curl_off_t offset, int origin); +int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin); CURLcode do_propfind_request( DavSession *sn, - UcxBuffer *request, - UcxBuffer *response); + CxBuffer *request, + CxBuffer *response); CURLcode do_proppatch_request( DavSession *sn, char *lock, - UcxBuffer *request, - UcxBuffer *response); + CxBuffer *request, + CxBuffer *response); CURLcode do_put_request( DavSession *sn, @@ -82,21 +82,21 @@ CURLcode do_put_request( 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); +CxBuffer* create_allprop_propfind_request(void); +CxBuffer* create_cryptoprop_propfind_request(void); +CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt); +CxBuffer* create_basic_propfind_request(void); -PropfindParser* create_propfind_parser(UcxBuffer *response, char *url); +PropfindParser* create_propfind_parser(CxBuffer *response, char *url); void destroy_propfind_parser(PropfindParser *parser); int get_propfind_response(PropfindParser *parser, ResponseTag *result); void cleanup_response(ResponseTag *result); -int hrefeq(DavSession *sn, char *href1, char *href2); +int hrefeq(DavSession *sn, const char *href1, const char *href2); DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path); void add_properties(DavResource *res, ResponseTag *response); -DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response); +DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response); int parse_response_tag(DavResource *resource, xmlNode *node); void set_davprops(DavResource *res); @@ -106,10 +106,10 @@ void set_davprops(DavResource *res); */ 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); +CxBuffer* create_proppatch_request(DavResourceData *data); +CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash); -CURLcode do_delete_request(DavSession *sn, char *lock, UcxBuffer *response); +CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response); CURLcode do_mkcol_request(DavSession *sn, char *lock); @@ -117,14 +117,14 @@ 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); +CxBuffer* create_lock_request(void); +int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock); +CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout); CURLcode do_unlock_request(DavSession *sn, char *locktoken); CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken); -CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response); +CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response); #ifdef __cplusplus } diff --git a/libidav/pwdstore.c b/libidav/pwdstore.c new file mode 100644 index 0000000..5832ddb --- /dev/null +++ b/libidav/pwdstore.c @@ -0,0 +1,587 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pwdstore.h" + +#include "utils.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#endif + + +static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput; +static void *pw_input_data = "Master password: "; + +char * pwdstore_default_pwinput(char *prompt) { + return util_password_input(prompt); +} + +void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata) { + pw_input = func; + pw_input_data = userdata; +} + +PwdStore* pwdstore_open(const char *file) { + FILE *in = fopen(file, "r"); + if(!in) { + return NULL; + } + + CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); + fclose(in); + + if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) { + cxBufferFree(buf); + return NULL; + } + + PwdStore *p = malloc(sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = buf; + p->key = NULL; + p->unlock_cmd = NULL; + p->lock_cmd = NULL; + p->encoffset = PWDS_HEADER_SIZE; + p->isdecrypted = 0; + + if(pwdstore_getindex(p)) { + pwdstore_free(p); + return NULL; + } + + return p; +} + +PwdStore* pwdstore_new(void) { + PwdStore *p = calloc(1, sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + PWDS_MAGIC(p) = PWDS_MAGIC_CHAR; + PWDS_VERSION(p) = 1; + PWDS_ENC(p) = DAV_KEY_AES256; + PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256; + dav_rand_bytes((unsigned char*)p->content->space+4, 16); + p->isdecrypted = 1; + p->encoffset = PWDS_HEADER_SIZE; + return p; +} + +PwdStore* pwdstore_clone(PwdStore *p) { + CxBuffer *newbuffer = calloc(1, sizeof(CxBuffer)); + *newbuffer = *p->content; + newbuffer->space = malloc(p->content->capacity); + memcpy(newbuffer->space, p->content->space, p->content->capacity); + + DavKey *key = NULL; + if(p->key) { + key = malloc(sizeof(DavKey)); + key->data = malloc(p->key->length); + memcpy(key->data, p->key->data, p->key->length); + key->length = p->key->length; + key->type = p->key->type; + key->name = NULL; + } + + PwdStore *newp = calloc(1, sizeof(PwdStore)); + newp->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + newp->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + newp->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + newp->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + newp->content = newbuffer; + newp->key = key; + newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) : NULL; + newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) : NULL; + newp->encoffset = p->encoffset; + newp->isdecrypted = p->isdecrypted; + + CxIterator i = cxMapIterator(p->ids); + cx_foreach(CxMapEntry *, e, i) { + PwdEntry *entry = e->value; + pwdstore_put(newp, entry->id, entry->user, entry->password); + } + + i = cxMapIterator(p->index); + cx_foreach(CxMapEntry *, e, i) { + PwdIndexEntry *entry = e->value; + CxList *locations = NULL; + if(entry->locations) { + locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + CxIterator li = cxListIterator(entry->locations); + cx_foreach(char *, location, li) { + cxListAdd(locations, strdup(location)); + } + } + pwdstore_put_index(newp, strdup(entry->id), locations); + } + + return newp; +} + +static int readval(CxBuffer *in, char **val, int allowzero) { + // value = length string + // length = uint32 + // string = bytes + + *val = NULL; + + // get length + uint32_t length = 0; + if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) { + return 0; + } + length = ntohl(length); // convert from BE to host byte order + if(length == 0) { + if(allowzero) { + return 1; + } else { + return 0; + } + } + if(length > PWDSTORE_MAX_LEN) { + return 0; + } + + // get value + char *value = malloc(length + 1); + value[length] = 0; + if(cxBufferRead(value, 1, length, in) != length) { + free(value); + return 0; + } + + *val = value; + return 1; +} + +static int read_indexentry(PwdStore *p, CxBuffer *in) { + // read type of index element + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + cxDefineDestructor(locations, free); + + // get id (required) + int ret = 0; + if(readval(in, &id, FALSE)) { + // get locations + char *location = NULL; + while((ret = readval(in, &location, TRUE)) == 1) { + if(!location) { + break; + } + cxListAdd(locations, location); + } + } + + if(ret) { + pwdstore_put_index(p, id, locations); + if(cxListSize(locations) == 0) { + cxListFree(locations); + } + } else { + if(id) free(id); + cxListFree(locations); + } + + return ret; +} + +static int read_pwdentry(PwdStore *p, CxBuffer *in) { + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + char *user = NULL; + char *password = NULL; + + int ret = 0; + if(readval(in, &id, FALSE)) { + if(readval(in, &user, FALSE)) { + if(readval(in, &password, FALSE)) { + pwdstore_put(p, id, user, password); + ret = 1; + } + } + } + + if(id) free(id); + if(user) free(user); + if(password) free(password); + + return ret; +} + +static void remove_list_entries(PwdStore *s, const char *id) { + CxIterator i = cxListMutIterator(s->locations); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + cxIteratorNext(i); + break; + } + } + i = cxListMutIterator(s->noloc); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + cxIteratorNext(i); + break; + } + } +} + +void pwdstore_remove_entry(PwdStore *s, const char *id) { + remove_list_entries(s, id); + + CxHashKey key = cx_hash_key_str(id); + PwdIndexEntry *i = NULL; + cxMapRemoveAndGet(s->index, key, &i); + PwdEntry *e = NULL; + cxMapRemoveAndGet(s->ids, key, &e); + + if(i) { + if(i->locations) { + cxListFree(i->locations); + } + free(i->id); + free(i); + } + if(e) { + free(e->id); + free(e->user); + free(e->password); + free(e); + } +} + +int pwdstore_getindex(PwdStore *s) { + uint32_t netindexlen; + + // set the position to the last 4 bytes of the header + // for reading index length + s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); + + // read indexlen and convert to host byte order + if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) { + return 1; + } + uint32_t indexlen = ntohl(netindexlen); + + // integer overflow check + if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) { + return 1; + } + if(s->content->size < PWDS_HEADER_SIZE + indexlen) { + return 1; + } + // encrypted content starts after the index content + s->encoffset = PWDS_HEADER_SIZE + indexlen; + + // the index starts after the header + CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0); + index->size = indexlen; + + // read index + while(read_indexentry(s, index)) {} + + // free index buffer structure (not the content) + cxBufferFree(index); + + return 0; +} + +int pwdstore_decrypt(PwdStore *p) { + if(!p->key) { + return 1; + } + if(p->isdecrypted) { + return 0; + } + + // decrypt contet + size_t encsz = p->content->size - p->encoffset; + CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0); + enc->size = encsz; + enc->size = p->content->size - p->encoffset; + CxBuffer *content = aes_decrypt_buffer(enc, p->key); + cxBufferFree(enc); + if(!content) { + return 1; + } + + while(read_pwdentry(p, content)) {} + + cxBufferFree(content); + + p->isdecrypted = 1; + + return 0; +} + +int pwdstore_setpassword(PwdStore *p, const char *password) { + DavKey *key = dav_pw2key( + password, + (unsigned char*)(p->content->space + 4), + 16, + PWDS_PWFUNC(p), + PWDS_ENC(p)); + if(!key) { + return 1; + } + + p->key = key; + return 0; +} + +void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) { + PWDS_ENC(p) = enc; + PWDS_PWFUNC(p) = pwfunc; +} + +void pwdstore_free_entry(PwdEntry *e) { + if(e->id) free(e->id); + if(e->user) free(e->user); + if(e->password) free(e->password); + free(e); +} + +void pwdstore_free(PwdStore* p) { + cxDefineDestructor(p->ids, pwdstore_free_entry); + cxMapFree(p->ids); + + cxListFree(p->locations); + + if(p->content) { + cxBufferFree(p->content); + } + + free(p); +} + +int pwdstore_has_id(PwdStore *s, const char *id) { + return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0; +} + +PwdEntry* pwdstore_get(PwdStore *p, const char *id) { + PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id)); + if(e && e->user && e->password) { + return e; + } else { + return NULL; + } +} + +void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) { + PwdEntry *entry = malloc(sizeof(PwdEntry)); + entry->id = strdup(id); + entry->user = strdup(username); + entry->password = strdup(password); + cxMapPut(p->ids, cx_hash_key_str(id), entry); +} + +void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) { + PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id)); + if(e) { + return; + } + PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry)); + newentry->id = id; + if(locations && cxListSize(locations) > 0) { + newentry->locations = locations; + cxListAdd(p->locations, newentry); + } else { + newentry->locations = NULL; + cxListAdd(p->noloc, newentry); + } + cxMapPut(p->index, cx_hash_key_str(id), newentry); +} + +void write_index_entry(CxBuffer *out, PwdIndexEntry *e) { + uint32_t idlen = strlen(e->id); + uint32_t netidlen = htonl(idlen); + + cxBufferPut(out, 0); // type + + cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out); + cxBufferWrite(e->id, 1, idlen, out); + + if(e->locations) { + CxIterator i = cxListIterator(e->locations); + cx_foreach(char *, location, i) { + uint32_t locationlen = strlen(location); + uint32_t netlocationlen = htonl(locationlen); + + cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out); + cxBufferWrite(location, 1, locationlen, out); + } + } + + uint32_t terminate = 0; + cxBufferWrite(&terminate, 1, sizeof(uint32_t), out); +} + +int pwdstore_store(PwdStore *p, const char *file) { + if(!p->key) { + return 1; + } + + CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + + // create index + CxIterator i = cxListIterator(p->noloc); + cx_foreach(PwdIndexEntry*, e, i) { + write_index_entry(index, e); + } + i = cxListIterator(p->locations); + cx_foreach(PwdIndexEntry*, e, i) { + write_index_entry(index, e); + } + + i = cxMapIteratorValues(p->ids); + cx_foreach(PwdEntry*, value, i) { + if(!value->id || !value->user || !value->password) { + continue; + } + + uint32_t idlen = strlen(value->id); + uint32_t ulen = strlen(value->user); + uint32_t plen = strlen(value->password); + uint32_t netidlen = htonl(idlen); + uint32_t netulen = htonl(ulen); + uint32_t netplen = htonl(plen); + + // content buffer + cxBufferPut(content, 0); // type + + cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->id, 1, idlen, content); + cxBufferWrite(&netulen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->user, 1, ulen, content); + cxBufferWrite(&netplen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->password, 1, plen, content); + } + + content->pos = 0; + CxBuffer *enc = aes_encrypt_buffer(content, p->key); + + p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); + p->content->size = PWDS_HEADER_SIZE; + + // add index after header + uint32_t netindexlen = htonl((uint32_t)index->size); + cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content); + cxBufferWrite(index->space, 1, index->size, p->content); + + // add encrypted buffer + cxBufferWrite(enc->space, 1, enc->size, p->content); + + cxBufferFree(enc); + + FILE *out = fopen(file, "w"); + if(!out) { + return 1; + } + fwrite(p->content->space, 1, p->content->size, out); + fclose(out); + + return 0; +} + +int pwdstore_decrypt_secrets(PwdStore *secrets) { + if(!pw_input) { + return 1; + } + + char *ps_password = NULL; + if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) { + CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + if(!util_exec_command(secrets->unlock_cmd, cmd_out)) { + // command successful, get first line from output without newline + // and use that as password for the secretstore + size_t len = 0; + for(size_t i=0;i<=cmd_out->size;i++) { + if(i == cmd_out->size || cmd_out->space[i] == '\n') { + len = i; + break; + } + } + if(len > 0) { + ps_password = malloc(len + 1); + memcpy(ps_password, cmd_out->space, len); + ps_password[len] = 0; + } + } + cxBufferFree(cmd_out); + } + + if(!ps_password) { + ps_password = pw_input(pw_input_data); + if(!ps_password) { + return 1; + } + } + + int err = pwdstore_setpassword(secrets, ps_password); + free(ps_password); + if(err) { + fprintf(stderr, "Error: cannot create key from password\n"); + return 1; + } + if(pwdstore_decrypt(secrets)) { + fprintf(stderr, "Error: cannot decrypt secrets store\n"); + return 1; + } + return 0; +} diff --git a/libidav/pwdstore.h b/libidav/pwdstore.h new file mode 100644 index 0000000..d865039 --- /dev/null +++ b/libidav/pwdstore.h @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBIDAV_PWDSTORE_H +#define LIBIDAV_PWDSTORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include +#include "crypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PWDSTORE_MAX_LEN 4096 + +/* + * File Format: + * + * file = header, index, enc_content + * header = magic, version, enc, pwfunc, salt, indexlen + * magic = 1 byte + * version = 1 byte + * enc = 1 byte + * pwfunc = 1 byte + * salt = 16 bytes + * indexlen = uint32 + * index = { itype length id locations zero } + * enc_content = iv bytes + * iv = 16 bytes + * content = { entry } + * entry = itype length id length username length password + * length = uint32 + * zero = 4 zero bytes + * itype = 1 byte + * id = string + * locations = { length string } + * username = string + * password = string + * + * The content is AES encrypted with a key derived from a password + * and the salt. The first 16 bytes are the aes iv. + * + * All integers are big endian + */ + +#define PWDS_HEADER_SIZE 24 + +typedef struct PwdStore PwdStore; +typedef struct PwdEntry PwdEntry; +typedef struct PwdIndexEntry PwdIndexEntry; + +struct PwdStore { + /* + * map of all credentials + * key is the username + * value is PwdEntry* + */ + CxMap *ids; + + /* + * list of all credentials with location + * value is PwdIndexEntry* + */ + CxList *locations; + + /* + * list of all credentials without location + * value is PwdIndexEntry* + */ + CxList *noloc; + + /* + * index map that contains all elements from the lists + * 'locations' and 'noloc' + */ + CxMap *index; + + /* + * a buffer containing the complete file content + */ + CxBuffer *content; + + /* + * key used for encryption/decryption + */ + DavKey *key; + + /* + * optional shell command, that is used for getting the master password + */ + char *unlock_cmd; + + /* + * optional shell command, that is exected when the secretstore is closed + */ + char *lock_cmd; + + /* + * start offset of the encrypted buffer + */ + uint32_t encoffset; + + /* + * indicates if the PwdStore is decrypted with pwdstore_decrypt + */ + uint8_t isdecrypted; +}; + +#define PWDS_MAGIC(p) (p)->content->space[0] +#define PWDS_VERSION(p) (p)->content->space[1] +#define PWDS_ENC(p) (p)->content->space[2] +#define PWDS_PWFUNC(p) (p)->content->space[3] + +#define PWDS_MAGIC_CHAR 'P' + +struct PwdEntry { + char *id; + char *user; + char *password; +}; + +struct PwdIndexEntry { + char *id; + CxList *locations; +}; + +/* + * opens the password store + * the content is still encrypted and must be decrypted using pwdstore_decrypt + */ +PwdStore* pwdstore_open(const char *file); + +PwdStore* pwdstore_new(void); + +PwdStore* pwdstore_clone(PwdStore *p); + +/* + * decrypts the password store with the previously set password + */ +int pwdstore_decrypt(PwdStore *p); + +int pwdstore_setpassword(PwdStore *p, const char *password); + +void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc); + +void pwdstore_free_entry(PwdEntry *e); +void pwdstore_free(PwdStore* p); + +int pwdstore_has_id(PwdStore *s, const char *id); + +PwdEntry* pwdstore_get(PwdStore *p, const char *id); + +void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password); +void pwdstore_put_index(PwdStore *p, char *id, CxList *locations); + +void pwdstore_remove_entry(PwdStore *s, const char *id); + +int pwdstore_store(PwdStore *p, const char *file); + + +int pwdstore_decrypt_secrets(PwdStore *secrets); + + + + + + +typedef char*(*pwdstore_pwinput_func)(void *userdata); + +void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata); + +char * pwdstore_default_pwinput(char *prompt); + + + +/* private */ +int pwdstore_getindex(PwdStore *s); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIDAV_PWDSTORE_H */ + diff --git a/libidav/resource.c b/libidav/resource.c index 8853e44..7808bc3 100644 --- a/libidav/resource.c +++ b/libidav/resource.c @@ -36,8 +36,12 @@ #include "session.h" #include "methods.h" #include "crypto.h" -#include "ucx/buffer.h" -#include "ucx/utils.h" +#include +#include +#include +#include +#include +#include #include "resource.h" #include "xml.h" @@ -45,11 +49,11 @@ #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) -DavResource* dav_resource_new(DavSession *sn, char *path) { +DavResource* dav_resource_new(DavSession *sn, const char *path) { //char *href = util_url_path(url); //DavResource *res = dav_resource_new_href(sn, href); char *parent = util_parent_path(path); - char *name = util_resource_name(path); + const char *name = util_resource_name(path); char *href = dav_session_create_plain_href(sn, path); DavResource *res = dav_resource_new_full(sn, parent, name, href); @@ -57,7 +61,7 @@ DavResource* dav_resource_new(DavSession *sn, char *path) { return res; } -DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *name) { +DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name) { char *path = util_concat_path(parent->path, name); char *href = dav_session_create_plain_href(sn, path); DavResource *res = dav_resource_new_full(sn, parent->path, name, href); @@ -66,8 +70,8 @@ DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, char *n } -DavResource* dav_resource_new_href(DavSession *sn, char *href) { - DavResource *res = ucx_mempool_calloc(sn->mp, 1, sizeof(DavResource)); +DavResource* dav_resource_new_href(DavSession *sn, const char *href) { + DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource)); res->session = sn; // set name, path and href @@ -79,14 +83,14 @@ DavResource* dav_resource_new_href(DavSession *sn, char *href) { return res; } -DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href) { - sstr_t n = sstr(name); +DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href) { + cxstring n = cx_str(name); // the name must not contain path separators if(n.length > 0 && href) { for(int i=0;imp, 1, sizeof(DavResource)); + DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource)); res->session = sn; // set name, path and href - res->name = sstrdup_a(sn->mp->allocator, n).ptr; + res->name = cx_strdup_a(sn->mp->allocator, n).ptr; char *path = util_concat_path(parent_path, name); res->path = dav_session_strdup(sn, path); @@ -112,23 +116,22 @@ DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name // cache href/path if(href) { - dav_session_cache_path(sn, sstr(path), sstr(href)); + dav_session_cache_path(sn, cx_str(path), cx_str(href)); } free(path); return res; } -void resource_free_properties(DavSession *sn, UcxMap *properties) { +void resource_free_properties(DavSession *sn, CxMap *properties) { if(!properties) return; - UcxMapIterator i = ucx_map_iterator(properties); - DavProperty *property; - UCX_MAP_FOREACH(key, property, i) { + CxIterator i = cxMapIteratorValues(properties); + cx_foreach(DavProperty*, property, i) { // TODO: free everything dav_session_free(sn, property); } - ucx_map_free(properties); + cxMapFree(properties); } void dav_resource_free(DavResource *res) { @@ -144,29 +147,62 @@ void dav_resource_free(DavResource *res) { 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); + if(data->set) { + CxIterator i = cxListIterator(data->set); + cx_foreach(DavProperty *, p, i) { + dav_session_free(sn, p->ns->name); + if(p->ns->prefix) { + dav_session_free(sn, p->ns->prefix); + } + dav_session_free(sn, p->ns); + + dav_session_free(sn, p->name); + dav_free_xml_node_sn(sn, p->value); + dav_session_free(sn, p); } - 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); + if(data->remove) { + CxIterator i = cxListIterator(data->remove); + cx_foreach(DavProperty *, p, i) { + dav_session_free(sn, p->ns->name); + if(p->ns->prefix) { + dav_session_free(sn, p->ns->prefix); + } + dav_session_free(sn, p->ns); + + dav_session_free(sn, p->name); + dav_session_free(sn, p); + } + } + + if(data->crypto_set) { + CxIterator i = cxListIterator(data->crypto_set); + cx_foreach(DavProperty *, p, i) { + dav_session_free(sn, p->ns->name); + if(p->ns->prefix) { + dav_session_free(sn, p->ns->prefix); + } + dav_session_free(sn, p->ns); + + dav_session_free(sn, p->name); + dav_free_xml_node_sn(sn, p->value); + dav_session_free(sn, p); + } + } + + if(data->crypto_remove) { + CxIterator i = cxListIterator(data->crypto_remove); + cx_foreach(DavProperty *, p, i) { + dav_session_free(sn, p->ns->name); + if(p->ns->prefix) { + dav_session_free(sn, p->ns->prefix); + } + dav_session_free(sn, p->ns); + + dav_session_free(sn, p->name); + dav_session_free(sn, p); } - dav_session_free(sn, p->ns); - - dav_session_free(sn, p->name); - dav_session_free(sn, p); } if(!data->read && data->content) { @@ -187,20 +223,20 @@ void dav_resource_free_all(DavResource *res) { } } -void resource_set_href(DavResource *res, sstr_t href) { - res->href = sstrdup_a(res->session->mp->allocator, href).ptr; +void resource_set_href(DavResource *res, cxstring href) { + res->href = cx_strdup_a(res->session->mp->allocator, href).ptr; } -void resource_set_info(DavResource *res, char *href_str) { +void resource_set_info(DavResource *res, const char *href_str) { char *url_str = NULL; curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str); - sstr_t name = sstr(util_resource_name(href_str)); - sstr_t href = sstr(href_str); + cxstring name = cx_str(util_resource_name(href_str)); + cxstring href = cx_str(href_str); - sstr_t base_href = sstr(util_url_path(res->session->base_url)); - sstr_t path = sstrsubs(href, base_href.length - 1); + cxstring base_href = cx_str(util_url_path(res->session->base_url)); + cxstring path = cx_strsubs(href, base_href.length - 1); - UcxAllocator *a = res->session->mp->allocator; + const CxAllocator *a = res->session->mp->allocator; CURL *handle = res->session->handle; int nlen = 0; @@ -208,22 +244,22 @@ void resource_set_info(DavResource *res, char *href_str) { 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; + res->name = cx_strdup_a(a, cx_strn(uname, nlen)).ptr; + res->href = cx_strdup_a(a, href).ptr; + res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr; curl_free(uname); curl_free(upath); } DavResourceData* resource_data_new(DavSession *sn) { - DavResourceData *data = ucx_mempool_malloc( - sn->mp, + DavResourceData *data = cxMalloc( + sn->mp->allocator, sizeof(DavResourceData)); if(!data) { return NULL; } - data->properties = ucx_map_new_a(sn->mp->allocator, 32); + data->properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32); data->crypto_properties = NULL; data->set = NULL; data->remove = NULL; @@ -257,9 +293,10 @@ void resource_add_prop(DavResource *res, const char *ns, const char *name, DavXm 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); + cxmutstr keystr = dav_property_key(ns, name); + CxHashKey key = cx_hash_key(keystr.ptr, keystr.length); + cxMapPut(((DavResourceData*)res->data)->properties, key, prop); + free(keystr.ptr); } void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val) { @@ -278,15 +315,15 @@ void resource_add_string_property(DavResource *res, char *ns, char *name, char * resource_add_prop(res, ns, name, dav_text_node(res->session, val)); } -void resource_set_crypto_properties(DavResource *res, UcxMap *cprops) { +void resource_set_crypto_properties(DavResource *res, CxMap *cprops) { DavResourceData *data = res->data; resource_free_properties(res->session, data->crypto_properties); data->crypto_properties = cprops; } DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name) { - sstr_t keystr = dav_property_key(ns, name); - UcxKey key = ucx_key(keystr.ptr, keystr.length); + cxmutstr keystr = dav_property_key(ns, name); + CxHashKey key = cx_hash_key(keystr.ptr, keystr.length); DavXmlNode *ret = resource_get_property_k(res, key); free(keystr.ptr); @@ -294,37 +331,37 @@ DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char * } 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); + cxmutstr keystr = dav_property_key(ns, name); + CxHashKey key = cx_hash_key(keystr.ptr, keystr.length); DavXmlNode *ret = resource_get_encrypted_property_k(res, key); free(keystr.ptr); return ret; } -DavXmlNode* resource_get_property_k(DavResource *res, UcxKey key) { +DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key) { DavResourceData *data = (DavResourceData*)res->data; - DavProperty *property = ucx_map_get(data->properties, key); + DavProperty *property = cxMapGet(data->properties, key); return property ? property->value : NULL; } -DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key) { +DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) { DavResourceData *data = (DavResourceData*)res->data; - DavProperty *property = ucx_map_get(data->crypto_properties, key); + DavProperty *property = cxMapGet(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); +cxmutstr dav_property_key(const char *ns, const char *name) { + return dav_property_key_a(cxDefaultAllocator, 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); +cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name) { + cxstring ns_str = cx_str(ns); + cxstring name_str = cx_str(name); - return sstrcat_a(a, 4, ns_str, S("\0"), name_str, S("\0")); + return cx_strcat_a(a, 4, ns_str, CX_STR("\0"), name_str, CX_STR("\0")); } @@ -411,7 +448,7 @@ static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion return cr->descending ? -ret : ret; } -void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr) { +void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr) { if(!ordercr) { resource_add_child(parent, child); return; @@ -427,8 +464,8 @@ void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList DavResource *resource = parent->children; while(resource) { int r = 0; - UCX_FOREACH(elm, ordercr) { - DavOrderCriterion *cr = elm->data; + CxIterator i = cxListIterator(ordercr); + cx_foreach(DavOrderCriterion*, cr, i) { r = resource_cmp(child, resource, cr); if(r != 0) { break; @@ -495,8 +532,8 @@ static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const ch DavResourceData *data = res->data; DavXmlNode *property = NULL; - UcxList *remove_list = NULL; - UcxList *set_list = NULL; + CxList *remove_list = NULL; + CxList *set_list = NULL; if(encrypted) { // check if crypto_properties because it will only be created @@ -515,23 +552,27 @@ static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const ch // resource_get_property only returns persistent properties // check the remove and set list - if(property) { + if(property && remove_list) { // if the property is in the remove list, we return NULL - UCX_FOREACH(elm, remove_list) { - DavProperty *p = elm->data; + CxIterator i = cxListIterator(remove_list); + cx_foreach(DavProperty*, p, i) { if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) { return NULL; } } } + // the set list contains property updates // we return an updated property if possible - UCX_FOREACH(elm, set_list) { - DavProperty *p = elm->data; - if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) { - return p->value; // TODO: fix + if(set_list) { + CxIterator i = cxListIterator(set_list); + cx_foreach(DavProperty*, p, i) { + if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) { + return p->value; // TODO: fix + } } } + // no property update return property; @@ -565,25 +606,42 @@ static DavProperty* createprop(DavSession *sn, const char *ns, const char *name) return property; } -void dav_set_string_property(DavResource *res, char *name, char *value) { +int dav_set_string_property(DavResource *res, char *name, char *value) { char *pns; char *pname; dav_get_property_namespace_str(res->session->context, name, &pns, &pname); + if(!pns) { + res->session->errorstr = "Property namespace not found"; + return 1; + } dav_set_string_property_ns(res, pns, pname, value); + return 0; +} + +static int add2propertylist(const CxAllocator *a, CxList **list, DavProperty *property) { + if(!*list) { + CxList *newlist = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); + if(!newlist) { + return 1; + } + *list = newlist; + } + cxListAdd(*list, property); + return 0; } void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) { DavSession *sn = res->session; - UcxAllocator *a = res->session->mp->allocator; + const CxAllocator *a = res->session->mp->allocator; DavResourceData *data = res->data; DavProperty *property = createprop(res->session, ns, name); property->value = dav_text_node(res->session, value); if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) { - data->crypto_set = ucx_list_append_a(a, data->crypto_set, property); + add2propertylist(a, &data->crypto_set, property); } else { - data->set = ucx_list_append_a(a, data->set, property); + add2propertylist(a, &data->set, property); } } @@ -596,16 +654,18 @@ void dav_set_property(DavResource *res, char *name, DavXmlNode *value) { void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) { DavSession *sn = res->session; - UcxAllocator *a = sn->mp->allocator; + const CxAllocator *a = sn->mp->allocator; DavResourceData *data = res->data; DavProperty *property = createprop(sn, ns, name); - property->value = value; // TODO: copy node? + // TODO: this function should copy the value + // but we also need a function, that doesn't create a copy + property->value = value; if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) { - data->crypto_set = ucx_list_append_a(a, data->crypto_set, property); + add2propertylist(a, &data->crypto_set, property); } else { - data->set = ucx_list_append_a(a, data->set, property); + add2propertylist(a, &data->set, property); } } @@ -619,44 +679,44 @@ void dav_remove_property(DavResource *res, char *name) { 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; + const CxAllocator *a = res->session->mp->allocator; DavProperty *property = createprop(res->session, ns, name); if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) { - data->crypto_remove = ucx_list_append_a(a, data->crypto_remove, property); + add2propertylist(a, &data->crypto_remove, property); } else { - data->remove = ucx_list_append_a(a, data->remove, property); + add2propertylist(a, &data->remove, property); } } void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) { - UcxAllocator *a = res->session->mp->allocator; + const CxAllocator *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); + add2propertylist(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; + const CxAllocator *a = res->session->mp->allocator; DavResourceData *data = res->data; DavProperty *property = createprop(res->session, ns, name); property->value = dav_text_node(res->session, value); - data->crypto_set = ucx_list_append_a(a, data->crypto_set, property); + add2propertylist(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; + const CxAllocator *a = res->session->mp->allocator; DavProperty *property = createprop(res->session, ns, name); - data->crypto_remove = ucx_list_append_a(a, data->crypto_remove, property); + add2propertylist(a, &data->crypto_remove, property); } static int compare_propname(const void *a, const void *b) { @@ -674,23 +734,18 @@ static int compare_propname(const void *a, const void *b) { DavPropName* dav_get_property_names(DavResource *res, size_t *count) { DavResourceData *data = res->data; - *count = data->properties->count; + *count = cxMapSize(data->properties); 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]; - + CxIterator i = cxMapIteratorValues(data->properties); + cx_foreach(DavProperty*, value, i) { + DavPropName *name = &names[i.index]; name->ns = value->ns->name; name->name = value->name; - - j++; } qsort(names, *count, sizeof(DavPropName), compare_propname); @@ -724,50 +779,39 @@ void dav_set_content_length(DavResource *res, size_t length) { int dav_load(DavResource *res) { - UcxBuffer *rqbuf = create_allprop_propfind_request(); + CxBuffer *rqbuf = create_allprop_propfind_request(); int ret = dav_propfind(res->session, res, rqbuf); - ucx_buffer_free(rqbuf); + cxBufferFree(rqbuf); return ret; } int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) { - UcxMempool *mp = ucx_mempool_new(64); + CxMempool *mp = cxMempoolCreate(64, NULL); + const CxAllocator *a = mp->allocator; - UcxList *proplist = NULL; + CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop); for(size_t i=0;iname = properties[i].name; - p->ns = ucx_mempool_malloc(mp, sizeof(DavNamespace)); - p->ns->name = properties[i].ns; + DavProperty p; + p.name = properties[i].name; + p.ns = cxMalloc(a, sizeof(DavNamespace)); + p.ns->name = properties[i].ns; if(!strcmp(properties[i].ns, "DAV:")) { - p->ns->prefix = "D"; + p.ns->prefix = "D"; } else { - p->ns->prefix = ucx_asprintf(mp->allocator, "x%d", i).ptr; + p.ns->prefix = cx_asprintf_a(a, "x%d", (int)i).ptr; } - p->value = NULL; - proplist = ucx_list_append_a(mp->allocator, proplist, p); + p.value = NULL; + cxListAdd(proplist, &p); } - UcxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0); + CxBuffer *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); + cxBufferFree(rqbuf); + cxMempoolFree(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; @@ -811,11 +855,15 @@ int dav_store(DavResource *res) { // store content if(data->content) { + curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, dav_session_put_progress); + curl_easy_setopt(sn->handle, CURLOPT_XFERINFODATA, res); + curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 0L); + int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key; CURLcode ret; if(encryption) { AESEncrypter *enc = NULL; - UcxBuffer *buf = NULL; + CxBuffer *buf = NULL; if(data->read) { enc = aes_encrypter_new( sn->key, @@ -823,13 +871,13 @@ int dav_store(DavResource *res) { data->read, data->seek); } else { - buf = ucx_buffer_new(data->content, data->length, 0); + buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0); buf->size = data->length; enc = aes_encrypter_new( sn->key, buf, - (dav_read_func)ucx_buffer_read, - (dav_seek_func)dav_buffer_seek); + (dav_read_func)cxBufferRead, + (dav_seek_func)cxBufferSeek); } // put resource @@ -848,28 +896,30 @@ int dav_store(DavResource *res) { aes_encrypter_close(enc); if(buf) { - ucx_buffer_free(buf); + cxBufferFree(buf); } // add crypto properties // TODO: store the properties later if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) { free(enc_hash); + curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL); + curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L); return 1; } resource_add_string_property(res, DAV_NS, "crypto-hash", enc_hash); free(enc_hash); } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) { HashStream hstr; - UcxBuffer *iobuf = NULL; + CxBuffer *iobuf = NULL; if(!data->read) { - iobuf = ucx_buffer_new(data->content, data->length, 0); + iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0); iobuf->size = data->length; init_hash_stream( &hstr, iobuf, - (dav_read_func)ucx_buffer_read, - (dav_seek_func)ucx_buffer_seek); + (dav_read_func)cxBufferRead, + (dav_seek_func)cxBufferSeek); } else { init_hash_stream( &hstr, @@ -903,6 +953,9 @@ int dav_store(DavResource *res) { data->seek, data->length); } + + curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL); + curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L); long status = 0; curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &status); @@ -910,7 +963,7 @@ int dav_store(DavResource *res) { res->session->error = 0; // cleanup node data if(!data->read) { - ucx_mempool_free(sn->mp, data->content); + cxFree(sn->mp->allocator, data->content); } data->content = NULL; data->read = NULL; @@ -927,51 +980,57 @@ int dav_store(DavResource *res) { int ret = 1; if(crypto_res) { - UcxBuffer *rqbuf = create_cryptoprop_propfind_request(); + CxBuffer *rqbuf = create_cryptoprop_propfind_request(); ret = dav_propfind(res->session, res, rqbuf); - ucx_buffer_free(rqbuf); + cxBufferFree(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); + CxMap *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 + crypto_props = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 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 + if(data->crypto_remove) { + CxIterator i = cxListIterator(data->crypto_remove); + cx_foreach(DavProperty *, property, i) { + if(cxMapSize(crypto_props) == 0) { + break; // map already empty, can't remove any more + } + + cxmutstr key = dav_property_key(property->ns->name, property->name); + DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length)); + if(existing_prop) { + // TODO: free existing_prop + } + free(key.ptr); } - - 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); + if(data->crypto_set) { + CxIterator i = cxListIterator(data->crypto_set); + cx_foreach(DavProperty *, property, i) { + cxmutstr keystr = dav_property_key(property->ns->name, property->name); + CxHashKey key = cx_hash_key(keystr.ptr, keystr.length); + DavProperty *existing_prop = NULL; + cxMapRemoveAndGet(crypto_props, key, &existing_prop); + cxMapPut(crypto_props, key, property); + if(existing_prop) { + // TODO: free existing_prop + } + free(keystr.ptr); + } } DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props); if(crypto_prop_value) { DavProperty *new_crypto_prop = createprop(sn, DAV_NS, "crypto-prop"); new_crypto_prop->value = crypto_prop_value; - data->set = ucx_list_prepend_a(sn->mp->allocator, data->set, new_crypto_prop); + add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop); } dav_resource_free(crypto_res); @@ -985,9 +1044,9 @@ int dav_store(DavResource *res) { // 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); + if(data->set || data->remove > 0) { + CxBuffer *request = create_proppatch_request(data); + CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); //printf("request:\n%.*s\n\n", request->pos, request->space); CURLcode ret = do_proppatch_request(sn, locktoken, request, response); @@ -1004,8 +1063,8 @@ int dav_store(DavResource *res) { r = -1; } - ucx_buffer_free(request); - ucx_buffer_free(response); + cxBufferFree(request); + cxBufferFree(response); } return r; @@ -1139,7 +1198,7 @@ int dav_delete(DavResource *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); + CxBuffer *response = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); CURLcode ret = do_delete_request(res->session, locktoken, response); long status = 0; curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); @@ -1155,7 +1214,7 @@ int dav_delete(DavResource *res) { r = 1; } - ucx_buffer_free(response); + cxBufferFree(response); return r; } @@ -1182,7 +1241,7 @@ static int create_ancestors(DavSession *sn, char *href, char *path) { curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); if(status == 201) { // resource successfully created - char *name = util_resource_name(p); + char *name = (char*)util_resource_name(p); int len = strlen(name); if(name[len - 1] == '/') { name[len - 1] = '\0'; @@ -1235,9 +1294,9 @@ static int create_resource(DavResource *res, int *status) { // 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); + CxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0); int ret = dav_propfind(sn, res, rqbuf); - ucx_buffer_free(rqbuf); + cxBufferFree(rqbuf); return ret; } else { return 1; @@ -1341,26 +1400,26 @@ int dav_lock_t(DavResource *res, time_t timeout) { 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); + CxBuffer *request = create_lock_request(); + CxBuffer *response = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); CURLcode ret = do_lock_request(sn, request, response, timeout); //printf("\nlock\n"); //printf("%.*s\n\n", request->size, request->space); //printf("%.*s\n\n", response->size, response->space); - ucx_buffer_free(request); + cxBufferFree(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)) { + int parse_error = parse_lock_response(sn, response, &lock); + cxBufferFree(response); + if(parse_error) { 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); @@ -1383,7 +1442,7 @@ int dav_lock_t(DavResource *res, time_t timeout) { } } else { dav_session_set_error(sn, ret, status); - ucx_buffer_free(response); + cxBufferFree(response); return -1; } } @@ -1418,46 +1477,46 @@ int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, return 0; } - UcxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash); - UcxBuffer *response = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash); + CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); util_set_url(sn, href); // TODO: lock CURLcode ret = do_proppatch_request(sn, NULL, request, response); - ucx_buffer_free(request); + cxBufferFree(request); long status = 0; curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status); if(ret == CURLE_OK && status == 207) { // TODO: parse response sn->error = DAV_OK; - ucx_buffer_free(response); + cxBufferFree(response); return 0; } else { dav_session_set_error(sn, ret, status); - ucx_buffer_free(response); + cxBufferFree(response); return 1; } } /* ----------------------------- crypto-prop ----------------------------- */ -DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties) { +DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) { if(!sn->key) { return NULL; } - UcxBuffer *content = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND); + CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); // create an xml document containing all properties - UcxMap *nsmap = ucx_map_new(8); - ucx_map_cstr_put(nsmap, "DAV:", strdup("D")); + CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); + cxDefineDestructor(nsmap, free); + cxMapPut(nsmap, cx_hash_key_str("DAV:"), strdup("D")); - ucx_buffer_puts(content, "\n"); - ucx_buffer_puts(content, "\n"); + cxBufferPutString(content, "\n"); + cxBufferPutString(content, "\n"); - UcxMapIterator i = ucx_map_iterator(properties); - DavProperty *prop; - UCX_MAP_FOREACH(key, prop, i) { + CxIterator i = cxMapIteratorValues(properties); + cx_foreach(DavProperty*, prop, i) { DavXmlNode pnode; pnode.type = DAV_XML_ELEMENT; pnode.namespace = prop->ns->name; @@ -1470,18 +1529,17 @@ DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties) { pnode.content = NULL; pnode.contentlength = 0; - dav_print_node(content, (write_func)ucx_buffer_write, nsmap, &pnode); - ucx_buffer_putc(content, '\n'); + dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode); + cxBufferPut(content, '\n'); } - ucx_buffer_puts(content, ""); + cxBufferPutString(content, ""); - ucx_map_free_content(nsmap, (ucx_destructor)free); - ucx_map_free(nsmap); + cxMapFree(nsmap); // encrypt xml document char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key); - ucx_buffer_free(content); + cxBufferDestroy(content); DavXmlNode *ret = NULL; if(crypto_prop_content) { @@ -1491,7 +1549,7 @@ DavXmlNode* create_crypto_prop(DavSession *sn, UcxMap *properties) { return ret; } -UcxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) { +CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) { if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) { return NULL; } @@ -1499,7 +1557,7 @@ UcxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) { return parse_crypto_prop_str(sn, key, node->content); } -UcxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) { +CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) { size_t len = 0; char *dec_str = aes_decrypt(content, &len, key); @@ -1529,7 +1587,7 @@ UcxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) } // ready to get the properties - UcxMap *map = ucx_map_new(32); + CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); xmlNode *n = xml_root->children; while(n) { if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) { @@ -1541,17 +1599,285 @@ UcxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) 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); + cxmutstr propkey = dav_property_key(property->ns->name, property->name); + cxMapPut(map, cx_hash_key_cxstr(propkey), property); + cx_strfree(&propkey); } n = n->next; } xmlFreeDoc(doc); - if(map->count == 0) { - ucx_map_free(map); + if(cxMapSize(map) == 0) { + cxMapFree(map); return NULL; } return map; } + + +/* ----------------------------- streams ----------------------------- */ + +static size_t in_write(const char *ptr, size_t size, size_t nitems, void *in_stream) { + DavInputStream *in = in_stream; + size_t len = size * nitems; + + if(in->alloc < len) { + char *newb = realloc(in->buffer, len); + if(!newb) { + if(in->buffer) free(in->buffer); + in->eof = 1; + return 0; + } + + in->buffer = newb; + in->alloc = len; + } + + memcpy(in->buffer, ptr, len); + + in->size = len; + in->pos = 0; + + return nitems; +} + +/* +DavInputStream* dav_inputstream_open(DavResource *res) { + DavSession *sn = res->session; + + DavInputStream *in = dav_session_malloc(sn, sizeof(DavInputStream)); + if(!in) { + return NULL; + } + memset(in, 0, sizeof(DavInputStream)); + + in->res = res; + + in->c = curl_easy_duphandle(sn->handle); + char *url = util_get_url(sn, dav_resource_get_href(res)); + curl_easy_setopt(in->c, CURLOPT_URL, url); + free(url); + + in->m = curl_multi_init(); + + curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL); + curl_easy_setopt(in->c, CURLOPT_PUT, 0L); + curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L); + + curl_multi_add_handle(in->m, in->c); + + dav_write_func write_fnc = (dav_write_func)in_write; + void *stream = in; + + // check encryption + AESDecrypter *dec = NULL; + DavKey *key = NULL; + if(DAV_DECRYPT_CONTENT(sn)) { + char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key"); + if(keyname) { + key = dav_context_get_key(sn->context, keyname); + if(key) { + dec = aes_decrypter_new(key, stream, write_fnc); + stream = dec; + write_fnc = (dav_write_func)aes_write; + } + } + } + + curl_easy_setopt(in->c, CURLOPT_WRITEFUNCTION, write_fnc); + curl_easy_setopt(in->c, CURLOPT_WRITEDATA, stream); + + in->dec = dec; + + return in; +} + +size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in) { + size_t len = in->size - in->pos; + size_t rl = size * nitems; + if(len > 0) { + len = rl > len ? len : rl; + len -= len % size; + memcpy(buf, in->buffer + in->pos, len); + in->pos += len; + return len / size; + } + in->size = 0; + + if(in->eof) { + if(in->dec) { + aes_decrypter_shutdown(in->dec); // get final bytes + aes_decrypter_close(in->dec); + in->dec = NULL; + } else { + return 0; + } + } else { + int running; + while(!in->eof && in->size == 0) { + CURLMcode r = curl_multi_perform(in->m, &running); + if(r != CURLM_OK || running == 0) { + in->eof = 1; + break; + } + + int numfds; + if(curl_multi_poll(in->m, NULL, 0, 5000, &numfds) != CURLM_OK) { + in->eof = 1; + } + } + } + + return in->size > 0 ? dav_read(buf, size, nitems, in) : 0; +} + +void dav_inputstream_close(DavInputStream *in) { + curl_multi_cleanup(in->m); + curl_easy_cleanup(in->c); + if(in->buffer) free(in->buffer); + dav_session_free(in->res->session, in); +} + + +static size_t out_read(char *ptr, size_t size, size_t nitems, void *out_stream) { + DavOutputStream *out = out_stream; + size_t len = size * nitems; + size_t available = out->size - out->pos; + if(available == 0) { + return 0; + } + + size_t r = len > available ? available : len; + r -= r % size; + memcpy(ptr, out->buffer + out->pos, r); + + out->pos += r; + + return r / size; +} + +static size_t dummy_write(void *buf, size_t s, size_t n, void *data) { + return s*n; +} + +DavOutputStream* dav_outputstream_open(DavResource *res) { + DavSession *sn = res->session; + + DavOutputStream *out = dav_session_malloc(sn, sizeof(DavOutputStream)); + if(!out) { + return NULL; + } + memset(out, 0, sizeof(DavOutputStream)); + + out->res = res; + + out->c = curl_easy_duphandle(sn->handle); + char *url = util_get_url(sn, dav_resource_get_href(res)); + curl_easy_setopt(out->c, CURLOPT_URL, url); + free(url); + + out->m = curl_multi_init(); + curl_multi_add_handle(out->m, out->c); + + void *stream = out; + dav_read_func read_fnc = (dav_read_func)out_read; + + // if encryption or hashing in enabled, we need a stream wrapper + if(DAV_ENCRYPT_CONTENT(sn) && sn->key) { + AESEncrypter *enc = aes_encrypter_new(sn->key, out, (dav_read_func)out_read, NULL); + out->enc = enc; + stream = enc; + read_fnc = (dav_read_func)aes_read; + } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) { + HashStream *hstr = dav_session_malloc(sn, sizeof(HashStream)); + out->hstr = hstr; + init_hash_stream(hstr, out, (dav_read_func)out_read, NULL); + stream = hstr; + read_fnc = (dav_read_func)dav_read_h; + } + + curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL); + curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL); + curl_easy_setopt(out->c, CURLOPT_PUT, 1L); + curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc); + curl_easy_setopt(out->c, CURLOPT_READDATA, stream); + curl_easy_setopt(out->c, CURLOPT_SEEKFUNCTION, NULL); + curl_easy_setopt(out->c, CURLOPT_INFILESIZE, -1); + curl_easy_setopt(out->c, CURLOPT_INFILESIZE_LARGE, -1L); + + curl_easy_setopt(out->c, CURLOPT_WRITEFUNCTION, dummy_write); + curl_easy_setopt(out->c, CURLOPT_WRITEDATA, NULL); + + return out; +} + +size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out) { + if(out->eof) return 0; + + out->buffer = buf; + out->size = size * nitems; + out->pos = 0; + + int running; + while(!out->eof && (out->size == 0 || out->size - out->pos > 0)) { + CURLMcode r = curl_multi_perform(out->m, &running); + if(r != CURLM_OK || running == 0) { + out->eof = 1; + break; + } + + int numfds; + if(curl_multi_poll(out->m, NULL, 0, 5000, &numfds) != CURLM_OK) { + out->eof = 1; + } + } + + return (out->size - out->pos) / size; +} + +int dav_outputstream_close(DavOutputStream *out) { + DavSession *sn = out->res->session; + DavResource *res = out->res; + DavResourceData *data = res->data; + + int ret = 0; + + dav_write(NULL, 1, 0, out); + + curl_multi_cleanup(out->m); + curl_easy_cleanup(out->c); + + int store = 0; + if(out->enc) { + // get sha256 hash + char hash[32]; + dav_get_hash(&out->enc->sha256, (unsigned char*)data->hash); + aes_encrypter_close(out->enc); + char *enc_hash = aes_encrypt(hash, DAV_SHA256_DIGEST_LENGTH, sn->key); + // add crypto properties + if(resource_add_crypto_info(sn, out->res->href, out->res->name, enc_hash)) { + ret = 1; + } + free(enc_hash); + } else if(out->hstr) { + dav_hash_final(out->hstr->sha, (unsigned char*)data->hash); + char *hash = util_hexstr((unsigned char*)data->hash, 32); + dav_set_string_property_ns(res, DAV_NS, "content-hash", hash); + free(hash); + dav_session_free(sn, out->hstr); + store = 1; + } + + if(store) { + ret = dav_store(out->res); + } + + dav_session_free(out->res->session, out); + + return ret; +} + +*/ diff --git a/libidav/resource.h b/libidav/resource.h index 5cce0b7..37f2033 100644 --- a/libidav/resource.h +++ b/libidav/resource.h @@ -30,7 +30,9 @@ #define RESOURCE_H #include "webdav.h" -#include +#include "crypto.h" +#include +#include #ifdef __cplusplus extern "C" { @@ -39,16 +41,16 @@ extern "C" { typedef struct DavResourceData DavResourceData; struct DavResourceData { - UcxMap *properties; - UcxList *set; - UcxList *remove; - UcxList *crypto_set; - UcxList *crypto_remove; + CxMap *properties; + CxList *set; + CxList *remove; + CxList *crypto_set; + CxList *crypto_remove; /* * properties encapsulated in a crypto-prop property or NULL */ - UcxMap *crypto_properties; + CxMap *crypto_properties; /* * char* or stream @@ -57,7 +59,7 @@ struct DavResourceData { /* * if NULL, content is a char* */ - read_func read; + dav_read_func read; /* * curl seek func */ @@ -73,29 +75,64 @@ struct DavResourceData { char hash[32]; }; -DavResource* dav_resource_new_full(DavSession *sn, char *parent_path, char *name, char *href); +/* + * read wrapper with integrated hashing + */ +typedef struct { + DAV_SHA_CTX *sha; + void *stream; + dav_read_func read; + dav_seek_func seek; + int error; +} HashStream; + +struct DavInputStream { + DavResource *res; + CURLM *m; + CURL *c; + AESDecrypter *dec; + char *buffer; + size_t alloc; + size_t size; + size_t pos; + int eof; +}; + +struct DavOutputStream { + DavResource *res; + CURLM *m; + CURL *c; + AESEncrypter *enc; + HashStream *hstr; + const char *buffer; + size_t size; + size_t pos; + int eof; +}; + +DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href); -void resource_free_properties(DavSession *sn, UcxMap *properties); +void resource_free_properties(DavSession *sn, CxMap *properties); -void resource_set_href(DavResource *res, sstr_t href); +void resource_set_href(DavResource *res, cxstring href); -void resource_set_info(DavResource *res, char *href_str); +void resource_set_info(DavResource *res, const char *href_str); DavResourceData* resource_data_new(DavSession *sn); void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val); -void resource_set_crypto_properties(DavResource *res, UcxMap *cprops); +void resource_set_crypto_properties(DavResource *res, CxMap *cprops); DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name); DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name); -DavXmlNode* resource_get_property_k(DavResource *res, UcxKey key); -DavXmlNode* resource_get_encrypted_property_k(DavResource *res, UcxKey key); +DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key); +DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key); void resource_add_child(DavResource *parent, DavResource *child); -void resource_add_ordered_child(DavResource *parent, DavResource *child, UcxList *ordercr); +void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr); int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash); -sstr_t dav_property_key_a(UcxAllocator *a, const char *ns, const char *name); +cxmutstr dav_property_key_a(const CxAllocator *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); +DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties); +CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node); +CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content); #ifdef __cplusplus } diff --git a/libidav/session.c b/libidav/session.c index 9f57fa4..13ec76d 100644 --- a/libidav/session.c +++ b/libidav/session.c @@ -30,8 +30,9 @@ #include #include -#include -#include +#include +#include +#include #include "utils.h" #include "session.h" @@ -42,14 +43,14 @@ DavSession* dav_session_new(DavContext *context, char *base_url) { if(!base_url) { return NULL; } - sstr_t url = sstr(base_url); + cxstring url = cx_str(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->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); + sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); sn->key = NULL; sn->errorstr = NULL; sn->error = DAV_OK; @@ -60,14 +61,11 @@ DavSession* dav_session_new(DavContext *context, char *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; + // lock manager is created on-demand + sn->locks = NULL; // set proxy - DavProxy *proxy = sstrprefix(url, S("https")) ? context->https_proxy + DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy : context->http_proxy; if (proxy->url) { @@ -95,7 +93,7 @@ DavSession* dav_session_new(DavContext *context, char *base_url) { curl_easy_setopt(sn->handle, CURLOPT_URL, base_url); // add to context - context->sessions = ucx_list_append(context->sessions, sn); + dav_context_add_session(context, sn); sn->context = context; return sn; @@ -115,29 +113,63 @@ DavSession* dav_session_new_auth( return sn; } -void dav_session_set_auth(DavSession *sn, char *user, char *password) { +DavSession* dav_session_clone(DavSession *sn) { + CURL *newhandle = curl_easy_duphandle(sn->handle); + + DavSession *newsn = malloc(sizeof(DavSession)); + memset(newsn, 0, sizeof(DavSession)); + newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); + newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); + newsn->key = sn->key; + newsn->errorstr = NULL; + newsn->error = DAV_OK; + newsn->flags = 0; + + newsn->handle = newhandle; + + newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr; + newsn->auth_prompt = sn->auth_prompt; + newsn->authprompt_userdata = sn->authprompt_userdata; + newsn->logfunc = sn->logfunc; + newsn->get_progress = sn->get_progress; + newsn->put_progress = sn->put_progress; + newsn->progress_userdata = sn->progress_userdata; + + // add to context + dav_context_add_session(sn->context, newsn); + newsn->context = sn->context; + + return newsn; +} + +void dav_session_set_auth(DavSession *sn, const char *user, const char *password) { if(user && password) { - size_t ulen = strlen(user); - size_t plen = strlen(password); - size_t upwdlen = ulen + plen + 2; + dav_session_set_auth_s(sn, cx_str(user), cx_str(password)); + } +} + +void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) { + if(user.length > 0 && password.length > 0) { + size_t upwdlen = user.length + password.length + 2; char *upwdbuf = malloc(upwdlen); - snprintf(upwdbuf, upwdlen, "%s:%s", user, password); + snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr); curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf); free(upwdbuf); } } void dav_session_set_baseurl(DavSession *sn, char *base_url) { + const CxAllocator *a = sn->mp->allocator; if(sn->base_url) { - ucx_mempool_free(sn->mp, sn->base_url); + cxFree(a, sn->base_url); } - sstr_t url = sstr(base_url); + cxstring url = cx_str(base_url); if(url.ptr[url.length - 1] == '/') { - sstr_t url = sstrdup_a(sn->mp->allocator, sstr(base_url)); - sn->base_url = url.ptr; + cxmutstr url_m = cx_strdup_a(a, cx_str(base_url)); + sn->base_url = url_m.ptr; } else { - char *url_str = ucx_mempool_malloc(sn->mp, url.length + 2); + char *url_str = cxMalloc(a, url.length + 2); memcpy(url_str, base_url, url.length); url_str[url.length] = '/'; url_str[url.length + 1] = '\0'; @@ -170,22 +202,60 @@ 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 dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) { CURLcode ret = curl_easy_perform(sn->handle); long http_status; curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); - if(ret == CURLE_OK && http_status == 401 && sn->auth_prompt) { - if(!sn->auth_prompt(sn, sn->authprompt_userdata)) { + if(ret == CURLE_OK) { + if(sn->logfunc) { + char *log_method; + char *log_url; + curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url); +#if LIBCURL_VERSION_NUM >= 0x074800 + curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method); +#else + long opt_upload = 0; + curl_easy_getinfo(sn->handle, CURLOPT_UPLOAD, &opt_upload); + char *opt_custom = NULL; + curl_easy_getinfo(sn->handle, CURLOPT_CUSTOMREQUEST, &opt_custom); + if(opt_custom) { + log_method = opt_custom; + } else if(opt_upload) { + log_method = "PUT"; + } else { + log_method = "GET"; + } +#endif + char *log_reqbody = NULL; + size_t log_reqbodylen = 0; + char *log_rpbody = NULL; + size_t log_rpbodylen = 0; if(request) { - ucx_buffer_seek(request, 0, SEEK_SET); + log_reqbody = request->space; + log_reqbodylen = request->size; } if(response) { - ucx_buffer_seek(response, 0, SEEK_SET); + log_rpbody = response->space; + log_rpbodylen = response->size; } - ret = curl_easy_perform(sn->handle); - curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); + sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen); } + + if(http_status == 401 && sn->auth_prompt) { + if(!sn->auth_prompt(sn, sn->authprompt_userdata)) { + if(request) { + cxBufferSeek(request, 0, SEEK_SET); + } + if(response) { + cxBufferSeek(response, 0, SEEK_SET); + } + ret = curl_easy_perform(sn->handle); + curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); + } + } + } + if(status) { *status = http_status; } @@ -244,9 +314,9 @@ void dav_session_set_error(DavSession *sn, CURLcode c, int status) { case CURLE_SSL_ENGINE_SETFAILED: case CURLE_SSL_CERTPROBLEM: case CURLE_SSL_CIPHER: -#ifndef CURLE_SSL_CACERT - case CURLE_SSL_CACERT: -#endif +//#ifndef CURLE_SSL_CACERT +// case CURLE_SSL_CACERT: +//#endif case CURLE_SSL_CACERT_BADFILE: case CURLE_SSL_SHUTDOWN_FAILED: case CURLE_SSL_CRL_BADFILE: @@ -274,43 +344,41 @@ void dav_session_set_errstr(DavSession *sn, const char *str) { 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); - } + if (dav_context_remove_session(sn->context, sn)) { + fprintf(stderr, "Error: session not found in ctx->sessions\n"); + dav_session_destructor(sn); } - - ucx_mempool_destroy(sn->mp); +} + +void dav_session_destructor(DavSession *sn) { + cxMempoolFree(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); + return cxMalloc(sn->mp->allocator, size); } void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) { - return ucx_mempool_calloc(sn->mp, nelm, size); + return cxCalloc(sn->mp->allocator, nelm, size); } void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) { - return ucx_mempool_realloc(sn->mp, ptr, size); + return cxRealloc(sn->mp->allocator, ptr, size); } void dav_session_free(DavSession *sn, void *ptr) { - ucx_mempool_free(sn->mp, ptr); + cxFree(sn->mp->allocator, ptr); } char* dav_session_strdup(DavSession *sn, const char *str) { - return sstrdup_a(sn->mp->allocator, sstr((char*)str)).ptr; + return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr; } -char* dav_session_create_plain_href(DavSession *sn, char *path) { +char* dav_session_create_plain_href(DavSession *sn, const char *path) { if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) { // non encrypted file names char *url = util_path_to_url(sn, path); @@ -322,11 +390,14 @@ char* dav_session_create_plain_href(DavSession *sn, char *path) { } } -char* dav_session_get_href(DavSession *sn, char *path) { +char* dav_session_get_href(DavSession *sn, const 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); + cxstring p = cx_str(path); + CxBuffer href; + CxBuffer pbuf; + cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + int start = 0; int begin = 0; @@ -334,11 +405,11 @@ char* dav_session_get_href(DavSession *sn, char *path) { char *cp = strdup(path); //printf("cp: %s\n", cp); while(strlen(cp) > 1) { - char *cached = ucx_map_cstr_get(sn->pathcache, cp); + char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp)); if(cached) { start = strlen(cp); begin = start; - ucx_buffer_puts(href, cached); + cxBufferPutString(&href, cached); break; } else { // check, if the parent path is cached @@ -348,101 +419,96 @@ char* dav_session_get_href(DavSession *sn, char *path) { } } free(cp); - if(href->pos == 0) { + 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)); + cxBufferPutString(&href, util_url_path(sn->base_url)); } // create resource for name lookup - sstr_t rp = sstrdup(sstrn(path, start)); + cxmutstr rp = cx_strdup(cx_strn(path, start)); DavResource *root = dav_resource_new(sn, rp.ptr); free(rp.ptr); - resource_set_href(root, sstrn(href->space, href->pos)); + resource_set_href(root, cx_strn(href.space, href.pos)); // create request buffer for propfind requests - UcxBuffer *rqbuf = create_basic_propfind_request(); + CxBuffer *rqbuf = create_basic_propfind_request(); - sstr_t remaining = sstrsubs(p, start); - ssize_t nelm = 0; - sstr_t *elms = sstrsplit(remaining, S("/"), &nelm); + cxstring remaining = cx_strsubs(p, start); + CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX); DavResource *res = root; - ucx_buffer_puts(pbuf, res->path); + cxBufferPutString(&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, '/'); + if(pbuf.space[pbuf.pos-1] != '/') { + if(href.space[href.pos-1] != '/') { + cxBufferPut(&href, '/'); } - ucx_buffer_putc(pbuf, '/'); + cxBufferPut(&pbuf, '/'); } // add last path/href to the cache - sstr_t pp = sstrn(pbuf->space, pbuf->size); - sstr_t hh = sstrn(href->space, href->size); + cxstring pp = cx_strn(pbuf.space, pbuf.size); + cxstring hh = cx_strn(href.space, href.size); dav_session_cache_path(sn, pp, hh); - ucx_buffer_write(elm.ptr, 1, elm.length, pbuf); + cxBufferWrite(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)); + cxBufferPutString(&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); + cxBufferPutString(&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)); + cxstring resname = cx_str(util_resource_name((const char*)path)); // the name of collections ends with // a trailing slash, which MUST NOT be encoded if(resname.ptr[resname.length-1] == '/') { char *esc = curl_easy_escape(sn->handle, resname.ptr, resname.length-1); - ucx_buffer_write(esc, 1, strlen(esc), href); - ucx_buffer_putc(href, '/'); + cxBufferWrite(esc, 1, strlen(esc), &href); + cxBufferPut(&href, '/'); curl_free(esc); } else { char *esc = curl_easy_escape(sn->handle, resname.ptr, resname.length); - ucx_buffer_write(esc, 1, strlen(esc), href); + cxBufferWrite(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, '/'); + if(href.space[href.pos-1] != '/') { + cxBufferPut(&href, '/'); } - ucx_buffer_putc(pbuf, '/'); + cxBufferPut(&pbuf, '/'); } // add the final path to the cache - sstr_t pp = sstrn(pbuf->space, pbuf->size); - sstr_t hh = sstrn(href->space, href->size); + cxstring pp = cx_strn(pbuf.space, pbuf.size); + cxstring hh = cx_strn(href.space, href.size); dav_session_cache_path(sn, pp, hh); - sstr_t href_str = sstrdup_a( + cxmutstr href_str = cx_strdup_a( sn->mp->allocator, - sstrn(href->space, - href->size)); + cx_strn(href.space, href.size)); // cleanup dav_resource_free_all(root); - ucx_buffer_free(rqbuf); - ucx_buffer_free(pbuf); - ucx_buffer_free(href); + cxBufferFree(rqbuf); + + cxBufferDestroy(&pbuf); + cxBufferDestroy(&href); return href_str.ptr; } else { @@ -450,7 +516,7 @@ char* dav_session_get_href(DavSession *sn, char *path) { } } -DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name) { +DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) { if(res && !dav_propfind(sn, res, rqbuf)) { DavResource *child = res->children; while(child) { @@ -463,16 +529,17 @@ DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, return NULL; } -void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href) { - char *elm = ucx_map_sstr_get(sn->pathcache, path); +void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) { + CxHashKey path_key = cx_hash_key(path.ptr, path.length); + char *elm = cxMapGet(sn->pathcache, path_key); if(!elm) { - href = sstrdup_a(sn->mp->allocator, href); - ucx_map_sstr_put(sn->pathcache, path, href.ptr); + cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href); + cxMapPut(sn->pathcache, path_key, href_s.ptr); } } -DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout) { +DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) { DavLock *lock = dav_session_malloc(sn, sizeof(DavLock)); lock->path = NULL; lock->token = dav_session_strdup(sn, token); @@ -490,78 +557,81 @@ void dav_destroy_lock(DavSession *sn, DavLock *lock) { 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); + +static int dav_lock_cmp(void const *left, void const *right) { + const DavLock *l = left; + const DavLock *r = right; + return strcmp(l->path, r->path); +} + +static int create_lock_manager(DavSession *sn) { + // create lock manager + DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager)); + locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16); + locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS); + sn->locks = locks; return 0; } -static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) { - UcxList *next = elm->next; - if(next) { - next->prev = newelm; - newelm->next = next; +static DavLockManager* get_lock_manager(DavSession *sn) { + DavLockManager *locks = sn->locks; + if(!locks) { + if(create_lock_manager(sn)) { + return NULL; + } + locks = sn->locks; } - newelm->prev = elm; - elm->next = newelm; + return locks; } -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; +int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) { + DavLockManager *locks = get_lock_manager(sn); + if(!locks) { + return -1; } - 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; - } + CxHashKey path_key = cx_hash_key_str(path); + if(cxMapGet(locks->resource_locks, path_key)) { + return -1; } + cxMapPut(locks->resource_locks, path_key, lock); return 0; } -DavLock* dav_get_lock(DavSession *sn, char *path) { - DavLockManager *locks = sn->locks; +int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) { + DavLockManager *locks = get_lock_manager(sn); + if(!locks) { + return -1; + } - DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path); + lock->path = dav_session_strdup(sn, path); + cxListAdd(locks->collection_locks, lock); + cxListSort(locks->collection_locks); + + return 0; +} + +DavLock* dav_get_lock(DavSession *sn, const char *path) { + DavLockManager *locks = get_lock_manager(sn); + if(!locks) { + return NULL; + } + + cxstring p = cx_str(path); + + DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length)); if(lock) { return lock; } - sstr_t p = sstr(path); - UCX_FOREACH(elm, locks->collection_locks) { - DavLock *cl = elm->data; - int cmd = strcmp(path, cl->path); + CxIterator i = cxListIterator(locks->collection_locks); + cx_foreach(DavLock*, col_lock, i) { + int cmd = strcmp(path, col_lock->path); if(cmd == 0) { - return cl; - } else if(sstrprefix(p, sstr(cl->path))) { - return cl; + return col_lock; + } else if(cx_strprefix(p, cx_str(col_lock->path))) { + return col_lock; } else if(cmd > 0) { break; } @@ -570,26 +640,15 @@ DavLock* dav_get_lock(DavSession *sn, char *path) { 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)) { +void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) { + DavLockManager *locks = get_lock_manager(sn); + if(!locks) { 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); + if(!cxMapRemove(locks->resource_locks, cx_hash_key_str(path))) { + return; } + + cxListFindRemove(locks->collection_locks, lock); } diff --git a/libidav/session.h b/libidav/session.h index 2ea2b76..6759da4 100644 --- a/libidav/session.h +++ b/libidav/session.h @@ -29,7 +29,7 @@ #ifndef DAV_SESSION_H #define DAV_SESSION_H -#include +#include #include "webdav.h" #ifdef __cplusplus @@ -74,19 +74,19 @@ typedef struct DavPathCacheElement { } DavPathCacheElement; */ -typedef struct DavLock { +typedef struct DavLock DavLock; +struct DavLock { char *path; char *token; - -} DavLock; +}; typedef struct DavLockManager { - UcxMap *resource_locks; - UcxList *collection_locks; + CxMap *resource_locks; + CxList *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); +CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status); int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); @@ -94,23 +94,23 @@ int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow 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_create_plain_href(DavSession *sn, const char *path); -char* dav_session_get_href(DavSession *sn, char *path); +char* dav_session_get_href(DavSession *sn, const char *path); -DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name); +DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name); -void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href); +void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href); -DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout); +DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout); void dav_destroy_lock(DavSession *sn, DavLock *lock); -int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock); -int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock); +int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock); +int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock); -DavLock* dav_get_lock(DavSession *sn, char *path); -void dav_remove_lock(DavSession *sn, char *path, DavLock *lock); +DavLock* dav_get_lock(DavSession *sn, const char *path); +void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock); #ifdef __cplusplus } diff --git a/libidav/utils.c b/libidav/utils.c index 6ca83bd..d1f3776 100644 --- a/libidav/utils.c +++ b/libidav/utils.c @@ -32,9 +32,10 @@ #include #include #include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -44,6 +45,9 @@ #define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\') #define PATH_SEPARATOR '\\' #else +#include +#include +#include #include #define getpasswordchar() getchar() #define IS_PATH_SEPARATOR(c) (c == '/') @@ -63,7 +67,7 @@ #include */ -static size_t extractval(sstr_t str, char *result, char delim) { +static size_t extractval(cxstring str, char *result, char delim) { size_t n = 0; for(size_t i = 0; i < str.length ; i++) { if(isdigit(str.ptr[i])) { @@ -90,19 +94,19 @@ static time_t parse_iso8601(char *iso8601str) { char conv[16]; // work on the trimmed string - sstr_t date = sstrtrim(sstr(iso8601str)); + cxstring date = cx_strtrim(cx_str(iso8601str)); - sstr_t time = sstrchr(date, 'T'); + cxstring time = cx_strchr(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) { + cxstring tzinfo; + if((tzinfo = cx_strchr(time, 'Z')).length > 0 || + (tzinfo = cx_strchr(time, '+')).length > 0 || + (tzinfo = cx_strchr(time, '-')).length > 0) { time.length = tzinfo.ptr - time.ptr; } @@ -121,9 +125,9 @@ static time_t parse_iso8601(char *iso8601str) { 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) { + cxstring frac; + if((frac = cx_strchr(time, '.')).length > 0 || + (frac = cx_strchr(time, ',')).length > 0) { time.length = frac.ptr - time.ptr; } if((time.length != 6 && time.length != 8) @@ -141,9 +145,11 @@ static time_t parse_iso8601(char *iso8601str) { // local time tparts.tm_isdst = -1; return mktime(&tparts); - } else if(!sstrcmp(tzinfo, S("Z"))) { -#ifdef __FreeBSD__ + } else if(!cx_strcmp(tzinfo, cx_str("Z"))) { +#if defined(__FreeBSD__) return timegm(&tparts); +#elif defined(_WIN32) + return _mkgmtime(&tparts); #else return mktime(&tparts) - timezone; #endif @@ -157,8 +163,10 @@ static time_t parse_iso8601(char *iso8601str) { extractval(tzinfo, conv, ':'); val = atol(conv); val = 60 * (val / 100) + (val % 100); -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) return timegm(&tparts) + (time_t) (60 * val * sign); +#elif defined(_WIN32) + return _mkgmtime(&tparts) + (time_t)(60 * val * sign); #else return mktime(&tparts) - timezone + (time_t) (60 * val * sign); #endif @@ -205,10 +213,11 @@ int util_getboolean(const char *v) { } int util_strtouint(const char *str, uint64_t *value) { + if (str == NULL || *str == '\0') return 0; char *end; errno = 0; uint64_t val = strtoull(str, &end, 0); - if(errno == 0) { + if(errno == 0 && *end == '\0') { *value = val; return 1; } else { @@ -217,10 +226,11 @@ int util_strtouint(const char *str, uint64_t *value) { } int util_strtoint(const char *str, int64_t *value) { + if (str == NULL || *str == '\0') return 0; char *end; errno = 0; int64_t val = strtoll(str, &end, 0); - if(errno == 0) { + if(errno == 0 && *end == '\0') { *value = val; return 1; } else { @@ -229,11 +239,14 @@ int util_strtoint(const char *str, int64_t *value) { } int util_szstrtouint(const char *str, uint64_t *value) { + if (str == NULL || *str == '\0') return 0; char *end; errno = 0; size_t len = strlen(str); uint64_t val = strtoull(str, &end, 0); - if(end == str+len) { + if(errno != 0) { + return 0; + } if(end == str+len) { *value = val; return 1; } else if(end == str+len-1) { @@ -273,13 +286,13 @@ int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result) { } } -char* util_url_base_s(sstr_t url) { +cxstring util_url_base_s(cxstring url) { size_t i = 0; if(url.length > 0) { int slmax; - if(sstrprefix(url, SC("http://"))) { + if(cx_strprefix(url, cx_str("http://"))) { slmax = 3; - } else if(sstrprefix(url, SC("https://"))) { + } else if(cx_strprefix(url, cx_str("https://"))) { slmax = 3; } else { slmax = 1; @@ -295,44 +308,47 @@ char* util_url_base_s(sstr_t url) { } } } - sstr_t server = sstrsubsl(url, 0, i); - return sstrdup(server).ptr; + return cx_strsubsl(url, 0, i); +} + +char* util_url_base(const char *url) { + return cx_strdup(util_url_base_s(cx_str(url))).ptr; } -char* util_url_base(char *url) { - return util_url_base_s(sstr(url)); +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +const char* util_url_path(const char *url) { + return util_url_path_s(cx_str(url)).ptr; } -char* util_url_path(char *url) { - char *path = NULL; - size_t len = strlen(url); +cxstring util_url_path_s(cxstring url) { + cxstring path = { "", 0 }; int slashcount = 0; int slmax; - if(len > 7 && !strncasecmp(url, "http://", 7)) { + if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) { slmax = 3; - } else if(len > 8 && !strncasecmp(url, "https://", 8)) { + } else if(url.length > 8 && !strncasecmp(url.ptr, "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); @@ -342,24 +358,24 @@ char* util_url_decode(DavSession *sn, char *url) { static size_t util_header_callback(char *buffer, size_t size, size_t nitems, void *data) { - sstr_t sbuffer = sstrn(buffer, size*nitems); + cxstring sbuffer = cx_strn(buffer, size*nitems); - UcxMap *map = (UcxMap*) data; + CxMap *map = (CxMap*) 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); + if(cx_strprefix(sbuffer, cx_str("HTTP/"))) { + // TODO: use new map destructor ucx_map_free_content(map, free); + cxMapClear(map); return size*nitems; } // if we get the terminating CRLF, just exit - if(!sstrcmp(sbuffer, S("\r\n"))) { + if(!cx_strcmp(sbuffer, cx_str("\r\n"))) { return 2; } - sstr_t key = sbuffer; - sstr_t value = sstrchr(sbuffer, ':'); + cxstring key = sbuffer; + cxstring value = cx_strchr(sbuffer, ':'); if(value.length == 0) { return 0; // invalid header line @@ -368,19 +384,20 @@ static size_t util_header_callback(char *buffer, size_t size, key.length = value.ptr - key.ptr; value.ptr++; value.length--; - key = sstrlower(sstrtrim(key)); - value = sstrdup(sstrtrim(value)); + cxmutstr key_cp = cx_strdup(cx_strtrim(key)); + cx_strlower(key_cp); + cxmutstr value_cp = cx_strdup(cx_strtrim(value)); - ucx_map_sstr_put(map, key, value.ptr); + cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr); - free(key.ptr); + free(key_cp.ptr); return sbuffer.length; } int util_path_isrelated(const char *path1, const char *path2) { - scstr_t p1 = scstr(path1); - scstr_t p2 = scstr(path2); + cxstring p1 = cx_str(path1); + cxstring p2 = cx_str(path2); if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) { p1.length--; @@ -393,11 +410,11 @@ int util_path_isrelated(const char *path1, const char *path2) { return 0; } - if(!sstrcmp(p1, p2)) { + if(!cx_strcmp(p1, p2)) { return 1; } - if(sstrprefix(p2, p1)) { + if(cx_strprefix(p2, p1)) { if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) { return 1; } @@ -431,10 +448,11 @@ int util_path_isabsolut(const char *path) { char* util_path_normalize(const char *path) { size_t len = strlen(path); - UcxBuffer *buf = ucx_buffer_new(NULL, len+1, UCX_BUFFER_AUTOEXTEND); + CxBuffer buf; + cxBufferInit(&buf, NULL, len+1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(path[0] == '/') { - ucx_buffer_putc(buf, '/'); + cxBufferPut(&buf, '/'); } int add_separator = 0; @@ -450,25 +468,25 @@ char* util_path_normalize(const char *path) { } 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]; + cxstring seg = cx_strn(seg_ptr, seg_len); + if(!cx_strcmp(seg, CX_STR(".."))) { + for(int j=buf.pos;j>=0;j--) { + char t = j < buf.pos ? buf.space[j] : 0; if(IS_PATH_SEPARATOR(t) || j == 0) { - buf->pos = j; - buf->size = j; - buf->space[j] = 0; + buf.pos = j; + buf.size = j; + buf.space[j] = 0; add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0; break; } } - } else if(!sstrcmp(seg, SC("."))) { + } else if(!cx_strcmp(seg, CX_STR("."))) { // ignore } else { if(add_separator) { - ucx_buffer_putc(buf, PATH_SEPARATOR); + cxBufferPut(&buf, PATH_SEPARATOR); } - ucx_buffer_write(seg_ptr, 1, seg_len, buf); + cxBufferWrite(seg_ptr, 1, seg_len, &buf); add_separator = 1; } } @@ -477,13 +495,9 @@ char* util_path_normalize(const char *path) { } } - ucx_buffer_putc(buf, 0); - + cxBufferPut(&buf, 0); - char *space = buf->space; - buf->flags = 0; // disable autofree - ucx_buffer_free(buf); - return space; + return buf.space; } static char* create_relative_path(const char *abspath, const char *base) { @@ -508,9 +522,8 @@ static char* create_relative_path(const char *abspath, const char *base) { // 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); + cxBufferPutString(&out, abspath + last_dir + 1); + cxBufferPut(&out, 0); - return ret; + return out.space; } #ifdef _WIN32 @@ -578,7 +588,7 @@ char* util_create_relative_path(const char *abspath, const char *base) { #endif -void util_capture_header(CURL *handle, UcxMap* map) { +void util_capture_header(CURL *handle, CxMap* map) { if(map) { curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, util_header_callback); curl_easy_setopt(handle, CURLOPT_HEADERDATA, map); @@ -588,12 +598,25 @@ void util_capture_header(CURL *handle, UcxMap* map) { } } -char* util_resource_name(char *url) { - sstr_t urlstr = sstr(url); +const char* util_resource_name(const char *url) { + cxstring urlstr = cx_str(url); if(urlstr.ptr[urlstr.length-1] == '/') { urlstr.length--; } - sstr_t resname = sstrrchr(urlstr, '/'); + cxstring resname = cx_strrchr(urlstr, '/'); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +const char* util_resource_name_c(const char *url, char pathseparator) { + cxstring urlstr = cx_str(url); + if(urlstr.ptr[urlstr.length-1] == pathseparator) { + urlstr.length--; + } + cxstring resname = cx_strrchr(urlstr, pathseparator); if(resname.length > 1) { return resname.ptr+1; } else { @@ -601,6 +624,15 @@ char* util_resource_name(char *url) { } } +const char* util_path_file_name(const char *url) { +#ifdef _WIN32 + return util_resource_name_c(url, '\\'); +#else + return util_resource_name_c(url, '/'); +#endif +} + + int util_mkdir(char *path, mode_t mode) { #ifdef _WIN32 return mkdir(path); @@ -610,12 +642,20 @@ int util_mkdir(char *path, mode_t mode) { } char* util_concat_path(const char *url_base, const char *p) { - sstr_t base = sstr((char*)url_base); - sstr_t path; + cxstring base = cx_str(url_base); + cxstring path; if(p) { - path = sstr((char*)p); + path = cx_str((char*)p); } else { - path = sstrn("", 0); + path = CX_STR(""); + } + + return util_concat_path_s(base, path).ptr; +} + +cxmutstr util_concat_path_s(cxstring base, cxstring path) { + if(!path.ptr) { + path = CX_STR(""); } int add_separator = 0; @@ -629,24 +669,58 @@ char* util_concat_path(const char *url_base, const char *p) { } } - sstr_t url; + cxmutstr url; if(add_separator) { - url = sstrcat(3, base, sstr("/"), path); + url = cx_strcat(3, base, CX_STR("/"), path); } else { - url = sstrcat(2, base, path); + url = cx_strcat(2, base, path); } - return url.ptr; + return url; +} + +cxmutstr util_concat_path_ext(cxstring base, cxstring path, char separator) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == separator) { + if(path.ptr[0] == separator) { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != separator) { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, cx_strn(&separator, 1), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +cxmutstr util_concat_sys_path(cxstring base, cxstring path) { +#ifdef _WIN32 + return util_concat_path_ext(base, path, '\\'); +#else + return util_concat_path_ext(base, path, '/'); +#endif } char* util_get_url(DavSession *sn, const char *href) { - scstr_t base = scstr(sn->base_url); - scstr_t href_str = scstr(href); + cxstring base = cx_str(sn->base_url); + cxstring href_str = cx_str(href); - char *base_path = util_url_path(sn->base_url); + const char *base_path = util_url_path(sn->base_url); base.length -= strlen(base_path); - sstr_t url = sstrcat(2, base, href_str); + cxmutstr url = cx_strcat(2, base, href_str); return url.ptr; } @@ -656,43 +730,54 @@ void util_set_url(DavSession *sn, const char *href) { 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); +char* util_path_to_url(DavSession *sn, const char *path) { + size_t pathlen = path ? strlen(path) : 0; + if(pathlen == 0) { + return strdup(sn->base_url); + } + + CxBuffer url; + cxBufferInit(&url, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); // add base url - ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url); + cxBufferWrite(sn->base_url, 1, strlen(sn->base_url), &url); // remove trailing slash - ucx_buffer_seek(url, -1, SEEK_CUR); + cxBufferSeek(&url, -1, SEEK_CUR); - sstr_t p = sstr(path); - ssize_t ntk = 0; - sstr_t *tks = sstrsplit(p, S("/"), &ntk); + cxstring p = cx_strn(path, pathlen); - 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); + cxBufferPut(&url, '/'); + cxBufferWrite(esc, 1, strlen(esc), &url); curl_free(esc); } - free(node.ptr); } - free(tks); + if(path[p.length-1] == '/') { - ucx_buffer_putc(url, '/'); + cxBufferPut(&url, '/'); } - ucx_buffer_putc(url, 0); + cxBufferPut(&url, 0); - space = url->space; - ucx_buffer_free(url); - - return space; + return url.space; } char* util_parent_path(const char *path) { - char *name = util_resource_name((char*)path); + const char *name = util_resource_name(path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +char* util_sys_parent_path(const char *path) { + const char *name = util_path_file_name(path); size_t namelen = strlen(name); size_t pathlen = strlen(path); size_t parentlen = pathlen - namelen; @@ -703,50 +788,54 @@ char* util_parent_path(const char *path) { } char* util_size_str(DavBool iscollection, uint64_t contentlength) { + return util_size_str2(iscollection, contentlength, contentlength, 1); +} + +char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision) { char *str = malloc(16); uint64_t size = contentlength; - + if(iscollection) { str[0] = '\0'; // currently no information for collections - } else if(size < 0x400) { + } else if(dimension < 0x400) { snprintf(str, 16, "%" PRIu64 " bytes", size); - } else if(size < 0x100000) { + } else if(dimension < 0x100000) { float s = (float)size/0x400; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } - if(size < 0x2800 && diff != 0) { + if(dimension < 0x2800 && diff != 0) { // size < 10 KiB - snprintf(str, 16, "%.1f KiB", s); + snprintf(str, 16, "%.*f KiB", precision, s); } else { snprintf(str, 16, "%.0f KiB", s); } - } else if(size < 0x40000000) { + } else if(dimension < 0x40000000) { float s = (float)size/0x100000; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } - if(size < 0xa00000 && diff != 0) { + if(dimension < 0xa00000 && diff != 0) { // size < 10 MiB - snprintf(str, 16, "%.1f MiB", s); + snprintf(str, 16, "%.*f MiB", precision, s); } else { size /= 0x100000; snprintf(str, 16, "%.0f MiB", s); } - } else if(size < 0x1000000000ULL) { + } else if(dimension < 0x1000000000ULL) { float s = (float)size/0x40000000; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } - if(size < 0x280000000 && diff != 0) { + if(dimension < 0x280000000 && diff != 0) { // size < 10 GiB - snprintf(str, 16, "%.1f GiB", s); + snprintf(str, 16, "%.*f GiB", precision, s); } else { size /= 0x40000000; snprintf(str, 16, "%.0f GiB", s); @@ -759,9 +848,9 @@ char* util_size_str(DavBool iscollection, uint64_t contentlength) { diff = 0; s += 0.10f; } - if(size < 0x280000000 && diff != 0) { + if(dimension < 0x280000000 && diff != 0) { // size < 10 TiB - snprintf(str, 16, "%.1f TiB", s); + snprintf(str, 16, "%.*f TiB", precision, s); } else { size /= 0x40000000; snprintf(str, 16, "%.0f TiB", s); @@ -949,11 +1038,11 @@ char* util_base64encode(const char *in, size_t len) { return out; } -char* util_encrypt_str(DavSession *sn, char *str, char *key) { +char* util_encrypt_str(DavSession *sn, const char *str, const char *key) { DavKey *k = dav_context_get_key(sn->context, key); if(!k) { sn->error = DAV_ERROR; - sstr_t err = ucx_sprintf("Key %s not found", key); + cxmutstr err = cx_asprintf("Key %s not found", key); dav_session_set_errstr(sn, err.ptr); free(err.ptr); return NULL; @@ -962,18 +1051,18 @@ char* util_encrypt_str(DavSession *sn, char *str, char *key) { return util_encrypt_str_k(sn, str, k); } -char* util_encrypt_str_k(DavSession *sn, char *str, DavKey *key) { +char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key) { char *enc_str = aes_encrypt(str, strlen(str), key); char *ret_str = dav_session_strdup(sn, enc_str); free(enc_str); return ret_str; } -char* util_decrypt_str(DavSession *sn, char *str, char *key) { +char* util_decrypt_str(DavSession *sn, const char *str, const char *key) { DavKey *k = dav_context_get_key(sn->context, key); if(!k) { sn->error = DAV_ERROR; - sstr_t err = ucx_sprintf("Key %s not found", key); + cxmutstr err = cx_asprintf("Key %s not found", key); dav_session_set_errstr(sn, err.ptr); free(err.ptr); return NULL; @@ -982,7 +1071,7 @@ char* util_decrypt_str(DavSession *sn, char *str, char *key) { return util_decrypt_str_k(sn, str, k); } -char* util_decrypt_str_k(DavSession *sn, char *str, DavKey *key) { +char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key) { size_t len = 0; char *dec_str = aes_decrypt(str, &len, key); char *ret_str = dav_session_strdup(sn, dec_str); @@ -994,7 +1083,7 @@ char* util_random_str() { unsigned char *str = malloc(25); str[24] = '\0'; - sstr_t t = S( + cxstring t = CX_STR( "01234567890" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); @@ -1018,6 +1107,8 @@ char* util_random_str() { * tokens are separated by space * sets sub to the substring and returns the remaining string */ +// TODO: remove if it isn't used +/* sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) { int i; int token_start = -1; @@ -1057,20 +1148,22 @@ sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) { return str; } } +*/ -sstr_t util_readline(FILE *stream) { - UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); +cxmutstr util_readline(FILE *stream) { + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); int c; while((c = fgetc(stream)) != EOF) { if(c == '\n') { break; } - ucx_buffer_putc(buf, c); + cxBufferPut(&buf, c); } - sstr_t str = sstrdup(sstrtrim(sstrn(buf->space, buf->size))); - ucx_buffer_free(buf); + cxmutstr str = cx_strdup(cx_strtrim(cx_strn(buf.space, buf.size))); + cxBufferDestroy(&buf); return str; } @@ -1081,50 +1174,101 @@ char* util_password_input(char *prompt) { #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"); + if(isatty(fileno(stdin))) { + tcgetattr(fileno(stdin), &oflags); + nflags = oflags; + nflags.c_lflag &= ~ECHO; + nflags.c_lflag |= ECHONL; + if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) { + perror("tcsetattr"); + } } + #endif // read password input - UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); int c = 0; while((c = getpasswordchar()) != EOF) { if(c == '\n' || c == '\r') { break; } - ucx_buffer_putc(buf, c); + cxBufferPut(&buf, c); } - ucx_buffer_putc(buf, 0); + cxBufferPut(&buf, 0); fflush(stdin); #ifndef _WIN32 // restore terminal settings - if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) { + if (isatty(fileno(stdin)) && tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) { perror("tcsetattr"); } #endif - char *str = buf->space; - free(buf); // only free the UcxBuffer struct - return str; + return buf.space; } +int util_exec_command(char *command, CxBuffer *outbuf) { +#ifdef _WIN32 + fprintf(stderr, "util_exec_command unsupported\n"); + return 1; +#else + + int pout[2]; + if(pipe(pout)) { + perror("pipe"); + return 1; + } + + int ret = 0; + + // close stdin and stderr, use pipe for stdout + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addclose(&actions, 0); + posix_spawn_file_actions_adddup2(&actions, pout[1], 1); + posix_spawn_file_actions_addclose(&actions, 2); + + char *args[4]; + args[0] = "sh"; + args[1] = "-c"; + args[2] = command; + args[3] = NULL; + + pid_t pid; // child pid + ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, args, NULL); + + close(pout[1]); + + if(!ret) { + ssize_t r; + char buf[1024]; + while((r = read(pout[0], buf, 1024)) > 0) { + cxBufferWrite(buf, 1, r, outbuf); + } + } + + // wait for child process + ret = 1; + waitpid(pid, &ret, 0); + + posix_spawn_file_actions_destroy(&actions); + close(pout[0]); + + return ret; +#endif +} char* util_hexstr(const unsigned char *data, size_t len) { size_t buflen = 2*len + 4; - UcxBuffer *buf = ucx_buffer_new(malloc(buflen), buflen + 1, 0); + CxBuffer buf; + cxBufferInit(&buf, NULL, buflen + 1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); for(int i=0;ispace; - ucx_buffer_free(buf); - return str; + cxBufferPut(&buf, 0); + return buf.space; } void util_remove_trailing_pathseparator(char *path) { @@ -1159,3 +1303,4 @@ char* util_file_hash(const char *path) { return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); } + diff --git a/libidav/utils.h b/libidav/utils.h index f29138f..8952c76 100644 --- a/libidav/utils.h +++ b/libidav/utils.h @@ -36,23 +36,31 @@ #include #include -#include +#include +#include #include #include #include #include "webdav.h" +#ifdef _WIN32 +#ifndef mode_t +#define mode_t int +#endif +#endif + #ifndef S_IRWXG /* if one is not defined, the others are probably also not defined */ -#define S_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 +#define S_IRWXU 0700 +#define S_IRWXG 070 +#define S_IRGRP 040 +#define S_IWGRP 020 +#define S_IXGRP 010 +#define S_IRWXO 07 +#define S_IROTH 04 +#define S_IWOTH 02 +#define S_IXOTH 01 #endif /* S_IRWXG */ #ifdef __cplusplus @@ -64,12 +72,19 @@ 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_url_base(const char *url); +cxstring util_url_base_s(cxstring url); +const char* util_url_path(const char *url); +cxstring util_url_path_s(cxstring url); +char* util_url_decode(DavSession *sn, const char *url); +const char* util_resource_name(const char *url); +const char* util_resource_name_c(const char *url, char pathseparator); +const char* util_path_file_name(const char *url); + char* util_concat_path(const char *url_base, const char *path); +cxmutstr util_concat_path_s(cxstring url_base, cxstring path); +cxmutstr util_concat_path_ext(cxstring url_base, cxstring path, char separator); +cxmutstr util_concat_sys_path(cxstring base, cxstring path); char* util_get_url(DavSession *sn, const char *href); void util_set_url(DavSession *sn, const char *href); @@ -83,12 +98,14 @@ 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); +void util_capture_header(CURL *handle, CxMap* map); -char* util_path_to_url(DavSession *sn, char *path); +char* util_path_to_url(DavSession *sn, const char *path); char* util_parent_path(const char *path); +char* util_sys_parent_path(const char *path); char* util_size_str(DavBool iscollection, uint64_t contentlength); +char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision); char* util_date_str(time_t tm); int util_getboolean(const char *v); @@ -104,24 +121,27 @@ 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_encrypt_str(DavSession *sn, const char *str, const char *key); +char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key); +char* util_decrypt_str(DavSession *sn, const char *str, const char *key); +char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key); char* util_random_str(); -sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub); +//sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub); -sstr_t util_readline(FILE *stream); +cxmutstr util_readline(FILE *stream); char* util_password_input(char *prompt); +int util_exec_command(char *command, CxBuffer *outbuf); + char* util_hexstr(const unsigned char *data, size_t len); void util_remove_trailing_pathseparator(char *path); char* util_file_hash(const char *path); + #ifdef __cplusplus } #endif diff --git a/libidav/versioning.c b/libidav/versioning.c index 197f3b6..2a7c6c2 100644 --- a/libidav/versioning.c +++ b/libidav/versioning.c @@ -74,32 +74,34 @@ DavResource* dav_versiontree(DavResource *res, char *properties) { DavSession *sn = res->session; util_set_url(sn, dav_resource_get_href(res)); - UcxList *proplist = NULL; + CxList *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; + proplist = parse_properties_string(sn->context, cx_str(properties)); + + // check if the list already contains a D:version-name property + int add_vname = 1; + CxIterator i = cxListIterator(proplist); + cx_foreach(DavProperty *, p, i) { + if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) { + add_vname = 0; + break; + } + } + if(add_vname) { + // we need at least the D:version-name prop + DavProperty p; + p.ns = dav_get_namespace(sn->context, "D"); + p.name = strdup("version-name"); + p.value = NULL; + cxListInsert(proplist, 0, &p); } } - 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); + CxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1); + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); // do the request CURLcode ret = do_report_request(sn, rqbuf, rpbuf); @@ -151,14 +153,14 @@ DavResource* dav_versiontree(DavResource *res, char *properties) { } // cleanup - while(proplist) { - DavProperty *p = proplist->data; - free(p->name); - free(p); - UcxList *next = proplist->next; - free(proplist); - proplist = next; + if(proplist) { + CxIterator i = cxListIterator(proplist); + cx_foreach(DavProperty*, p, i) { + free(p->name); + } + cxListFree(proplist); } + if(error && versions) { DavResource *cur = versions; while(cur) { diff --git a/libidav/webdav.c b/libidav/webdav.c index ed8ceb2..97affd7 100644 --- a/libidav/webdav.c +++ b/libidav/webdav.c @@ -35,8 +35,11 @@ #include "webdav.h" #include "session.h" #include "methods.h" -#include "ucx/buffer.h" -#include "ucx/utils.h" +#include +#include +#include +#include +#include #include "davqlparser.h" #include "davqlexec.h" @@ -47,7 +50,8 @@ DavContext* dav_context_new(void) { if(!context) { return NULL; } - context->sessions = NULL; + context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_ptr, CX_STORE_POINTERS); + cxDefineDestructor(context->sessions, dav_session_destructor); context->http_proxy = calloc(1, sizeof(DavProxy)); if(!context->http_proxy) { dav_context_destroy(context); @@ -58,16 +62,16 @@ DavContext* dav_context_new(void) { dav_context_destroy(context); return NULL; } - context->namespaces = ucx_map_new(16); + context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); if(!context->namespaces) { dav_context_destroy(context); return NULL; } - context->namespaceinfo = ucx_map_new(16); + context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); if(!context->namespaceinfo) { dav_context_destroy(context); } - context->keys = ucx_map_new(16); + context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); if(!context->keys) { dav_context_destroy(context); return NULL; @@ -97,12 +101,9 @@ DavContext* dav_context_new(void) { 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); - } + // ctx->sessions destructor must be dav_session_destructor + cxListFree(ctx->sessions); + if(ctx->http_proxy) { free(ctx->http_proxy); } @@ -111,10 +112,8 @@ void dav_context_destroy(DavContext *ctx) { } if(ctx->namespaces) { - UcxMapIterator i = ucx_map_iterator(ctx->namespaces); - UcxKey k; - DavNamespace *ns; - UCX_MAP_FOREACH(k, ns, i) { + CxIterator i = cxMapIteratorValues(ctx->namespaces); + cx_foreach(DavNamespace*, ns, i) { if(!ns) continue; if(ns->prefix) { free(ns->prefix); @@ -124,16 +123,14 @@ void dav_context_destroy(DavContext *ctx) { } free(ns); } - ucx_map_free(ctx->namespaces); + cxMapFree(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) { + CxIterator i = cxMapIteratorValues(ctx->keys); + cx_foreach(DavKey*, key, i) { if(!key) continue; if(key->name) { free(key->name); @@ -143,21 +140,74 @@ void dav_context_destroy(DavContext *ctx) { } free(key); } - ucx_map_free(ctx->keys); + cxMapFree(ctx->keys); } free(ctx); } +#ifndef _WIN32 + +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) { + if (enable) { + pthread_mutex_init(&ctx->mutex, NULL); + } else { + pthread_mutex_destroy(&ctx->mutex); + } + ctx->mtsafe = enable; +} + +void dav_context_lock(DavContext *ctx) { + if (ctx->mtsafe) { + pthread_mutex_lock(&ctx->mutex); + } +} + +void dav_context_unlock(DavContext *ctx) { + if (ctx->mtsafe) { + pthread_mutex_unlock(&ctx->mutex); + } +} + +#else + +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) { + if (enable) { + ctx->mutex = CreateMutex(NULL, FALSE, NULL); + } else { + CloseHandle(ctx->mutex); + } + ctx->mtsafe = enable; +} + +void dav_context_lock(DavContext *ctx) { + if (ctx->mtsafe) { + WaitForSingleObject(ctx->mutex, INFINITE); + } +} + +void dav_context_unlock(DavContext *ctx) { + if (ctx->mtsafe) { + ReleaseMutex(ctx->mutex); + } +} + +#endif + void dav_context_add_key(DavContext *context, DavKey *key) { - ucx_map_cstr_put(context->keys, key->name, key); + dav_context_lock(context); + cxMapPut(context->keys, cx_hash_key_str(key->name), key); + dav_context_unlock(context); } -DavKey* dav_context_get_key(DavContext *context, char *name) { +DavKey* dav_context_get_key(DavContext *context, const char *name) { + DavKey *key = NULL; + dav_context_lock(context); if(name) { - return ucx_map_cstr_get(context->keys, name); + key = cxMapGet(context->keys, cx_hash_key_str(name)); } - return NULL; + dav_context_unlock(context); + return key; } int dav_add_namespace(DavContext *context, const char *prefix, const char *name) { @@ -167,13 +217,24 @@ int dav_add_namespace(DavContext *context, const char *prefix, const char *name) } char *p = strdup(prefix); + if (!p) { + free(namespace); + return 1; + } char *n = strdup(name); - + if (!n) { + free(namespace); + free(p); + return 1; + } + + dav_context_lock(context); + int err = 0; if(p && n) { namespace->prefix = p; namespace->name = n; - err = ucx_map_cstr_put(context->namespaces, prefix, namespace); + err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace); } if(err) { @@ -181,36 +242,53 @@ int dav_add_namespace(DavContext *context, const char *prefix, const char *name) if(p) free(p); if(n) free(n); } + + dav_context_unlock(context); return err; } DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) { - return ucx_map_cstr_get(context->namespaces, prefix); + dav_context_lock(context); + DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix)); + dav_context_unlock(context); + return ns; } -DavNamespace* dav_get_namespace_s(DavContext *context, sstr_t prefix) { - return ucx_map_sstr_get(context->namespaces, prefix); +DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) { + dav_context_lock(context); + DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length)); + dav_context_unlock(context); + return ns; } int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) { - DavNSInfo *info = ucx_map_cstr_get(context->namespaceinfo, ns); + dav_context_lock(context); + + CxHashKey hkey = cx_hash_key_str(ns); + DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey); if(!info) { info = calloc(1, sizeof(DavNSInfo)); info->encrypt = encrypt; - ucx_map_cstr_put(context->namespaceinfo, ns, info); + cxMapPut(context->namespaceinfo, hkey, info); } else { info->encrypt = encrypt; } + + dav_context_unlock(context); return 0; } int dav_namespace_is_encrypted(DavContext *context, const char *ns) { - DavNSInfo *info = ucx_map_cstr_get(context->namespaceinfo, ns); + int ret = 0; + dav_context_lock(context); + + DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns)); if(info) { - return info->encrypt; + ret = info->encrypt; } - return 0; + dav_context_unlock(context); + return ret; } void dav_get_property_namespace_str( @@ -224,11 +302,11 @@ void dav_get_property_namespace_str( char *pname = strchr(prefixed_name, ':'); char *pns = "DAV:"; if(pname) { - DavNamespace *ns = dav_get_namespace_s( + DavNamespace *davns = dav_get_namespace_s( ctx, - sstrn(prefixed_name, pname-prefixed_name)); - if(ns) { - pns = ns->name; + cx_strn(prefixed_name, pname-prefixed_name)); + if(davns) { + pns = davns->name; pname++; } else { pns = NULL; @@ -250,7 +328,7 @@ DavNamespace* dav_get_property_namespace( if(pname) { DavNamespace *ns = dav_get_namespace_s( ctx, - sstrn(prefixed_name, pname-prefixed_name)); + cx_strn(prefixed_name, pname-prefixed_name)); if(ns) { *name = pname +1; return ns; @@ -260,17 +338,39 @@ DavNamespace* dav_get_property_namespace( } } else { *name = prefixed_name; - return dav_get_namespace_s(ctx, S("D")); + return dav_get_namespace_s(ctx, cx_str("D")); + } +} + +int dav_context_add_session(DavContext *context, DavSession *sn) { + dav_context_lock(context); + int ret = cxListAdd(context->sessions, sn); + dav_context_unlock(context); + return ret; +} + +int dav_context_remove_session(DavContext *context, DavSession *sn) { + int ret = 0; + dav_context_lock(context); + CxList *sessions = context->sessions; + ssize_t i = cxListFind(sessions, sn); + if(i >= 0) { + cxListRemove(sessions, i); + } else { + ret = 1; } + dav_context_unlock(context); + return ret; } + // TODO: add sstr_t version of dav_get_property_ns void dav_set_effective_href(DavSession *sn, DavResource *resource) { char *eff_url; curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url); if(eff_url) { - char *href = util_url_path(eff_url); + const char *href = util_url_path(eff_url); if(strcmp(href, resource->href)) { dav_session_free(sn, resource->href); resource->href = dav_session_strdup(sn, href); @@ -278,17 +378,17 @@ void dav_set_effective_href(DavSession *sn, DavResource *resource) { } } -DavResource* dav_get(DavSession *sn, char *path, char *properties) { +DavResource* dav_get(DavSession *sn, char *path, const char *properties) { CURL *handle = sn->handle; DavResource *resource = dav_resource_new(sn, path); util_set_url(sn, dav_resource_get_href(resource)); - UcxList *proplist = NULL; + CxList *proplist = NULL; if(properties) { - proplist = parse_properties_string(sn->context, sstr(properties)); + proplist = parse_properties_string(sn->context, cx_str(properties)); } - UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0); - UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0); + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); //fwrite(rqbuf->space, 1, rqbuf->size, stdout); //printf("\n"); @@ -310,30 +410,30 @@ DavResource* dav_get(DavSession *sn, char *path, char *properties) { 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; + cxBufferFree(rqbuf); + cxBufferFree(rpbuf); + + if(proplist) { + CxIterator i = cxListIterator(proplist); + cx_foreach(DavProperty*, p, i) { + free(p->name); + } + cxListFree(proplist); } return resource; } -int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf) { +int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) { // clean resource properties DavResourceData *data = root->data; - ucx_map_clear(data->properties); // TODO: free existing content + cxMapClear(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); + CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); DavResource *resource = root; CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); long status = 0; @@ -350,41 +450,40 @@ int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf) { dav_session_set_error(sn, ret, status); error = 1; } - ucx_buffer_free(rpbuf); + cxBufferFree(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); + cxstring nspre = cx_strsubsl(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); + DavProperty dp; + cxstring pre = cx_strtrim(nspre); + dp.ns = dav_get_namespace_s(context, pre); + dp.name = cx_strdup(nsname).ptr; + dp.value = NULL; + if(dp.ns && dp.name) { + cxListAdd(proplist, &dp); } else { - free(dp->name); - free(dp); + free(dp.name); } } - free(s.ptr); } - free(props); + return proplist; } DavResource* dav_query(DavSession *sn, char *query, ...) { - DavQLStatement *stmt = dav_parse_statement(sstr(query)); + DavQLStatement *stmt = dav_parse_statement(cx_str(query)); if(!stmt) { sn->error = DAV_ERROR; return NULL; @@ -401,9 +500,31 @@ DavResource* dav_query(DavSession *sn, char *query, ...) { va_end(ap); dav_free_statement(stmt); + + if(result.status == -1) { + if(result.result) { + dav_resource_free(result.result); + result.result = NULL; + } + } + return result.result; } +void dav_verbose_log( + DavSession *sn, + const char *method, + const char *url, + const char *request_body, + size_t request_bodylen, + int status, + const char *response_body, + size_t response_bodylen) +{ + fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status); + fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body); + fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body); +} diff --git a/libidav/webdav.h b/libidav/webdav.h index eb9a891..3bd60ac 100644 --- a/libidav/webdav.h +++ b/libidav/webdav.h @@ -30,13 +30,21 @@ #define WEBDAV_H #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include +#ifndef _WIN32 +#include +#else +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -49,18 +57,27 @@ typedef char DavBool; #define FALSE 0 #endif -typedef struct DavContext DavContext; -typedef struct DavProxy DavProxy; -typedef struct DavSession DavSession; -typedef struct DavResource DavResource; -typedef struct DavResult DavResult; -typedef struct DavNamespace DavNamespace; -typedef struct DavProperty DavProperty; -typedef struct DavPropName DavPropName; -typedef struct DavKey DavKey; -typedef struct DavNSInfo DavNSInfo; -typedef struct DavXmlNode DavXmlNode; -typedef struct DavXmlAttr DavXmlAttr; +typedef struct DavContext DavContext; +typedef struct DavProxy DavProxy; +typedef struct DavSession DavSession; +typedef struct DavResource DavResource; +typedef struct DavResult DavResult; +typedef struct DavNamespace DavNamespace; +typedef struct DavProperty DavProperty; +typedef struct DavPropName DavPropName; +typedef struct DavKey DavKey; +typedef struct DavNSInfo DavNSInfo; +typedef struct DavXmlNode DavXmlNode; +typedef struct DavXmlAttr DavXmlAttr; + +typedef struct DavInputStream DavInputStream; +typedef struct DavOutputStream DavOutputStream; + +#ifndef _WIN32 +#define DAV_MUTEX pthread_mutex_t +#else +#define DAV_MUTEX HANDLE +#endif typedef size_t(*dav_read_func)(void*, size_t, size_t, void*); typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*); @@ -69,6 +86,17 @@ typedef int(*dav_seek_func)(const void *, long, int); typedef int(*dav_auth_func)(DavSession *, void *); typedef void(*dav_progress_func)(DavResource *, int64_t, int64_t, void *); + +typedef void(*dav_rqlog_func)( + DavSession *sn, + const char *method, + const char *url, + const char *request_body, + size_t request_bodylen, + int status, + const char *response_body, + size_t response_bodylen); + enum DavError { DAV_OK = 0, DAV_ERROR, @@ -145,8 +173,8 @@ struct DavSession { DavContext *context; CURL *handle; char *base_url; - UcxMempool *mp; - UcxMap *pathcache; + CxMempool *mp; + CxMap *pathcache; DavKey *key; void *locks; uint32_t flags; @@ -156,18 +184,22 @@ struct DavSession { int(*auth_prompt)(DavSession *sn, void *userdata); void *authprompt_userdata; + dav_rqlog_func logfunc; + void(*get_progress)(DavResource *res, int64_t total, int64_t now, void *userdata); void(*put_progress)(DavResource *res, int64_t total, int64_t now, void *userdata); void *progress_userdata; }; struct DavContext { - UcxMap *namespaces; - UcxMap *namespaceinfo; - UcxMap *keys; - UcxList *sessions; - DavProxy *http_proxy; - DavProxy *https_proxy; + CxMap *namespaces; + CxMap *namespaceinfo; + CxMap *keys; + CxList *sessions; + DavProxy *http_proxy; + DavProxy *https_proxy; + DAV_MUTEX mutex; + DavBool mtsafe; }; struct DavProxy { @@ -233,23 +265,33 @@ struct DavXmlAttr { DavContext* dav_context_new(void); void dav_context_destroy(DavContext *ctx); +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable); + +void dav_context_lock(DavContext *ctx); +void dav_context_unlock(DavContext *ctx); void dav_context_add_key(DavContext *context, DavKey *key); -DavKey* dav_context_get_key(DavContext *context, char *name); +DavKey* dav_context_get_key(DavContext *context, const char *name); int dav_add_namespace(DavContext *context, const char *prefix, const char *ns); DavNamespace* dav_get_namespace(DavContext *context, const char *prefix); +DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix); int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt); int dav_namespace_is_encrypted(DavContext *context, const char *ns); +int dav_context_add_session(DavContext *context, DavSession *sn); +int dav_context_remove_session(DavContext *context, DavSession *sn); + DavSession* dav_session_new(DavContext *context, char *base_url); DavSession* dav_session_new_auth( DavContext *context, char *base_url, char *user, char *password); -void dav_session_set_auth(DavSession *sn, char *user, char *password); +DavSession* dav_session_clone(DavSession *sn); +void dav_session_set_auth(DavSession *sn, const char *user, const char *password); +void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password); void dav_session_set_baseurl(DavSession *sn, char *base_url); void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags); @@ -258,6 +300,8 @@ void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav void dav_session_destroy(DavSession *sn); +void dav_session_destructor(DavSession *sn); + void* dav_session_malloc(DavSession *sn, size_t size); void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size); void* dav_session_realloc(DavSession *sn, void *ptr, size_t size); @@ -265,13 +309,13 @@ 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); +DavResource* dav_get(DavSession *sn, char *path, const char *properties); -UcxList* parse_properties_string(DavContext *context, sstr_t str); +CxList* parse_properties_string(DavContext *context, cxstring str); DavResource* dav_query(DavSession *sn, char *query, ...); -sstr_t dav_property_key(const char *ns, const char *name); +cxmutstr dav_property_key(const char *ns, const char *name); void dav_get_property_namespace_str( DavContext *ctx, char *prefixed_name, @@ -284,9 +328,9 @@ DavNamespace* dav_get_property_namespace( /* ------------------------ 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); +DavResource* dav_resource_new(DavSession *sn, const char *path); +DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name); +DavResource* dav_resource_new_href(DavSession *sn, const char *href); void dav_resource_free(DavResource *res); void dav_resource_free_all(DavResource *res); @@ -314,7 +358,7 @@ DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *na 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); +int dav_set_string_property(DavResource *res, char *name, char *value); void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value); void dav_set_property(DavResource *res, char *name, DavXmlNode *value); void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value); @@ -335,8 +379,26 @@ int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop); int dav_store(DavResource *res); int dav_get_content(DavResource *res, void *stream, dav_write_func write_func); +DavInputStream* dav_inputstream_open(DavResource *res); +size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in); +void dav_inputstream_close(DavInputStream *in); + +DavOutputStream* dav_outputstream_open(DavResource *res); +size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out); +int dav_outputstream_close(DavOutputStream *out); + +void dav_verbose_log( + DavSession *sn, + const char *method, + const char *url, + const char *request_body, + size_t request_bodylen, + int status, + const char *response_body, + size_t response_bodylen); + // private -int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf); +int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf); /* --------------------------- DeltaV ---------------------------- */ @@ -351,10 +413,14 @@ DavResource* dav_versiontree(DavResource *res, char *properties); 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_text_node(DavSession *sn, const char *text); +DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text); DavXmlNode* dav_copy_node(DavXmlNode *node); +void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node); +void dav_free_xml_node(DavXmlNode *node); + DavXmlNode* dav_xml_createnode(const char *ns, const char *name); DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text); DavXmlNode* dav_xml_createtextnode(const char *text); diff --git a/libidav/xml.c b/libidav/xml.c index 082cfba..32221ad 100644 --- a/libidav/xml.c +++ b/libidav/xml.c @@ -30,7 +30,8 @@ #include #include -#include +#include +#include #include "xml.h" @@ -58,30 +59,34 @@ DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) { return NULL; } - UcxMempool *mp = sn->mp; + const CxAllocator *a = sn->mp->allocator; - ConvXmlElm *ce = malloc(sizeof(ConvXmlElm)); - ce->node = node; - ce->parent = NULL; - UcxList *stack = ucx_list_prepend(NULL, ce); + ConvXmlElm ce; + ce.node = node; + ce.parent = NULL; + CxList *stack = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(ConvXmlElm)); + if(!stack) { + return NULL; + } + cxListInsert(stack, 0, &ce); DavXmlNode *ret = NULL; - while(stack) { - ConvXmlElm *c = stack->data; - stack = ucx_list_remove(stack, stack); - + while(cxListSize(stack) > 0) { + ConvXmlElm *c = cxListAt(stack, 0); xmlNode *n = c->node; + DavXmlNode *c_parent = c->parent; DavXmlNode *prev = NULL; + cxListRemove(stack, 0); while(n) { - DavXmlNode *newxn = ucx_mempool_calloc(mp, 1, sizeof(DavXmlNode)); + DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode)); if(!ret) { ret = newxn; } newxn->type = convert_type(n->type); - newxn->parent = c->parent; - if(c->parent && !c->parent->children) { - c->parent->children = newxn; + newxn->parent = c_parent; + if(c_parent && !c_parent->children) { + c_parent->children = newxn; } newxn->prev = prev; if(prev) { @@ -98,7 +103,7 @@ DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) { DavXmlAttr *newattr = NULL; DavXmlAttr *newattr_last = NULL; while(attr) { - DavXmlAttr *na = ucx_mempool_calloc(mp, 1, sizeof(DavXmlAttr)); + DavXmlAttr *na = cxCalloc(a, 1, sizeof(DavXmlAttr)); na->name = dav_session_strdup(sn, (char*)attr->name); if(attr->children && attr->children->type == XML_TEXT_NODE) { na->value = dav_session_strdup(sn, (char*)attr->children->content); @@ -115,13 +120,13 @@ DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) { newxn->attributes = newattr; if(n->children) { - ConvXmlElm *convc = malloc(sizeof(ConvXmlElm)); - convc->node = n->children; - convc->parent = newxn; - stack = ucx_list_prepend(stack, convc); + ConvXmlElm convc; + convc.node = n->children; + convc.parent = newxn; + cxListInsert(stack, 0, &convc); } } else if(newxn->type == DAV_XML_TEXT) { - sstr_t content = sstrdup_a(mp->allocator, sstr((char*)n->content)); + cxmutstr content = cx_strdup_a(a, cx_str((char*)n->content)); newxn->content = content.ptr; newxn->contentlength = content.length; } @@ -129,8 +134,6 @@ DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) { prev = newxn; n = n->next; } - - free(c); } return ret; @@ -161,19 +164,21 @@ void dav_print_xml(DavXmlNode *node) { } } -void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode *node) { +void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node) { while(node) { if(node->type == DAV_XML_ELEMENT) { char *tagend = node->children ? ">" : " />"; char *prefix = NULL; + char *prefix_fr = NULL; if(node->namespace) { - prefix = ucx_map_cstr_get(nsmap, node->namespace); + prefix = cxMapGet(nsmap, cx_hash_key_str(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); + cxmutstr newpre = cx_asprintf("x%zu", cxMapSize(nsmap)+1); + // TODO: fix + //cxMapPut(nsmap, node->namespace, newpre.ptr); prefix = newpre.ptr; - ucx_fprintf( + prefix_fr = prefix; + cx_fprintf( stream, writef, "<%s:%s xmlns:%s=\"%s\"", @@ -182,15 +187,15 @@ void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode * prefix, node->namespace); } else { - ucx_fprintf(stream, writef, "<%s:%s", prefix, node->name); + cx_fprintf(stream, writef, "<%s:%s", prefix, node->name); } } else { - ucx_fprintf(stream, writef, "<%s", node->name); + cx_fprintf(stream, writef, "<%s", node->name); } DavXmlAttr *attr = node->attributes; while(attr) { - ucx_fprintf(stream, writef, " %s=\"%s\"", attr->name, attr->value); + cx_fprintf(stream, writef, " %s=\"%s\"", attr->name, attr->value); attr = attr->next; } writef(tagend, 1, strlen(tagend), stream); // end xml tag @@ -198,11 +203,15 @@ void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode * if(node->children) { dav_print_node(stream, writef, nsmap, node->children); if(prefix) { - ucx_fprintf(stream, writef, "", prefix, node->name); + cx_fprintf(stream, writef, "", prefix, node->name); } else { - ucx_fprintf(stream, writef, "", node->name); + cx_fprintf(stream, writef, "", node->name); } } + + if(prefix_fr) { + free(prefix_fr); + } } else if(node->type == DAV_XML_TEXT) { writef(node->content, 1, node->contentlength, stream); } @@ -240,16 +249,52 @@ DavXmlNode* dav_xml_nextelm(DavXmlNode *node) { return NULL; } -DavXmlNode* dav_text_node(DavSession *sn, char *text) { - UcxMempool *mp = sn->mp; - DavXmlNode *newxn = ucx_mempool_calloc(mp, 1, sizeof(DavXmlNode)); +DavXmlNode* dav_text_node(DavSession *sn, const char *text) { + const CxAllocator *a = sn->mp->allocator; + DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode)); newxn->type = DAV_XML_TEXT; - sstr_t content = sstrdup_a(mp->allocator, sstr(text)); + cxmutstr content = cx_strdup_a(a, cx_str(text)); newxn->content = content.ptr; newxn->contentlength = content.length; return newxn; } +DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text) { + const CxAllocator *a = sn->mp->allocator; + DavXmlNode *newelm = cxCalloc(a, 1, sizeof(DavXmlNode)); + newelm->type = DAV_XML_ELEMENT; + newelm->namespace = cx_strdup_a(a, cx_str(ns)).ptr; + newelm->name = cx_strdup_a(a, cx_str(name)).ptr; + newelm->children = dav_text_node(sn, text); + return newelm; +} + +static void dav_free_xml_node_a(const CxAllocator *a, DavXmlNode *node) { + if(node->name) cxFree(a, node->name); + if(node->namespace) cxFree(a, node->namespace); + if(node->content) cxFree(a, node->content); + DavXmlAttr *attr = node->attributes; + while(attr) { + if(attr->name) cxFree(a, attr->name); + if(attr->value) cxFree(a, attr->value); + attr = attr->next; + } + DavXmlNode *children = node->children; + while(children) { + DavXmlNode *next_ch = children->next; + dav_free_xml_node_a(a, children); + children = next_ch; + } + cxFree(a, node); +} + +void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node) { + dav_free_xml_node_a(sn->mp->allocator, node); +} + +void dav_free_xml_node(DavXmlNode *node) { + dav_free_xml_node_a(cxDefaultAllocator, node); +} DavXmlAttr* dav_copy_xml_attr(DavXmlAttr *attr) { if(!attr) { @@ -326,7 +371,7 @@ DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const 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)); + cxmutstr content = cx_strdup(cx_str((char*)text)); node->content = content.ptr; node->contentlength = content.length; return node; @@ -352,9 +397,9 @@ void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value) { attr->name = strdup(name); attr->value = strdup(value); - if(node->attributes) { - DavXmlAttr *last; + if(node->attributes) { DavXmlAttr *end = node->attributes; + DavXmlAttr* last = end; while(end) { last = end; end = end->next; diff --git a/libidav/xml.h b/libidav/xml.h index a78449f..834440b 100644 --- a/libidav/xml.h +++ b/libidav/xml.h @@ -38,7 +38,7 @@ 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); +void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node); #ifdef __cplusplus diff --git a/make/cc.mk b/make/cc.mk new file mode 100644 index 0000000..149bde6 --- /dev/null +++ b/make/cc.mk @@ -0,0 +1,14 @@ +# +# cc toolchain config +# + +CFLAGS = +CXXFLAGS = +DEBUG_CC_FLAGS = -g +DEBUG_CXX_FLAGS = -g +RELEASE_CC_FLAGS = -O3 -DNDEBUG +RELEASE_CXX_FLAGS = -O3 -DNDEBUG +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared \ No newline at end of file diff --git a/make/clang.mk b/make/clang.mk index 93e8096..772ebb7 100644 --- a/make/clang.mk +++ b/make/clang.mk @@ -2,8 +2,13 @@ # clang toolchain config # -CFLAGS = -LDFLAGS = +CFLAGS = +CXXFLAGS = +DEBUG_CC_FLAGS = -g +DEBUG_CXX_FLAGS = -g +RELEASE_CC_FLAGS = -O3 -DNDEBUG +RELEASE_CXX_FLAGS = -O3 -DNDEBUG +LDFLAGS = SHLIB_CFLAGS = -fPIC SHLIB_LDFLAGS = -shared diff --git a/make/configure.vm b/make/configure.vm index 54ffdc1..b8acad3 100644 --- a/make/configure.vm +++ b/make/configure.vm @@ -1,48 +1,52 @@ #!/bin/sh -#set( $D = '$' ) -#[[ -# some utility functions -isplatform() -{ - for p in $PLATFORM - do - if [ "$p" = "$1" ]; then - return 0 - fi - done - return 1 -} -notisplatform() -{ - for p in $PLATFORM - do - if [ "$p" = "$1" ]; then - return 1 - fi - done - return 0 -} -istoolchain() -{ - for t in $TOOLCHAIN - do - if [ "$t" = "$1" ]; then - return 0 - fi - done - return 1 -} -notistoolchain() -{ - for t in $TOOLCHAIN - do - if [ "$t" = "$1" ]; then - return 1 - fi - done - return 0 -} +# create temporary directory +TEMP_DIR=".tmp-`uname -n`" +rm -Rf "$TEMP_DIR" +if mkdir -p "$TEMP_DIR"; then + : +else + echo "Cannot create tmp dir $TEMP_DIR" + echo "Abort" + exit 1 +fi +touch "$TEMP_DIR/options" +touch "$TEMP_DIR/features" + +# define standard variables +# also define standard prefix (this is where we will search for config.site) +prefix=/usr +exec_prefix= +bindir= +sbindir= +libdir= +libexecdir= +datarootdir= +datadir= +sysconfdir= +sharedstatedir= +localstatedir= +runstatedir= +includedir= +infodir= +localedir= +mandir= + +# custom variables +#foreach( $var in $vars ) +#if( $var.exec ) +${var.varName}=`${var.value}` +#else +${var.varName}="${var.value}" +#end +#end + +# features +#foreach( $feature in $features ) +#if( ${feature.auto} ) +${feature.varName}=auto +#end +#end # clean abort abort_configure() @@ -51,36 +55,6 @@ abort_configure() exit 1 } -# Test for availability of pkg-config -PKG_CONFIG=`command -v pkg-config` -: ${PKG_CONFIG:="false"} - -# Simple uname based platform detection -# $PLATFORM is used for platform dependent dependency selection -OS=`uname -s` -OS_VERSION=`uname -r` -printf "detect platform... " -if [ "$OS" = "SunOS" ]; then - PLATFORM="solaris sunos unix svr4" -elif [ "$OS" = "Linux" ]; then - PLATFORM="linux unix" -elif [ "$OS" = "FreeBSD" ]; then - PLATFORM="freebsd bsd unix" -elif [ "$OS" = "OpenBSD" ]; then - PLATFORM="openbsd bsd unix" -elif [ "$OS" = "NetBSD" ]; then - PLATFORM="netbsd bsd unix" -elif [ "$OS" = "Darwin" ]; then - PLATFORM="macos osx bsd unix" -elif echo "$OS" | grep -i "MINGW" > /dev/null; then - PLATFORM="windows mingw" -fi -: ${PLATFORM:="unix"} - -PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -` -echo "$PLATFORM_NAME" -]]# - # help text printhelp() { @@ -88,7 +62,7 @@ printhelp() cat << __EOF__ Installation directories: --prefix=PREFIX path prefix for architecture-independent files - [${D}prefix] + [/usr] --exec-prefix=EPREFIX path prefix for architecture-dependent files [PREFIX] @@ -107,90 +81,30 @@ Installation directories: --mandir=DIR man documentation [DATAROOTDIR/man] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] -Build Types: ---debug add extra compile flags for debug builds ---release add extra compile flags for release builds #if( $options.size() > 0 ) - Options: + --debug add extra compile flags for debug builds + --release add extra compile flags for release builds #foreach( $opt in $options ) --${opt.argument}=${opt.valuesString} #end + #end #if( $features.size() > 0 ) - Optional Features: #foreach( $feature in $features ) ${feature.helpText} #end -#end +#end __EOF__ } -# create temporary directory -TEMP_DIR=".tmp-`uname -n`" -rm -Rf "$TEMP_DIR" -if mkdir -p "$TEMP_DIR"; then - : -else - echo "Cannot create tmp dir $TEMP_DIR" - echo "Abort" - exit 1 -fi -touch "$TEMP_DIR/options" -touch "$TEMP_DIR/features" - -# define standard variables -# also define standard prefix (this is where we will search for config.site) -prefix=/usr -exec_prefix= -bindir= -sbindir= -libdir= -libexecdir= -datarootdir= -datadir= -sysconfdir= -sharedstatedir= -localstatedir= -runstatedir= -includedir= -infodir= -localedir= -mandir= - -# custom variables -#foreach( $cfg in $config ) -if true \ -#if( $cfg.platform ) - && isplatform "${cfg.platform}" \ -#end -#foreach( $np in $cfg.notList ) - && notisplatform "${np}" \ -#end - ; then - #foreach( $var in $cfg.vars ) - #if( $var.exec ) - ${var.varName}=`${var.value}` - #else - ${var.varName}="${var.value}" - #end - #end -fi -#end - -# features -#foreach( $feature in $features ) -#if( ${feature.auto} ) -${feature.varName}=auto -#end -#end - # # parse arguments # BUILD_TYPE="default" +#set( $D = '$' ) for ARG in "$@" do case "$ARG" in @@ -209,12 +123,11 @@ do "--infodir="*) infodir=${D}{ARG#--infodir=} ;; "--mandir"*) mandir=${D}{ARG#--mandir} ;; "--localedir"*) localedir=${D}{ARG#--localedir} ;; - "--help"*) printhelp; abort_configure ;; - "--debug") BUILD_TYPE="debug" ;; - "--release") BUILD_TYPE="release" ;; + "--help"*) printhelp; abort_configure ;; + "--debug") BUILD_TYPE="debug" ;; + "--release") BUILD_TYPE="release" ;; #foreach( $opt in $options ) "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;; - "--${opt.argument}") echo "option '$ARG' needs a value:"; echo " $ARG=${opt.valuesString}"; abort_configure ;; #end #foreach( $feature in $features ) "--enable-${feature.arg}") ${feature.varName}=on ;; @@ -261,6 +174,76 @@ elif [ -f "$prefix/etc/config.site" ]; then . "$prefix/etc/config.site" echo ok fi + +# Test for availability of pkg-config +PKG_CONFIG=`command -v pkg-config` +: ${PKG_CONFIG:="false"} + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +OS=`uname -s` +OS_VERSION=`uname -r` +printf "detect platform... " +if [ "$OS" = "SunOS" ]; then + PLATFORM="solaris sunos unix svr4" +elif [ "$OS" = "Linux" ]; then + PLATFORM="linux unix" +elif [ "$OS" = "FreeBSD" ]; then + PLATFORM="freebsd bsd unix" +elif [ "$OS" = "OpenBSD" ]; then + PLATFORM="openbsd bsd unix" +elif [ "$OS" = "NetBSD" ]; then + PLATFORM="netbsd bsd unix" +elif [ "$OS" = "Darwin" ]; then + PLATFORM="macos osx bsd unix" +elif echo "$OS" | grep -i "MINGW" > /dev/null; then + PLATFORM="windows mingw" +fi +: ${PLATFORM:="unix"} + +PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -` +echo "$PLATFORM_NAME" + +isplatform() +{ + for p in $PLATFORM + do + if [ "$p" = "$1" ]; then + return 0 + fi + done + return 1 +} +notisplatform() +{ + for p in $PLATFORM + do + if [ "$p" = "$1" ]; then + return 1 + fi + done + return 0 +} +istoolchain() +{ + for t in $TOOLCHAIN + do + if [ "$t" = "$1" ]; then + return 0 + fi + done + return 1 +} +notistoolchain() +{ + for t in $TOOLCHAIN + do + if [ "$t" = "$1" ]; then + return 1 + fi + done + return 0 +} ]]# ## End of unparsed content ** @@ -411,9 +394,9 @@ DEPENDENCIES_FAILED= ERROR=0 #if( $dependencies.size() > 0 ) # unnamed dependencies -TEMP_CFLAGS="$CFLAGS" -TEMP_CXXFLAGS="$CXXFLAGS" -TEMP_LDFLAGS="$LDFLAGS" +TEMP_CFLAGS= +TEMP_CXXFLAGS= +TEMP_LDFLAGS= #foreach( $dependency in $dependencies ) while true do @@ -571,35 +554,6 @@ if [ -n "${D}${feature.varName}" ]; then unset ${feature.varName} fi fi -if [ -n "${D}${feature.varName}" ]; then - : -#foreach( $def in $feature.defines ) - TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}" - TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}" -#end -#if( $feature.hasMake() ) - cat >> "$TEMP_DIR/make.mk" << __EOF__ -$feature.make -__EOF__ -#end -else - : -#foreach( $def in $feature.disabled.defines ) - TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}" - TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}" -#end -#if( $feature.disabled.hasMake() ) - cat >> "$TEMP_DIR/make.mk" << __EOF__ -$feature.disabled.make -__EOF__ -#end -#foreach( $dependency in $feature.disabled.dependencies ) - if dependency_error_$dependency ; then - DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " - ERROR=1 - fi -#end -fi #end #foreach( $opt in $target.options ) @@ -646,11 +600,6 @@ else DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED" fi #end - else - echo - echo "Invalid option value - usage:" - echo " --${opt.argument}=${opt.valuesString}" - abort_configure fi fi #end diff --git a/make/gcc.mk b/make/gcc.mk index 624bdf1..5eea8e1 100644 --- a/make/gcc.mk +++ b/make/gcc.mk @@ -2,8 +2,13 @@ # gcc toolchain config # -CFLAGS = -LDFLAGS = +CFLAGS = +CXXFLAGS = +DEBUG_CC_FLAGS = -g +DEBUG_CXX_FLAGS = -g +RELEASE_CC_FLAGS = -O3 -DNDEBUG +RELEASE_CXX_FLAGS = -O3 -DNDEBUG +LDFLAGS = SHLIB_CFLAGS = -fPIC SHLIB_LDFLAGS = -shared diff --git a/make/mingw.mk b/make/mingw.mk deleted file mode 100644 index 340102e..0000000 --- a/make/mingw.mk +++ /dev/null @@ -1,46 +0,0 @@ -# -# 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/project.xml b/make/project.xml index c2010cd..21adfa3 100644 --- a/make/project.xml +++ b/make/project.xml @@ -1,5 +1,5 @@ - + c @@ -66,14 +66,6 @@ -DUI_GTK2 -lpthread - - gtk+-2.0 - -DUI_GTK2 -DUI_GTK2LEGACY - -lpthread - - - -DUI_WINUI - diff --git a/make/suncc.mk b/make/suncc.mk index a97fe3c..38cbb87 100644 --- a/make/suncc.mk +++ b/make/suncc.mk @@ -2,8 +2,13 @@ # suncc toolchain # -CFLAGS = -LDFLAGS = +CFLAGS = +CXXFLAGS = +DEBUG_CC_FLAGS = -g +DEBUG_CXX_FLAGS = -g +RELEASE_CC_FLAGS = -O3 -DNDEBUG +RELEASE_CXX_FLAGS = -O3 -DNDEBUG +LDFLAGS = SHLIB_CFLAGS = -Kpic SHLIB_LDFLAGS = -G diff --git a/make/uwproj.xsd b/make/uwproj.xsd index a62ddb2..f702701 100644 --- a/make/uwproj.xsd +++ b/make/uwproj.xsd @@ -3,7 +3,7 @@ xmlns="http://unixwork.de/uwproj" targetNamespace="http://unixwork.de/uwproj" elementFormDefault="qualified" - version="0.3" + version="0.2" > @@ -17,33 +17,22 @@ - + - -

      - The configuration section. - Consists of an arbitrary number of var elements. -

      -

      - The optional platform attribute may specify a single platform identifier and - the optional not attribute may specify a comma-separated list of platform identifiers. - The configure script shall skip this config declaration if the detected platform is not matching - the filter specification of these attributes. -

      + The configuration section. + Consists of an arbitrary number of var elements.
      - -
      @@ -196,9 +185,6 @@ dependencies are satisfied. If a feature is enabled, all define and make definitions are supposed to be applied to the config file. - If a feature is disabled, an optional disabled element may specify which - define and make definitions are supposed to be applied. - There might also be dependencies when the feature is disabled (e.g. specifying a fallback). In case the optional default attribute is set to true, the feature is enabled by default and is supposed to be automatically disabled (without error) when the dependencies are not satisfied. The name that is supposed to be used for the --enable and --disable arguments can be optionally @@ -209,18 +195,11 @@ - - - - - - - - + diff --git a/make/windows.mk b/make/windows.mk deleted file mode 100644 index 2f3bc72..0000000 --- a/make/windows.mk +++ /dev/null @@ -1,42 +0,0 @@ -# -# 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 index 5e816f3..faf1790 100644 --- a/mizucp/Makefile +++ b/mizucp/Makefile @@ -40,7 +40,7 @@ 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) + $(CC) -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 index f590dc7..c4436aa 100644 --- a/mizucp/main.c +++ b/mizucp/main.c @@ -42,7 +42,7 @@ #include -#include +#include #define OPTSTR "hlpsuv" @@ -255,7 +255,7 @@ int mzcp_start_scan(CPSettings *settings) { void* scan_run(void *data) { CPSettings *settings = data; - UcxList *stack = NULL; + CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); char *root = strdup(""); @@ -274,13 +274,9 @@ void* scan_run(void *data) { return NULL; } - stack = ucx_list_prepend(NULL, file); - while(stack) { - SrcFile *elm = stack->data; - UcxList *next = stack->next; - free(stack); - stack = next; - + cxListInsert(stack, 0, file); + SrcFile *elm; + while(!cxListRemoveAndGet(stack, 0, &elm)) { char *path = util_concat_path(settings->from, elm->path); int dir_fd = open(path, O_RDONLY); @@ -323,12 +319,13 @@ void* scan_run(void *data) { // put dir on stack if(f->isdir) { - stack = ucx_list_prepend(stack, f); + cxListInsert(stack, 0, f); } } closedir(dir); } + cxListFree(stack); scan_complete = 1; diff --git a/mizucp/main.h b/mizucp/main.h index e5537e5..feac930 100644 --- a/mizucp/main.h +++ b/mizucp/main.h @@ -27,7 +27,7 @@ #include #include -#include +#include #include diff --git a/mizucp/srvctrl.c b/mizucp/srvctrl.c index 8c2d089..9ef822b 100644 --- a/mizucp/srvctrl.c +++ b/mizucp/srvctrl.c @@ -1,5 +1,5 @@ /* - * Copyright 2019 Olaf Wintermann + * Copyright 2022 Olaf Wintermann * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -35,7 +35,7 @@ #include #include -#include +#include #include #define TIMEOUT_IDLE -1 @@ -52,7 +52,7 @@ int create_control_socket(void) { // create unix domain socket char *random_str = util_random_str(); - sstr_t socketp = ucx_sprintf("%s/%.*s", copydir, 8, random_str); + cxmutstr socketp = cx_asprintf("%s/%.*s", copydir, 8, random_str); free(random_str); socket_path = socketp.ptr; @@ -68,6 +68,8 @@ int create_control_socket(void) { addr.sun_family = AF_UNIX; memcpy(addr.sun_path, socketp.ptr, socketp.length); + free(socketp.ptr); + srvctrl = socket(AF_UNIX, SOCK_STREAM, 0); if(srvctrl == -1) { fprintf(stderr, @@ -205,7 +207,7 @@ int handle_messages(CtrlClient *client) { int msgstart = 0; for(int i=0;ipos;i++) { if(client->buf[i] == '\n') { - sstr_t msg; + cxstring msg; msg.ptr = &client->buf[msgstart]; msg.length = i - msgstart; msgstart = i+1; @@ -226,10 +228,10 @@ int handle_messages(CtrlClient *client) { return 0; } -int handle_client_msg(CtrlClient *client, sstr_t msg) { +int handle_client_msg(CtrlClient *client, cxstring msg) { printf("msg: %.*s\n", (int)msg.length, msg.ptr); - if(!sstrcmp(msg, S("abort"))) { + if(!cx_strcmp(msg, CX_STR("abort"))) { return -1; } diff --git a/mizucp/srvctrl.h b/mizucp/srvctrl.h index 9c78d40..5746bd0 100644 --- a/mizucp/srvctrl.h +++ b/mizucp/srvctrl.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Olaf Wintermann + * Copyright 2022 Olaf Wintermann * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -46,7 +46,7 @@ int mzcp_srvctrl(CPSettings *settings); void client_free(CtrlClient *client); int handle_messages(CtrlClient *client); -int handle_client_msg(CtrlClient *client, sstr_t msg); +int handle_client_msg(CtrlClient *client, cxstring msg); void client_send_status(CtrlClient *client); diff --git a/mizunara/Makefile b/mizunara/Makefile index 136b523..2f8d5e3 100644 --- a/mizunara/Makefile +++ b/mizunara/Makefile @@ -31,8 +31,11 @@ include $(BUILD_ROOT)/config.mk CFLAGS += -I../ui/ -I../ucx -I.. -SRC = main.c +SRC = main.c +SRC += application.c SRC += menu.c +SRC += window.c +SRC += filebrowser.c SRC += $(MZUI) @@ -41,7 +44,7 @@ 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) + $(CC) -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/application.c b/mizunara/application.c new file mode 100644 index 0000000..f0a29da --- /dev/null +++ b/mizunara/application.c @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "application.h" + +#include "menu.h" +#include "window.h" + +void application_init(void) { + menu_init(); +} + +void application_startup(UiEvent* event, void* data) { + UiObject *win = window_create(NULL); + ui_show(win); +} diff --git a/mizunara/application.h b/mizunara/application.h new file mode 100644 index 0000000..7fd12bc --- /dev/null +++ b/mizunara/application.h @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MZ_APPLICATION_H +#define MZ_APPLICATION_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * main window data + */ +typedef struct MainWindow { + int a; +} MainWindow; + +/* + * main window document object + * later, the MainWindow will contain multiple documents (document tabview) + */ +typedef struct FileBrowser { + UiContext *ctx; +} FileBrowser; + + +void application_init(void); + +/* + * startup callback for the ui framework + */ +void application_startup(UiEvent* event, void* data); + + +#ifdef __cplusplus +} +#endif + +#endif /* MZ_APPLICATION_H */ + diff --git a/ui/motif/dnd.c b/mizunara/filebrowser.c similarity index 79% rename from ui/motif/dnd.c rename to mizunara/filebrowser.c index 2f2313e..4059ccd 100644 --- a/ui/motif/dnd.c +++ b/mizunara/filebrowser.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,20 +26,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "dnd.h" +#include "filebrowser.h" -void ui_selection_settext(UiSelection *sel, char *str, int len) { - -} -void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) { +FileBrowser* filebrowser_new(const char *url) { + FileBrowser *browser = ui_document_new(sizeof(FileBrowser)); + UiContext *ctx = ui_document_context(browser); + browser->ctx = ctx; -} - -char* ui_selection_gettext(UiSelection *sel) { - return NULL; -} - -char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { - return NULL; + return browser; } diff --git a/ui/motif/range.h b/mizunara/filebrowser.h similarity index 75% rename from ui/motif/range.h rename to mizunara/filebrowser.h index 1c6da04..482c78a 100644 --- a/ui/motif/range.h +++ b/mizunara/filebrowser.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,26 +26,21 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RANGE_H -#define RANGE_H +#ifndef MZ_FILEBROWSER_H +#define MZ_FILEBROWSER_H -#include "toolkit.h" -#include "../ui/range.h" +#include "application.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); +FileBrowser* filebrowser_new(const char *url); #ifdef __cplusplus } #endif -#endif /* RANGE_H */ +#endif /* MZ_FILEBROWSER_H */ diff --git a/mizunara/main.c b/mizunara/main.c index 9500f65..7ba86dd 100644 --- a/mizunara/main.c +++ b/mizunara/main.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2021 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,27 +30,21 @@ #include #include -#include -#include +#include "application.h" #include "menu.h" -void application_startup(UiEvent *event, void *data) { - setup_menu(); - - UiObject *obj = ui_window("Test", NULL); - - - ui_show(obj); -} int main(int argc, char** argv) { - ui_init("app1", argc, argv); + ui_init("mizunara", argc, argv); + + application_init(); ui_onstartup(application_startup, NULL); - + + ui_main(); - - return (EXIT_SUCCESS); + + return 0; } diff --git a/ui/motif/dnd.h b/mizunara/main.h similarity index 92% rename from ui/motif/dnd.h rename to mizunara/main.h index 3653692..08df683 100644 --- a/ui/motif/dnd.h +++ b/mizunara/main.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,10 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DND_H -#define DND_H - -#include "../ui/dnd.h" +#ifndef MZ_MAIN_H +#define MZ_MAIN_H #ifdef __cplusplus extern "C" { @@ -42,5 +40,5 @@ extern "C" { } #endif -#endif /* DND_H */ +#endif /* MZ_MAIN_H */ diff --git a/mizunara/menu.c b/mizunara/menu.c index 2ba886b..08873dc 100644 --- a/mizunara/menu.c +++ b/mizunara/menu.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2021 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,13 +32,33 @@ #include #include -static void action_menu(UiEvent *event, void *userdata) { - +static void menubar_init(void); +static void toolbar_init(void); +static void appmenu_init(void); + +void menu_init(void) { + menubar_init(); + toolbar_init(); + appmenu_init(); } +static void menubar_init(void) { + ui_menu("File") { + ui_menuitem(.label = "New Window"); + ui_menuseparator(); + ui_menuitem(.label = "Close"); + } +} +static void toolbar_init(void) { + ui_toolbar_item("Home", .icon = UI_ICON_HOME); + + ui_toolbar_add_default("Home", UI_TOOLBAR_LEFT); +} -void setup_menu(void) { - ui_menu("File"); - ui_menuitem("Test", action_menu, NULL); +static void appmenu_init(void) { + ui_toolbar_appmenu() { + ui_menuitem("New Window"); + ui_menuseparator(); + } } diff --git a/mizunara/menu.h b/mizunara/menu.h index 1aba01e..7126c4c 100644 --- a/mizunara/menu.h +++ b/mizunara/menu.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2021 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,6 +25,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ + #ifndef MZ_MENU_H #define MZ_MENU_H @@ -34,8 +35,11 @@ extern "C" { #endif -void setup_menu(void); - +/* + * main menu/toolbar initialization + */ + +void menu_init(void); #ifdef __cplusplus } diff --git a/mizunara/window.c b/mizunara/window.c new file mode 100644 index 0000000..1c627f9 --- /dev/null +++ b/mizunara/window.c @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "window.h" + +#include "filebrowser.h" + +UiObject* window_create(const char *url) { + // toplevel window object + UiObject *obj = ui_sidebar_window("Mizunara", NULL); + + // create window data object + MainWindow *wdata = ui_malloc(obj->ctx, sizeof(MainWindow)); + memset(wdata, 0, sizeof(MainWindow)); + obj->window = wdata; + + // create browser document + // TODO: in the future we want multiple documents per window (tabs) + FileBrowser *browser = filebrowser_new(url); + ui_attach_document(obj->ctx, browser); + + return obj; +} diff --git a/ui/motif/label.h b/mizunara/window.h similarity index 86% rename from ui/motif/label.h rename to mizunara/window.h index adc644c..20ee458 100644 --- a/ui/motif/label.h +++ b/mizunara/window.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,19 +26,21 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LABEL_H -#define LABEL_H +#ifndef MZ_WINDOW_H +#define MZ_WINDOW_H -#ifdef __cplusplus +#include "application.h" + +#ifdef __cplusplus extern "C" { #endif +UiObject* window_create(const char *url); - -#ifdef __cplusplus +#ifdef __cplusplus } #endif -#endif /* LABEL_H */ +#endif /* MZ_WINDOW_H */ diff --git a/ucx/Makefile b/ucx/Makefile index 2369197..e24476e 100644 --- a/ucx/Makefile +++ b/ucx/Makefile @@ -26,28 +26,30 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT = ../ include ../config.mk # list of source files -SRC = utils.c +SRC = allocator.c +SRC += array_list.c +SRC += mempool.c +SRC += buffer.c +SRC += compare.c +SRC += hash_key.c +SRC += hash_map.c +SRC += iterator.c +SRC += linked_list.c SRC += list.c SRC += map.c -SRC += avl.c -SRC += properties.c -SRC += mempool.c +SRC += printf.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 +SRC += tree.c +SRC += streams.c +SRC += properties.c +SRC += json.c -OBJ = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT)) +OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT)) -UCX_LIB = ../build/lib/libucx.$(LIB_EXT) +UCX_LIB = ../build/lib/libucx$(LIB_EXT) all: ../build/ucx $(UCX_LIB) @@ -57,6 +59,6 @@ $(UCX_LIB): $(OBJ) ../build/ucx: mkdir -p ../build/ucx -../build/ucx/%.$(OBJ_EXT): %.c +../build/ucx/%$(OBJ_EXT): %.c $(CC) $(CFLAGS) -o $@ -c $< diff --git a/ucx/allocator.c b/ucx/allocator.c index 22a5cd5..e3a967e 100644 --- a/ucx/allocator.c +++ b/ucx/allocator.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,35 +26,162 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx/allocator.h" +#include "cx/allocator.h" -#include +#include -static UcxAllocator default_allocator = { - NULL, - ucx_default_malloc, - ucx_default_calloc, - ucx_default_realloc, - ucx_default_free +static void *cx_malloc_stdlib( + cx_attr_unused void *d, + size_t n +) { + return malloc(n); +} + +static void *cx_realloc_stdlib( + cx_attr_unused void *d, + void *mem, + size_t n +) { + return realloc(mem, n); +} + +static void *cx_calloc_stdlib( + cx_attr_unused void *d, + size_t nelem, + size_t n +) { + return calloc(nelem, n); +} + +static void cx_free_stdlib( + cx_attr_unused void *d, + void *mem +) { + free(mem); +} + +static cx_allocator_class cx_default_allocator_class = { + cx_malloc_stdlib, + cx_realloc_stdlib, + cx_calloc_stdlib, + cx_free_stdlib +}; + +struct cx_allocator_s cx_default_allocator = { + &cx_default_allocator_class, + NULL }; +CxAllocator *cxDefaultAllocator = &cx_default_allocator; -UcxAllocator *ucx_default_allocator() { - UcxAllocator *allocator = &default_allocator; - return allocator; +#undef cx_reallocate +int cx_reallocate( + void **mem, + size_t n +) { + void *nmem = realloc(*mem, n); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } } -void *ucx_default_malloc(void *ignore, size_t n) { - return malloc(n); +#undef cx_reallocatearray +int cx_reallocatearray( + void **mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return 1; + } else { + void *nmem = realloc(*mem, n); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } + } +} + +// IMPLEMENTATION OF HIGH LEVEL API + +void *cxMalloc( + const CxAllocator *allocator, + size_t n +) { + return allocator->cl->malloc(allocator->data, n); +} + +void *cxRealloc( + const CxAllocator *allocator, + void *mem, + size_t n +) { + return allocator->cl->realloc(allocator->data, mem, n); +} + +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +) { + size_t n; + if (cx_szmul(nmemb, size, &n)) { + errno = EOVERFLOW; + return NULL; + } else { + return allocator->cl->realloc(allocator->data, mem, n); + } +} + +#undef cxReallocate +int cxReallocate( + const CxAllocator *allocator, + void **mem, + size_t n +) { + void *nmem = allocator->cl->realloc(allocator->data, *mem, n); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } } -void *ucx_default_calloc(void *ignore, size_t n, size_t size) { - return calloc(n, size); +#undef cxReallocateArray +int cxReallocateArray( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +) { + void *nmem = cxReallocArray(allocator, *mem, nmemb, size); + if (nmem == NULL) { + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } } -void *ucx_default_realloc(void *ignore, void *data, size_t n) { - return realloc(data, n); +void *cxCalloc( + const CxAllocator *allocator, + size_t nelem, + size_t n +) { + return allocator->cl->calloc(allocator->data, nelem, n); } -void ucx_default_free(void *ignore, void *data) { - free(data); +void cxFree( + const CxAllocator *allocator, + void *mem +) { + allocator->cl->free(allocator->data, mem); } diff --git a/ucx/array_list.c b/ucx/array_list.c new file mode 100644 index 0000000..96b514f --- /dev/null +++ b/ucx/array_list.c @@ -0,0 +1,1041 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/array_list.h" +#include "cx/compare.h" +#include +#include +#include + +// Default array reallocator + +static void *cx_array_default_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + return realloc(array, n); +} + +CxArrayReallocator cx_array_default_reallocator_impl = { + cx_array_default_realloc, NULL, NULL, 0, 0 +}; + +CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; + +// Stack-aware array reallocator + +static void *cx_array_advanced_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + // check for overflow + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + + // retrieve the pointer to the actual allocator + const CxAllocator *al = alloc->ptr1; + + // check if the array is still located on the stack + void *newmem; + if (array == alloc->ptr2) { + newmem = cxMalloc(al, n); + if (newmem != NULL && array != NULL) { + memcpy(newmem, array, n); + } + } else { + newmem = cxRealloc(al, array, n); + } + return newmem; +} + +struct cx_array_reallocator_s cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + return (struct cx_array_reallocator_s) { + cx_array_advanced_realloc, + (void*) allocator, (void*) stackmem, + 0, 0 + }; +} + +// LOW LEVEL ARRAY LIST FUNCTIONS + +static size_t cx_array_align_capacity( + size_t cap, + size_t alignment, + size_t max +) { + if (cap > max - alignment) { + return cap; + } else { + return cap - (cap % alignment) + alignment; + } +} + +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + // assert pointers + assert(array != NULL); + assert(size != NULL); + assert(capacity != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*array != NULL || oldcap == 0); + + // check for overflow + if (elem_count > max_size - oldsize) { + errno = EOVERFLOW; + return 1; + } + + // determine new capacity + size_t newcap = oldsize + elem_count; + + // reallocate if possible + if (newcap > oldcap) { + // calculate new capacity (next number divisible by 16) + newcap = cx_array_align_capacity(newcap, 16, max_size); + + // perform reallocation + void *newmem = reallocator->realloc( + *array, newcap, elem_size, reallocator + ); + if (newmem == NULL) { + return 1; // LCOV_EXCL_LINE + } + + // store new pointer + *array = newmem; + + // store new capacity + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + } +#endif + } + + return 0; +} + +int cx_array_copy( + void **target, + void *size, + void *capacity, + unsigned width, + size_t index, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + // assert pointers + assert(target != NULL); + assert(size != NULL); + assert(capacity != NULL); + assert(src != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // determine size and capacity + size_t oldcap; + size_t oldsize; + size_t max_size; + if (width == 0 || width == sizeof(size_t)) { + oldcap = *(size_t*) capacity; + oldsize = *(size_t*) size; + max_size = SIZE_MAX; + } else if (width == sizeof(uint16_t)) { + oldcap = *(uint16_t*) capacity; + oldsize = *(uint16_t*) size; + max_size = UINT16_MAX; + } else if (width == sizeof(uint8_t)) { + oldcap = *(uint8_t*) capacity; + oldsize = *(uint8_t*) size; + max_size = UINT8_MAX; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + oldcap = *(uint32_t*) capacity; + oldsize = *(uint32_t*) size; + max_size = UINT32_MAX; + } +#endif + else { + errno = EINVAL; + return 1; + } + + // assert that the array is allocated when it has capacity + assert(*target != NULL || oldcap == 0); + + // check for overflow + if (index > max_size || elem_count > max_size - index) { + errno = EOVERFLOW; + return 1; + } + + // check if resize is required + size_t minsize = index + elem_count; + size_t newsize = oldsize < minsize ? minsize : oldsize; + + // reallocate if possible + size_t newcap = oldcap; + if (newsize > oldcap) { + // check, if we need to repair the src pointer + uintptr_t targetaddr = (uintptr_t) *target; + uintptr_t srcaddr = (uintptr_t) src; + bool repairsrc = targetaddr <= srcaddr + && srcaddr < targetaddr + oldcap * elem_size; + + // calculate new capacity (next number divisible by 16) + newcap = cx_array_align_capacity(newsize, 16, max_size); + assert(newcap > newsize); + + // perform reallocation + void *newmem = reallocator->realloc( + *target, newcap, elem_size, reallocator + ); + if (newmem == NULL) { + return 1; + } + + // repair src pointer, if necessary + if (repairsrc) { + src = ((char *) newmem) + (srcaddr - targetaddr); + } + + // store new pointer + *target = newmem; + } + + // determine target pointer + char *start = *target; + start += index * elem_size; + + // copy elements and set new size + // note: no overflow check here, b/c we cannot get here w/o allocation + memmove(start, src, elem_count * elem_size); + + // if any of size or capacity changed, store them back + if (newsize != oldsize || newcap != oldcap) { + if (width == 0 || width == sizeof(size_t)) { + *(size_t*) capacity = newcap; + *(size_t*) size = newsize; + } else if (width == sizeof(uint16_t)) { + *(uint16_t*) capacity = (uint16_t) newcap; + *(uint16_t*) size = (uint16_t) newsize; + } else if (width == sizeof(uint8_t)) { + *(uint8_t*) capacity = (uint8_t) newcap; + *(uint8_t*) size = (uint8_t) newsize; + } +#if CX_WORDSIZE == 64 + else if (width == sizeof(uint32_t)) { + *(uint32_t*) capacity = (uint32_t) newcap; + *(uint32_t*) size = (uint32_t) newsize; + } +#endif + } + + // return successfully + return 0; +} + +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + // assert pointers + assert(target != NULL); + assert(size != NULL); + assert(capacity != NULL); + assert(cmp_func != NULL); + assert(sorted_data != NULL); + + // default reallocator + if (reallocator == NULL) { + reallocator = cx_array_default_reallocator; + } + + // corner case + if (elem_count == 0) return 0; + + // overflow check + if (elem_count > SIZE_MAX - *size) { + errno = EOVERFLOW; + return 1; + } + + // store some counts + size_t old_size = *size; + size_t needed_capacity = old_size + elem_count; + + // if we need more than we have, try a reallocation + if (needed_capacity > *capacity) { + size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); + void *new_mem = reallocator->realloc( + *target, new_capacity, elem_size, reallocator + ); + if (new_mem == NULL) { + // give it up right away, there is no contract + // that requires us to insert as much as we can + return 1; // LCOV_EXCL_LINE + } + *target = new_mem; + *capacity = new_capacity; + } + + // now we have guaranteed that we can insert everything + size_t new_size = old_size + elem_count; + *size = new_size; + + // declare the source and destination indices/pointers + size_t si = 0, di = 0; + const char *src = sorted_data; + char *dest = *target; + + // find the first insertion point + di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func); + dest += di * elem_size; + + // move the remaining elements in the array completely to the right + // we will call it the "buffer" for parked elements + size_t buf_size = old_size - di; + size_t bi = new_size - buf_size; + char *bptr = ((char *) *target) + bi * elem_size; + memmove(bptr, dest, buf_size * elem_size); + + // while there are both source and buffered elements left, + // copy them interleaving + while (si < elem_count && bi < new_size) { + // determine how many source elements can be inserted + size_t copy_len, bytes_copied; + copy_len = cx_array_binary_search_sup( + src, + elem_count - si, + elem_size, + bptr, + cmp_func + ); + + // copy the source elements + bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied; + si += copy_len; + + // when all source elements are in place, we are done + if (si >= elem_count) break; + + // determine how many buffered elements need to be restored + copy_len = cx_array_binary_search_sup( + bptr, + new_size - bi, + elem_size, + src, + cmp_func + ); + + // restore the buffered elements + bytes_copied = copy_len * elem_size; + memmove(dest, bptr, bytes_copied); + dest += bytes_copied; + bptr += bytes_copied; + bi += copy_len; + } + + // still source elements left? simply append them + if (si < elem_count) { + memcpy(dest, src, elem_size * (elem_count - si)); + } + + // still buffer elements left? + // don't worry, we already moved them to the correct place + + return 0; +} + +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + // special case: empty array + if (size == 0) return 0; + + // declare a variable that will contain the compare results + int result; + + // cast the array pointer to something we can use offsets with + const char *array = arr; + + // check the first array element + result = cmp_func(elem, array); + if (result < 0) { + return size; + } else if (result == 0) { + return 0; + } + + // special case: there is only one element and that is smaller + if (size == 1) return 0; + + // check the last array element + result = cmp_func(elem, array + elem_size * (size - 1)); + if (result >= 0) { + return size - 1; + } + + // the element is now guaranteed to be somewhere in the list + // so start the binary search + size_t left_index = 1; + size_t right_index = size - 1; + size_t pivot_index; + + while (left_index <= right_index) { + pivot_index = left_index + (right_index - left_index) / 2; + const char *arr_elem = array + pivot_index * elem_size; + result = cmp_func(elem, arr_elem); + if (result == 0) { + // found it! + return pivot_index; + } else if (result < 0) { + // element is smaller than pivot, continue search left + right_index = pivot_index - 1; + } else { + // element is larger than pivot, continue search right + left_index = pivot_index + 1; + } + } + + // report the largest upper bound + return result < 0 ? (pivot_index - 1) : pivot_index; +} + +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t index = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (index < size && + cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { + return index; + } else { + return size; + } +} + +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t inf = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (inf == size) { + // no infimum means, first element is supremum + return 0; + } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { + return inf; + } else { + return inf + 1; + } +} + +#ifndef CX_ARRAY_SWAP_SBO_SIZE +#define CX_ARRAY_SWAP_SBO_SIZE 128 +#endif +const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; + +void cx_array_swap( + void *arr, + size_t elem_size, + size_t idx1, + size_t idx2 +) { + assert(arr != NULL); + + // short circuit + if (idx1 == idx2) return; + + char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE]; + void *tmp; + + // decide if we can use the local buffer + if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) { + tmp = malloc(elem_size); + // we don't want to enforce error handling + if (tmp == NULL) abort(); + } else { + tmp = sbo_mem; + } + + // calculate memory locations + char *left = arr, *right = arr; + left += idx1 * elem_size; + right += idx2 * elem_size; + + // three-way swap + memcpy(tmp, left, elem_size); + memcpy(left, right, elem_size); + memcpy(right, tmp, elem_size); + + // free dynamic memory, if it was needed + if (tmp != sbo_mem) { + free(tmp); + } +} + +// HIGH LEVEL ARRAY LIST FUNCTIONS + +typedef struct { + struct cx_list_s base; + void *data; + size_t capacity; + CxArrayReallocator reallocator; +} cx_array_list; + +static void cx_arl_destructor(struct cx_list_s *list) { + cx_array_list *arl = (cx_array_list *) list; + + char *ptr = arl->data; + + if (list->collection.simple_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { + cx_invoke_simple_destructor(list, ptr); + ptr += list->collection.elem_size; + } + } + if (list->collection.advanced_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { + cx_invoke_advanced_destructor(list, ptr); + ptr += list->collection.elem_size; + } + } + + cxFree(list->collection.allocator, arl->data); + cxFree(list->collection.allocator, list); +} + +static size_t cx_arl_insert_array( + struct cx_list_s *list, + size_t index, + const void *array, + size_t n +) { + // out of bounds and special case check + if (index > list->collection.size || n == 0) return 0; + + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + // do we need to move some elements? + if (index < list->collection.size) { + const char *first_to_move = (const char *) arl->data; + first_to_move += index * list->collection.elem_size; + size_t elems_to_move = list->collection.size - index; + size_t start_of_moved = index + n; + + if (cx_array_copy( + &arl->data, + &list->collection.size, + &arl->capacity, + 0, + start_of_moved, + first_to_move, + list->collection.elem_size, + elems_to_move, + &arl->reallocator + )) { + // if moving existing elems is unsuccessful, abort + return 0; + } + } + + // note that if we had to move the elements, the following operation + // is guaranteed to succeed, because we have the memory already allocated + // therefore, it is impossible to leave this function with an invalid array + + // place the new elements + if (cx_array_copy( + &arl->data, + &list->collection.size, + &arl->capacity, + 0, + index, + array, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; + } +} + +static size_t cx_arl_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + if (cx_array_insert_sorted( + &arl->data, + &list->collection.size, + &arl->capacity, + list->collection.cmpfunc, + sorted_data, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; + } +} + +static int cx_arl_insert_element( + struct cx_list_s *list, + size_t index, + const void *element +) { + return 1 != cx_arl_insert_array(list, index, element, 1); +} + +static int cx_arl_insert_iter( + struct cx_iterator_s *iter, + const void *elem, + int prepend +) { + struct cx_list_s *list = iter->src_handle.m; + if (iter->index < list->collection.size) { + int result = cx_arl_insert_element( + list, + iter->index + 1 - prepend, + elem + ); + if (result == 0) { + iter->elem_count++; + if (prepend != 0) { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; + } + } + return result; + } else { + int result = cx_arl_insert_element(list, list->collection.size, elem); + if (result == 0) { + iter->elem_count++; + iter->index = list->collection.size; + } + return result; + } +} + +static size_t cx_arl_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + cx_array_list *arl = (cx_array_list *) list; + + // out-of-bounds check + size_t remove; + if (index >= list->collection.size) { + remove = 0; + } else if (index + num > list->collection.size) { + remove = list->collection.size - index; + } else { + remove = num; + } + + // easy exit + if (remove == 0) return 0; + + // destroy or copy contents + if (targetbuf == NULL) { + for (size_t idx = index; idx < index + remove; idx++) { + cx_invoke_destructor( + list, + ((char *) arl->data) + idx * list->collection.elem_size + ); + } + } else { + memcpy( + targetbuf, + ((char *) arl->data) + index * list->collection.elem_size, + remove * list->collection.elem_size + ); + } + + // short-circuit removal of last elements + if (index + remove == list->collection.size) { + list->collection.size -= remove; + return remove; + } + + // just move the elements to the left + cx_array_copy( + &arl->data, + &list->collection.size, + &arl->capacity, + 0, + index, + ((char *) arl->data) + (index + remove) * list->collection.elem_size, + list->collection.elem_size, + list->collection.size - index - remove, + &arl->reallocator + ); + + // decrease the size + list->collection.size -= remove; + + return remove; +} + +static void cx_arl_clear(struct cx_list_s *list) { + if (list->collection.size == 0) return; + + cx_array_list *arl = (cx_array_list *) list; + char *ptr = arl->data; + + if (list->collection.simple_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { + cx_invoke_simple_destructor(list, ptr); + ptr += list->collection.elem_size; + } + } + if (list->collection.advanced_destructor) { + for (size_t i = 0; i < list->collection.size; i++) { + cx_invoke_advanced_destructor(list, ptr); + ptr += list->collection.elem_size; + } + } + + memset(arl->data, 0, list->collection.size * list->collection.elem_size); + list->collection.size = 0; +} + +static int cx_arl_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + if (i >= list->collection.size || j >= list->collection.size) return 1; + cx_array_list *arl = (cx_array_list *) list; + cx_array_swap(arl->data, list->collection.elem_size, i, j); + return 0; +} + +static void *cx_arl_at( + const struct cx_list_s *list, + size_t index +) { + if (index < list->collection.size) { + const cx_array_list *arl = (const cx_array_list *) list; + char *space = arl->data; + return space + index * list->collection.elem_size; + } else { + return NULL; + } +} + +static ssize_t cx_arl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + assert(list->collection.cmpfunc != NULL); + assert(list->collection.size < SIZE_MAX / 2); + char *cur = ((const cx_array_list *) list)->data; + + for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) { + if (0 == list->collection.cmpfunc(elem, cur)) { + if (remove) { + if (1 == cx_arl_remove(list, i, 1, NULL)) { + return i; + } else { + // should be unreachable + return -1; // LCOV_EXCL_LINE + } + } else { + return i; + } + } + cur += list->collection.elem_size; + } + + return -1; +} + +static void cx_arl_sort(struct cx_list_s *list) { + assert(list->collection.cmpfunc != NULL); + qsort(((cx_array_list *) list)->data, + list->collection.size, + list->collection.elem_size, + list->collection.cmpfunc + ); +} + +static int cx_arl_compare( + const struct cx_list_s *list, + const struct cx_list_s *other +) { + assert(list->collection.cmpfunc != NULL); + if (list->collection.size == other->collection.size) { + const char *left = ((const cx_array_list *) list)->data; + const char *right = ((const cx_array_list *) other)->data; + for (size_t i = 0; i < list->collection.size; i++) { + int d = list->collection.cmpfunc(left, right); + if (d != 0) { + return d; + } + left += list->collection.elem_size; + right += other->collection.elem_size; + } + return 0; + } else { + return list->collection.size < other->collection.size ? -1 : 1; + } +} + +static void cx_arl_reverse(struct cx_list_s *list) { + if (list->collection.size < 2) return; + void *data = ((const cx_array_list *) list)->data; + size_t half = list->collection.size / 2; + for (size_t i = 0; i < half; i++) { + cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i); + } +} + +static bool cx_arl_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + const struct cx_list_s *list = iter->src_handle.c; + return iter->index < list->collection.size; +} + +static void *cx_arl_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle; +} + +static void cx_arl_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + } else { + iter->index++; + iter->elem_handle = + ((char *) iter->elem_handle) + + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size; + } +} + +static void cx_arl_iter_prev(void *it) { + struct cx_iterator_s *iter = it; + const cx_array_list *list = iter->src_handle.c; + if (iter->base.remove) { + iter->base.remove = false; + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + } + iter->index--; + if (iter->index < list->base.collection.size) { + iter->elem_handle = ((char *) list->data) + + iter->index * list->base.collection.elem_size; + } +} + + +static struct cx_iterator_s cx_arl_iterator( + const struct cx_list_s *list, + size_t index, + bool backwards +) { + struct cx_iterator_s iter; + + iter.index = index; + iter.src_handle.c = list; + iter.elem_handle = cx_arl_at(list, index); + iter.elem_size = list->collection.elem_size; + iter.elem_count = list->collection.size; + iter.base.valid = cx_arl_iter_valid; + iter.base.current = cx_arl_iter_current; + iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next; + iter.base.remove = false; + iter.base.mutating = false; + + return iter; +} + +static cx_list_class cx_array_list_class = { + cx_arl_destructor, + cx_arl_insert_element, + cx_arl_insert_array, + cx_arl_insert_sorted, + cx_arl_insert_iter, + cx_arl_remove, + cx_arl_clear, + cx_arl_swap, + cx_arl_at, + cx_arl_find_remove, + cx_arl_sort, + cx_arl_compare, + cx_arl_reverse, + cx_arl_iterator, +}; + +CxList *cxArrayListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size, + size_t initial_capacity +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list)); + if (list == NULL) return NULL; + + list->base.cl = &cx_array_list_class; + list->base.collection.allocator = allocator; + list->capacity = initial_capacity; + + if (elem_size > 0) { + list->base.collection.elem_size = elem_size; + list->base.collection.cmpfunc = comparator; + } else { + elem_size = sizeof(void *); + list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator; + cxListStorePointers((CxList *) list); + } + + // allocate the array after the real elem_size is known + list->data = cxCalloc(allocator, initial_capacity, elem_size); + if (list->data == NULL) { // LCOV_EXCL_START + cxFree(allocator, list); + return NULL; + } // LCOV_EXCL_STOP + + // configure the reallocator + list->reallocator = cx_array_reallocator(allocator, NULL); + + return (CxList *) list; +} diff --git a/ucx/buffer.c b/ucx/buffer.c index a6a8085..ec67feb 100644 --- a/ucx/buffer.c +++ b/ucx/buffer.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,91 +26,131 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx/buffer.h" +#include "cx/buffer.h" -#include -#include +#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; +static int buffer_copy_on_write(CxBuffer* buffer) { + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; + void *newspace = cxMalloc(buffer->allocator, buffer->capacity); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; +} - buffer->pos = 0; +int cxBufferInit( + CxBuffer *buffer, + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (flags & CX_BUFFER_COPY_ON_EXTEND) { + flags |= CX_BUFFER_AUTO_EXTEND; + } + buffer->allocator = allocator; + buffer->flags = flags; + if (!space) { + buffer->bytes = cxMalloc(allocator, capacity); + if (buffer->bytes == NULL) { + return -1; // LCOV_EXCL_LINE + } + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + } else { + buffer->bytes = space; } + buffer->capacity = capacity; + buffer->size = 0; + buffer->pos = 0; - return buffer; + buffer->flush = NULL; + + return 0; +} + +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config +) { + buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE + memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); + return 0; } -void ucx_buffer_free(UcxBuffer *buffer) { - if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) { - free(buffer->space); +void cxBufferDestroy(CxBuffer *buffer) { + if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { + cxFree(buffer->allocator, buffer->bytes); } - free(buffer); + free(buffer->flush); + memset(buffer, 0, sizeof(CxBuffer)); } -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) - { +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); + if (buf == NULL) return NULL; + if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { + return buf; + } else { + // LCOV_EXCL_START + cxFree(allocator, buf); return NULL; + // LCOV_EXCL_STOP } +} - 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; +void cxBufferFree(CxBuffer *buffer) { + if (buffer == NULL) return; + const CxAllocator *allocator = buffer->allocator; + cxBufferDestroy(buffer); + cxFree(allocator, buffer); } -int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) { +int cxBufferSeek( + CxBuffer *buffer, + off_t offset, + int whence +) { size_t npos; switch (whence) { - case SEEK_CUR: - npos = buffer->pos; - break; - case SEEK_END: - npos = buffer->size; - break; - case SEEK_SET: - npos = 0; - break; - default: - return -1; + 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)) { + errno = EOVERFLOW; return -1; } - - if (npos >= buffer->size) { + + if (npos > buffer->size) { return -1; } else { buffer->pos = npos; @@ -119,135 +159,283 @@ int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) { } -int ucx_buffer_eof(UcxBuffer *buffer) { +void cxBufferClear(CxBuffer *buffer) { + if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) { + memset(buffer->bytes, 0, buffer->size); + } + buffer->size = 0; + buffer->pos = 0; +} + +void cxBufferReset(CxBuffer *buffer) { + buffer->size = 0; + buffer->pos = 0; +} + +bool cxBufferEof(const CxBuffer *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; - } +int cxBufferMinimumCapacity( + CxBuffer *buffer, + size_t newcap +) { + if (newcap <= buffer->capacity) { + return 0; } - - char *newspace = (char*)realloc(buffer->space, newcap); - if (newspace) { - memset(newspace+buffer->size, 0, newcap-buffer->size); + + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + void *newspace = cxMalloc(buffer->allocator, newcap); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); buffer->space = newspace; buffer->capacity = newcap; + buffer->flags &= ~force_copy_flags; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; + } else if (cxReallocate(buffer->allocator, + (void **) &buffer->bytes, newcap) == 0) { + buffer->capacity = newcap; + return 0; } else { - return -1; + return -1; // LCOV_EXCL_LINE } - - return 0; } -size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, - UcxBuffer *buffer) { +static size_t cx_buffer_flush_helper( + const CxBuffer *buffer, + size_t size, + const unsigned char *src, + size_t nitems +) { + // flush data from an arbitrary source + // does not need to be the buffer's contents + size_t max_items = buffer->flush->blksize / size; + size_t fblocks = 0; + size_t flushed_total = 0; + while (nitems > 0 && fblocks < buffer->flush->blkmax) { + fblocks++; + size_t items = nitems > max_items ? max_items : nitems; + size_t flushed = buffer->flush->wfunc( + src, size, items, buffer->flush->target); + if (flushed > 0) { + flushed_total += flushed; + src += flushed * size; + nitems -= flushed; + } else { + // if no bytes can be flushed out anymore, we give up + break; + } + } + return flushed_total; +} + +static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { + // flush the current contents of the buffer + unsigned char *space = buffer->bytes; + size_t remaining = buffer->pos / size; + size_t flushed_total = cx_buffer_flush_helper( + buffer, size, space, remaining); + + // shift the buffer left after flushing + // IMPORTANT: up to this point, copy on write must have been + // performed already, because we can't do error handling here + cxBufferShiftLeft(buffer, flushed_total*size); + + return flushed_total; +} + +size_t cxBufferFlush(CxBuffer *buffer) { + if (buffer_copy_on_write(buffer)) return 0; + return cx_buffer_flush_impl(buffer, 1); +} + +size_t cxBufferWrite( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { + // optimize for easy case + if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { + if (buffer_copy_on_write(buffer)) return 0; + memcpy(buffer->bytes + buffer->pos, ptr, nitems); + buffer->pos += nitems; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + return nitems; + } + size_t len; - if(ucx_szmul(size, nitems, &len)) { + if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; return 0; } - size_t required = buffer->pos + len; - if (buffer->pos > required) { + if (buffer->pos > SIZE_MAX - len) { + errno = EOVERFLOW; return 0; } - + size_t required = buffer->pos + len; + + bool perform_flush = false; if (required > buffer->capacity) { - if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { - if (ucx_buffer_extend(buffer, required - buffer->capacity)) { - return 0; + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + if (buffer->flush != NULL && required > buffer->flush->threshold) { + perform_flush = true; + } else { + if (cxBufferMinimumCapacity(buffer, required)) { + return 0; // LCOV_EXCL_LINE + } } } else { - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len%size; + if (buffer->flush != NULL) { + perform_flush = true; + } else { + // truncate data, if we can neither extend nor flush + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; } } } - + + // check here and not above because of possible truncation if (len == 0) { - return len; + return 0; } - - memcpy(buffer->space + buffer->pos, ptr, len); - buffer->pos += len; - if(buffer->pos > buffer->size) { - buffer->size = buffer->pos; + + // check if we need to copy + if (buffer_copy_on_write(buffer)) return 0; + + // perform the operation + if (perform_flush) { + size_t items_flush; + if (buffer->pos == 0) { + // if we don't have data in the buffer, but are instructed + // to flush, it means that we are supposed to relay the data + items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); + if (items_flush == 0) { + // we needed to flush, but could not flush anything + // give up and avoid endless trying + return 0; + } + size_t ritems = nitems - items_flush; + const unsigned char *rest = ptr; + rest += items_flush * size; + return items_flush + cxBufferWrite(rest, size, ritems, buffer); + } else { + items_flush = cx_buffer_flush_impl(buffer, size); + if (items_flush == 0) { + return 0; + } + return cxBufferWrite(ptr, size, nitems, buffer); + } + } else { + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + return nitems; + } + +} + +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { + size_t pos = buffer->pos; + buffer->pos = buffer->size; + size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; + return written; +} + +int cxBufferPut( + CxBuffer *buffer, + int c +) { + c &= 0xFF; + unsigned char const ch = c; + if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { + return c; + } else { + return EOF; } - - return len / size; } -size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, - UcxBuffer *buffer) { +int cxBufferTerminate(CxBuffer *buffer) { + bool success = 0 == cxBufferPut(buffer, 0); + if (success) { + buffer->pos--; + buffer->size--; + return 0; + } else { + return -1; + } +} + +size_t cxBufferPutString( + CxBuffer *buffer, + const char *str +) { + return cxBufferWrite(str, 1, strlen(str), buffer); +} + +size_t cxBufferRead( + void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { size_t len; - if(ucx_szmul(size, nitems, &len)) { + if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; return 0; } if (buffer->pos + len > buffer->size) { len = buffer->size - buffer->pos; - if (size > 1) len -= len%size; + if (size > 1) len -= len % size; } - + if (len <= 0) { return len; } - - memcpy(ptr, buffer->space + buffer->pos, len); + + memcpy(ptr, buffer->bytes + 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; + return len / size; } -int ucx_buffer_getc(UcxBuffer *buffer) { - if (ucx_buffer_eof(buffer)) { +int cxBufferGet(CxBuffer *buffer) { + if (cxBufferEof(buffer)) { return EOF; } else { - int c = ((unsigned char*)buffer->space)[buffer->pos]; + int c = buffer->bytes[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) { +int cxBufferShiftLeft( + CxBuffer *buffer, + size_t shift +) { if (shift >= buffer->size) { buffer->pos = buffer->size = 0; } else { - memmove(buffer->space, buffer->space + shift, buffer->size - shift); + if (buffer_copy_on_write(buffer)) return -1; + memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); buffer->size -= shift; - + if (buffer->pos >= shift) { buffer->pos -= shift; } else { @@ -257,15 +445,22 @@ int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) { return 0; } -int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) { +int cxBufferShiftRight( + CxBuffer *buffer, + size_t shift +) { + if (buffer->size > SIZE_MAX - shift) { + errno = EOVERFLOW; + return -1; + } 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; + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + if (cxBufferMinimumCapacity(buffer, req_capacity)) { + return -1; // LCOV_EXCL_LINE } movebytes = buffer->size; } else { @@ -274,23 +469,29 @@ int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) { } else { movebytes = buffer->size; } - - memmove(buffer->space + shift, buffer->space, movebytes); - buffer->size = shift+movebytes; - + + if (movebytes > 0) { + if (buffer_copy_on_write(buffer)) return -1; + memmove(buffer->bytes + shift, buffer->bytes, movebytes); + buffer->size = shift + movebytes; + } + buffer->pos += shift; if (buffer->pos > buffer->size) { buffer->pos = buffer->size; } - + return 0; } -int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) { +int cxBufferShift( + CxBuffer *buffer, + off_t shift +) { if (shift < 0) { - return ucx_buffer_shift_left(buffer, (size_t) (-shift)); + return cxBufferShiftLeft(buffer, (size_t) (-shift)); } else if (shift > 0) { - return ucx_buffer_shift_right(buffer, (size_t) shift); + return cxBufferShiftRight(buffer, (size_t) shift); } else { return 0; } diff --git a/ucx/compare.c b/ucx/compare.c new file mode 100644 index 0000000..b260aff --- /dev/null +++ b/ucx/compare.c @@ -0,0 +1,277 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/compare.h" + +#include + +int cx_vcmp_int(int a, int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_int(const void *i1, const void *i2) { + int a = *((const int *) i1); + int b = *((const int *) i2); + return cx_vcmp_int(a, b); +} + +int cx_vcmp_longint(long int a, long int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_longint(const void *i1, const void *i2) { + long int a = *((const long int *) i1); + long int b = *((const long int *) i2); + return cx_vcmp_longint(a, b); +} + +int cx_vcmp_longlong(long long a, long long b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_longlong(const void *i1, const void *i2) { + long long a = *((const long long *) i1); + long long b = *((const long long *) i2); + return cx_vcmp_longlong(a, b); +} + +int cx_vcmp_int16(int16_t a, int16_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_int16(const void *i1, const void *i2) { + int16_t a = *((const int16_t *) i1); + int16_t b = *((const int16_t *) i2); + return cx_vcmp_int16(a, b); +} + +int cx_vcmp_int32(int32_t a, int32_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_int32(const void *i1, const void *i2) { + int32_t a = *((const int32_t *) i1); + int32_t b = *((const int32_t *) i2); + return cx_vcmp_int32(a, b); +} + +int cx_vcmp_int64(int64_t a, int64_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_int64(const void *i1, const void *i2) { + int64_t a = *((const int64_t *) i1); + int64_t b = *((const int64_t *) i2); + return cx_vcmp_int64(a, b); +} + +int cx_vcmp_uint(unsigned int a, unsigned int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_uint(const void *i1, const void *i2) { + unsigned int a = *((const unsigned int *) i1); + unsigned int b = *((const unsigned int *) i2); + return cx_vcmp_uint(a, b); +} + +int cx_vcmp_ulongint(unsigned long int a, unsigned long int b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_ulongint(const void *i1, const void *i2) { + unsigned long int a = *((const unsigned long int *) i1); + unsigned long int b = *((const unsigned long int *) i2); + return cx_vcmp_ulongint(a, b); +} + +int cx_vcmp_ulonglong(unsigned long long a, unsigned long long b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_ulonglong(const void *i1, const void *i2) { + unsigned long long a = *((const unsigned long long *) i1); + unsigned long long b = *((const unsigned long long *) i2); + return cx_vcmp_ulonglong(a, b); +} + +int cx_vcmp_uint16(uint16_t a, uint16_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_uint16(const void *i1, const void *i2) { + uint16_t a = *((const uint16_t *) i1); + uint16_t b = *((const uint16_t *) i2); + return cx_vcmp_uint16(a, b); +} + +int cx_vcmp_uint32(uint32_t a, uint32_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_uint32(const void *i1, const void *i2) { + uint32_t a = *((const uint32_t *) i1); + uint32_t b = *((const uint32_t *) i2); + return cx_vcmp_uint32(a, b); +} + +int cx_vcmp_uint64(uint64_t a, uint64_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_uint64(const void *i1, const void *i2) { + uint64_t a = *((const uint64_t *) i1); + uint64_t b = *((const uint64_t *) i2); + return cx_vcmp_uint64(a, b); +} + +int cx_vcmp_float(float a, float b) { + if (fabsf(a - b) < 1e-6f) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_float(const void *f1, const void *f2) { + float a = *((const float *) f1); + float b = *((const float *) f2); + return cx_vcmp_float(a, b); +} + +int cx_vcmp_double(double a, double b) { + if (fabs(a - b) < 1e-14) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_double( + const void *d1, + const void *d2 +) { + double a = *((const double *) d1); + double b = *((const double *) d2); + return cx_vcmp_double(a, b); +} + +int cx_vcmp_intptr(intptr_t p1, intptr_t p2) { + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} + +int cx_cmp_intptr( + const void *ptr1, + const void *ptr2 +) { + intptr_t p1 = *(const intptr_t *) ptr1; + intptr_t p2 = *(const intptr_t *) ptr2; + return cx_vcmp_intptr(p1, p2); +} + +int cx_vcmp_uintptr(uintptr_t p1, uintptr_t p2) { + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} + +int cx_cmp_uintptr( + const void *ptr1, + const void *ptr2 +) { + uintptr_t p1 = *(const uintptr_t *) ptr1; + uintptr_t p2 = *(const uintptr_t *) ptr2; + return cx_vcmp_uintptr(p1, p2); +} + +int cx_cmp_ptr( + const void *ptr1, + const void *ptr2 +) { + uintptr_t p1 = (uintptr_t) ptr1; + uintptr_t p2 = (uintptr_t) ptr2; + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h new file mode 100644 index 0000000..ed6a318 --- /dev/null +++ b/ucx/cx/allocator.h @@ -0,0 +1,411 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file allocator.h + * Interface for custom allocators. + */ + +#ifndef UCX_ALLOCATOR_H +#define UCX_ALLOCATOR_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The class definition for an allocator. + */ +typedef struct { + /** + * The allocator's malloc() implementation. + */ + void *(*malloc)( + void *data, + size_t n + ); + + /** + * The allocator's realloc() implementation. + */ + void *(*realloc)( + void *data, + void *mem, + size_t n + ); + + /** + * The allocator's calloc() implementation. + */ + void *(*calloc)( + void *data, + size_t nelem, + size_t n + ); + + /** + * The allocator's free() implementation. + */ + void (*free)( + void *data, + void *mem + ); +} cx_allocator_class; + +/** + * Structure holding the data for an allocator. + */ +struct cx_allocator_s { + /** + * A pointer to the instance of the allocator class. + */ + cx_allocator_class *cl; + /** + * A pointer to the data this allocator uses. + */ + void *data; +}; + +/** + * High-Level type alias for the allocator type. + */ +typedef struct cx_allocator_s CxAllocator; + +/** + * A default allocator using standard library malloc() etc. + */ +extern CxAllocator *cxDefaultAllocator; + +/** + * Function pointer type for destructor functions. + * + * A destructor function deallocates possible contents and MAY free the memory + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. + * + * @param memory a pointer to the object to destruct + */ +typedef void (*cx_destructor_func)(void *memory); + +/** + * Function pointer type for destructor functions. + * + * A destructor function deallocates possible contents and MAY free the memory + * pointed to by @p memory. Read the documentation of the respective function + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. + * + * @param data an optional pointer to custom data + * @param memory a pointer to the object to destruct + */ +typedef void (*cx_destructor_func2)( + void *data, + void *memory +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * @par Error handling + * @c errno will be set by realloc() on failure. + * + * @param mem pointer to the pointer to allocated block + * @param n the new size in bytes + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_reallocate( + void **mem, + size_t n +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero failure + * @see cx_reallocate() + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_reallocatearray( + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * @par Error handling + * @c errno will be set by realloc() on failure. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + * @see cx_reallocatearray() + */ +#define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n) + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @par Error handling + * @c errno will be set by realloc() on failure or when the multiplication of + * @p nmemb and @p size overflows. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cx_reallocatearray(mem, nmemb, size) \ + cx_reallocatearray((void**)(mem), nmemb, size) + +/** + * Free a block allocated by this allocator. + * + * @note Freeing a block of a different allocator is undefined. + * + * @param allocator the allocator + * @param mem a pointer to the block to free + */ +cx_attr_nonnull_arg(1) +void cxFree( + const CxAllocator *allocator, + void *mem +); + +/** + * Allocate @p n bytes of memory. + * + * @param allocator the allocator + * @param n the number of bytes + * @return a pointer to the allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) +void *cxMalloc( + const CxAllocator *allocator, + size_t n +); + +/** + * Re-allocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @param allocator the allocator + * @param mem pointer to the previously allocated block + * @param n the new size in bytes + * @return a pointer to the re-allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3) +void *cxRealloc( + const CxAllocator *allocator, + void *mem, + size_t n +); + +/** + * Re-allocate the previously allocated block in @p mem, making the new block + * @p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * If that multiplication overflows, this function returns @c NULL and @c errno + * will be set. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @param allocator the allocator + * @param mem pointer to the previously allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @return a pointer to the re-allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3, 4) +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. + * + * @param allocator the allocator + * @param mem pointer to the pointer to allocated block + * @param n the new size in bytes + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nodiscard +cx_attr_nonnull +int cxReallocate( + const CxAllocator *allocator, + void **mem, + size_t n +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocate(allocator, mem, n) \ + cxReallocate(allocator, (void**)(mem), n) + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param allocator the allocator + * @param mem pointer to the pointer to allocated block + * @param nmemb the number of elements + * @param size the size of each element + * @retval zero success + * @retval non-zero on failure + */ +cx_attr_nodiscard +cx_attr_nonnull +int cxReallocateArray( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param allocator (@c CxAllocator*) the allocator + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure + */ +#define cxReallocateArray(allocator, mem, nmemb, size) \ + cxReallocateArray(allocator, (void**) (mem), nmemb, size) + +/** + * Allocate @p nelem elements of @p n bytes each, all initialized to zero. + * + * @param allocator the allocator + * @param nelem the number of elements + * @param n the size of each element in bytes + * @return a pointer to the allocated memory + */ +cx_attr_nonnull_arg(1) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2, 3) +void *cxCalloc( + const CxAllocator *allocator, + size_t nelem, + size_t n +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_ALLOCATOR_H diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h new file mode 100644 index 0000000..a2b3d59 --- /dev/null +++ b/ucx/cx/array_list.h @@ -0,0 +1,732 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file array_list.h + * @brief Array list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + + +#ifndef UCX_ARRAY_LIST_H +#define UCX_ARRAY_LIST_H + +#include "list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The maximum item size in an array list that fits into stack buffer + * when swapped. + */ +extern const unsigned cx_array_swap_sbo_size; + +/** + * Declares variables for an array that can be used with the convenience macros. + * + * @par Examples + * @code + * // integer array with at most 255 elements + * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t) + * + * // array of MyObject* pointers where size and capacity are stored as unsigned int + * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int) + * + * // initializing code + * cx_array_initialize(myarray, 16); // reserve space for 16 + * cx_array_initialize(objects, 100); // reserve space for 100 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t) + * + * @see cx_array_initialize() + * @see cx_array_simple_add() + * @see cx_array_simple_copy() + * @see cx_array_simple_add_sorted() + * @see cx_array_simple_insert_sorted() + */ +#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \ + type * name; \ + /** Array size. */ size_type name##_size; \ + /** Array capacity. */ size_type name##_capacity + +/** + * Declares variables for an array that can be used with the convenience macros. + * + * The size and capacity variables will have @c size_t type. + * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. + * + * @par Examples + * @code + * // int array + * CX_ARRAY_DECLARE(int, myarray) + * + * // initializing code + * cx_array_initialize(myarray, 32); // reserve space for 32 + * @endcode + * + * @param type the type of the data + * @param name the name of the array + * + * @see cx_array_initialize() + * @see cx_array_simple_add() + * @see cx_array_simple_copy() + * @see cx_array_simple_add_sorted() + * @see cx_array_simple_insert_sorted() + */ +#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t) + +/** + * Initializes an array with the given capacity. + * + * The type of the capacity depends on the type used during declaration. + * + * @par Examples + * @code + * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t) + * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t + * + * // initializing code + * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255 + * cx_array_initialize(arr2, 500); // OK + * @endcode + * + * + * The memory for the array is allocated with stdlib malloc(). + * @param array the name of the array + * @param capacity the initial capacity + * @see cx_array_initialize_a() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() + */ +#define cx_array_initialize(array, capacity) \ + array##_capacity = capacity; \ + array##_size = 0; \ + array = malloc(sizeof(array[0]) * capacity) + +/** + * Initializes an array with the given capacity using the specified allocator. + * + * @par Example + * @code + * CX_ARRAY_DECLARE(int, myarray) + * + * + * const CxAllocator *al = // ... + * cx_array_initialize_a(al, myarray, 128); + * // ... + * cxFree(al, myarray); // don't forget to free with same allocator + * @endcode + * + * The memory for the array is allocated with stdlib malloc(). + * @param allocator (@c CxAllocator*) the allocator + * @param array the name of the array + * @param capacity the initial capacity + * @see cx_array_initialize() + * @see CX_ARRAY_DECLARE_SIZED() + * @see CX_ARRAY_DECLARE() + */ +#define cx_array_initialize_a(allocator, array, capacity) \ + array##_capacity = capacity; \ + array##_size = 0; \ + array = cxMalloc(allocator, sizeof(array[0]) * capacity) + +/** + * Defines a reallocation mechanism for arrays. + * You can create your own, use cx_array_reallocator(), or + * use the #cx_array_default_reallocator. + */ +struct cx_array_reallocator_s { + /** + * Reallocates space for the given array. + * + * Implementations are not required to free the original array. + * This allows reallocation of static memory by allocating heap memory + * and copying the array contents. The information in the custom fields of + * the referenced allocator can be used to track the state of the memory + * or to transport other additional data. + * + * @param array the array to reallocate + * @param capacity the new capacity (number of elements) + * @param elem_size the size of each element + * @param alloc a reference to this allocator + * @return a pointer to the reallocated memory or @c NULL on failure + */ + cx_attr_nodiscard + cx_attr_nonnull_arg(4) + cx_attr_allocsize(2, 3) + void *(*realloc)( + void *array, + size_t capacity, + size_t elem_size, + struct cx_array_reallocator_s *alloc + ); + + /** + * Custom data pointer. + */ + void *ptr1; + /** + * Custom data pointer. + */ + void *ptr2; + /** + * Custom data integer. + */ + size_t int1; + /** + * Custom data integer. + */ + size_t int2; +}; + +/** + * Typedef for the array reallocator struct. + */ +typedef struct cx_array_reallocator_s CxArrayReallocator; + +/** + * A default stdlib-based array reallocator. + */ +extern CxArrayReallocator *cx_array_default_reallocator; + +/** + * Creates a new array reallocator. + * + * When @p allocator is @c NULL, the stdlib default allocator will be used. + * + * When @p stackmem is not @c NULL, the reallocator is supposed to be used + * @em only for the specific array that is initially located at @p stackmem. + * When reallocation is needed, the reallocator checks, if the array is + * still located at @p stackmem and copies the contents to the heap. + * + * @note Invoking this function with both arguments @c NULL will return a + * reallocator that behaves like #cx_array_default_reallocator. + * + * @param allocator the allocator this reallocator shall be based on + * @param stackmem the address of the array when the array is initially located + * on the stack or shall not reallocated in place + * @return an array reallocator + */ +CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +); + +/** + * Reserves memory for additional elements. + * + * This function checks if the @p capacity of the array is sufficient to hold + * at least @p size plus @p elem_count elements. If not, a reallocation is + * performed with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * This function can be useful to replace subsequent calls to cx_array_copy() + * with one single cx_array_reserve() and then - after guaranteeing a + * sufficient capacity - use simple memmove() or memcpy(). + * + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. + * + * @param array a pointer to the target array + * @param size a pointer to the size of the array + * @param capacity a pointer to the capacity of the array + * @param width the width in bytes for the @p size and @p capacity or zero for default + * @param elem_size the size of one element + * @param elem_count the number of expected additional elements + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() + */ +cx_attr_nonnull_arg(1, 2, 3) +int cx_array_reserve( + void **array, + void *size, + void *capacity, + unsigned width, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); + +/** + * Copies elements from one array to another. + * + * The elements are copied to the @p target array at the specified @p index, + * overwriting possible elements. The @p index does not need to be in range of + * the current array @p size. If the new index plus the number of elements added + * would extend the array's size, the remaining @p capacity is used. + * + * If the @p capacity is also insufficient to hold the new data, a reallocation + * attempt is made with the specified @p reallocator. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * The @p width in bytes refers to the size and capacity. + * Both must have the same width. + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * architecture. If set to zero, the native word width is used. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param width the width in bytes for the @p size and @p capacity or zero for default + * @param index the index where the copied elements shall be placed + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to copy + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + * @see cx_array_reallocator() + */ +cx_attr_nonnull_arg(1, 2, 3, 6) +int cx_array_copy( + void **target, + void *size, + void *capacity, + unsigned width, + size_t index, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); + +/** + * Convenience macro that uses cx_array_copy() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy() + */ +#define cx_array_simple_copy_a(reallocator, array, index, src, count) \ + cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), index, src, sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_copy() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param index (@c size_t) the index where the copied elements shall be placed + * @param src (@c void*) the source array + * @param count (@c size_t) the number of elements to copy + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_copy_a() + */ +#define cx_array_simple_copy(array, index, src, count) \ + cx_array_simple_copy_a(NULL, array, index, src, count) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected @em additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve() + */ +#define cx_array_simple_reserve_a(reallocator, array, count) \ + cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \ + sizeof(array##_size), sizeof((array)[0]), count, \ + reallocator) + +/** + * Convenience macro that uses cx_array_reserve() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param count (@c size_t) the number of expected additional elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_reserve_a() + */ +#define cx_array_simple_reserve(array, count) \ + cx_array_simple_reserve_a(NULL, array, count) + +/** + * Adds an element to an array with the possibility of allocating more space. + * + * The element @p elem is added to the end of the @p target array which contains + * @p size elements, already. The @p capacity must point to a variable denoting + * the current maximum number of elements the array can hold. + * + * If the capacity is insufficient to hold the new element, an attempt to + * increase the @p capacity is made and the new capacity is written back. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure + */ +#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \ + cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \ + *(size), elem, elem_size, 1, reallocator) + +/** + * Convenience macro that uses cx_array_add() with a default layout and + * the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add() + */ +#define cx_array_simple_add_a(reallocator, array, elem) \ + cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1) + +/** + * Convenience macro that uses cx_array_add() with a default layout and + * the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_a() + */ +#define cx_array_simple_add(array, elem) \ + cx_array_simple_add_a(cx_array_default_reallocator, array, elem) + +/** + * Inserts a sorted array into another sorted array. + * + * If either the target or the source array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param cmp_func the compare function for the elements + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to insert + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull_arg(1, 2, 3, 5) +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); + +/** + * Inserts an element into a sorted array. + * + * If the target array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success + * @retval non-zero failure + */ +#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ + cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) + +/** + * Convenience macro for cx_array_add_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted() + */ +#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \ + cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_sorted() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_sorted_a() + */ +#define cx_array_simple_add_sorted(array, elem, cmp_func) \ + cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_sorted() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted() + */ +#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) + +/** + * Convenience macro for cx_array_insert_sorted() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_sorted_a() + */ +#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ + cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) + +/** + * Searches the largest lower bound in a sorted array. + * + * In other words, this function returns the index of the largest element + * in @p arr that is less or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the largest lower bound, or @p size + * @see cx_array_binary_search_sup() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); + +/** + * Searches an item in a sorted array. + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the element in the array, or @p size if the element + * cannot be found + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search_sup() + */ +cx_attr_nonnull +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); + +/** + * Searches the smallest upper bound in a sorted array. + * + * In other words, this function returns the index of the smallest element + * in @p arr that is greater or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @return the index of the smallest upper bound, or @p size + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +); + +/** + * Swaps two array elements. + * + * @param arr the array + * @param elem_size the element size + * @param idx1 index of first element + * @param idx2 index of second element + */ +cx_attr_nonnull +void cx_array_swap( + void *arr, + size_t elem_size, + size_t idx1, + size_t idx2 +); + +/** + * Allocates an array list for storing elements with @p elem_size bytes each. + * + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if + * cxListStorePointers() was called immediately after creation and the compare + * function will be automatically set to cx_cmp_ptr(), if none is given. + * + * @param allocator the allocator for allocating the list memory + * (if @c NULL, a default stdlib allocator will be used) + * @param comparator the comparator for the elements + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @param initial_capacity the initial number of elements the array can store + * @return the created list + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) +CxList *cxArrayListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size, + size_t initial_capacity +); + +/** + * Allocates an array list for storing elements with @p elem_size bytes each. + * + * The list will use the cxDefaultAllocator and @em NO compare function. + * If you want to call functions that need a compare function, you have to + * set it immediately after creation or use cxArrayListCreate(). + * + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if + * cxListStorePointers() was called immediately after creation and the compare + * function will be automatically set to cx_cmp_ptr(). + * + * @param elem_size (@c size_t) the size of each element in bytes + * @param initial_capacity (@c size_t) the initial number of elements the array can store + * @return the created list + */ +#define cxArrayListCreateSimple(elem_size, initial_capacity) \ + cxArrayListCreate(NULL, NULL, elem_size, initial_capacity) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_ARRAY_LIST_H diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h new file mode 100644 index 0000000..5ba27b0 --- /dev/null +++ b/ucx/cx/buffer.h @@ -0,0 +1,680 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file buffer.h + * + * @brief Advanced buffer implementation. + * + * Instances of CxBuffer can be used to read from or to write to like one + * would do with a stream. + * + * Some features for convenient use of the buffer + * can be enabled. See the documentation of the macro constants for more + * information. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_BUFFER_H +#define UCX_BUFFER_H + +#include "common.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * No buffer features enabled (all flags cleared). + */ +#define CX_BUFFER_DEFAULT 0x00 + +/** + * If this flag is enabled, the buffer will automatically free its contents when destroyed. + * + * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically + * set when the copy-on-write operations is performed. + */ +#define CX_BUFFER_FREE_CONTENTS 0x01 + +/** + * If this flag is enabled, the buffer will automatically extend its capacity. + */ +#define CX_BUFFER_AUTO_EXTEND 0x02 + +/** + * If this flag is enabled, the buffer will allocate new memory when written to. + * + * The current contents of the buffer will be copied to the new memory and the flag + * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. + */ +#define CX_BUFFER_COPY_ON_WRITE 0x04 + +/** + * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation. + * + * After performing the copy, the flag is automatically cleared. + * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why + * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled. + */ +#define CX_BUFFER_COPY_ON_EXTEND 0x08 + +/** + * Configuration for automatic flushing. + */ +struct cx_buffer_flush_config_s { + /** + * The buffer may not extend beyond this threshold before starting to flush. + * + * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. + * The threshold will be the maximum capacity the buffer is extended to + * before flushing. + */ + size_t threshold; + /** + * The block size for the elements to flush. + */ + size_t blksize; + /** + * The maximum number of blocks to flush in one cycle. + * + * @attention while it is guaranteed that cxBufferFlush() will not flush + * more blocks, this is not necessarily the case for cxBufferWrite(). + * After performing a flush cycle, cxBufferWrite() will retry the write + * operation and potentially trigger another flush cycle, until the + * flush target accepts no more data. + */ + size_t blkmax; + + /** + * The target for write function. + */ + void *target; + + /** + * The write-function used for flushing. + * If NULL, the flushed content gets discarded. + */ + cx_write_func wfunc; +}; + +/** + * Type alais for the flush configuration struct. + * + * @code + * struct cx_buffer_flush_config_s { + * size_t threshold; + * size_t blksize; + * size_t blkmax; + * void *target; + * cx_write_func wfunc; + * }; + * @endcode + */ +typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; + +/** Structure for the UCX buffer data. */ +struct cx_buffer_s { + /** A pointer to the buffer contents. */ + union { + /** + * Data is interpreted as text. + */ + char *space; + /** + * Data is interpreted as binary. + */ + unsigned char *bytes; + }; + /** The allocator to use for automatic memory management. */ + const CxAllocator *allocator; + /** + * Optional flush configuration + * + * @see cxBufferEnableFlushing() + */ + CxBufferFlushConfig* flush; + /** 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 #CX_BUFFER_DEFAULT + * @see #CX_BUFFER_FREE_CONTENTS + * @see #CX_BUFFER_AUTO_EXTEND + * @see #CX_BUFFER_COPY_ON_WRITE + */ + int flags; +}; + +/** + * UCX buffer. + */ +typedef struct cx_buffer_s CxBuffer; + +/** + * Initializes a fresh buffer. + * + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * + * You need to set the size manually after initialization, if + * you provide @p space which already contains data. + * + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. + * Then this function will allocate the space and enforce + * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying + * copy-on-write should be avoided, because the allocated + * space will be leaking after the copy-on-write operation. + * + * @param buffer the buffer to initialize + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param allocator the allocator this buffer shall use for automatic + * memory management + * (if @c NULL, a default stdlib allocator will be used) + * @param flags buffer features (see cx_buffer_s.flags) + * @return zero on success, non-zero if a required allocation failed + */ +cx_attr_nonnull_arg(1) +int cxBufferInit( + CxBuffer *buffer, + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +); + +/** + * Configures the buffer for flushing. + * + * Flushing can happen automatically when data is written + * to the buffer (see cxBufferWrite()) or manually when + * cxBufferFlush() is called. + * + * @param buffer the buffer + * @param config the flush configuration + * @retval zero success + * @retval non-zero failure + * @see cxBufferFlush() + * @see cxBufferWrite() + */ +cx_attr_nonnull +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config +); + +/** + * Destroys the buffer contents. + * + * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled. + * If you want to free the memory of the entire buffer, use cxBufferFree(). + * + * @param buffer the buffer which contents shall be destroyed + * @see cxBufferInit() + */ +cx_attr_nonnull +void cxBufferDestroy(CxBuffer *buffer); + +/** + * Deallocates the buffer. + * + * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys + * the contents. If you @em only want to destroy the contents, use cxBufferDestroy(). + * + * @remark As with all free() functions, this accepts @c NULL arguments in which + * case it does nothing. + * + * @param buffer the buffer to deallocate + * @see cxBufferCreate() + */ +void cxBufferFree(CxBuffer *buffer); + +/** + * Allocates and initializes a fresh buffer. + * + * You may also provide a read-only @p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * When you specify stack memory as @p space and decide to use + * the auto-extension feature, you @em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * @note You may provide @c NULL as argument for @p space. + * Then this function will allocate the space and enforce + * the #CX_BUFFER_FREE_CONTENTS flag. + * + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param allocator the allocator to use for allocating the structure and the automatic + * memory management within the buffer + * (if @c NULL, a default stdlib allocator will be used) + * @param flags buffer features (see cx_buffer_s.flags) + * @return a pointer to the buffer on success, @c NULL if a required allocation failed + */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int 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 @p shift bytes are discarded. + * The new size of the buffer is the old size minus the absolute shift value. + * If this value is larger than the buffer size, the buffer is emptied (but + * not cleared, see the security note below). + * + * The buffer position gets shifted alongside with the content but is kept + * within the boundaries of the buffer. + * + * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and + * cxBufferShiftRight() functions using a @c size_t as parameter type. + * + * @attention + * Security Note: The shifting operation does @em not erase the previously occupied memory cells. + * But you can easily do that manually, e.g. by calling + * memset(buffer->bytes, 0, shift) for a right shift or + * memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size) + * for a left shift. + * + * @param buffer the buffer + * @param shift the shift offset (negative means left shift) + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails + * @see cxBufferShiftLeft() + * @see cxBufferShiftRight() + */ +cx_attr_nonnull +int cxBufferShift( + CxBuffer *buffer, + off_t shift +); + +/** + * Shifts the buffer to the right. + * See cxBufferShift() for details. + * + * @param buffer the buffer + * @param shift the shift offset + * @retval zero success + * @retval non-zero if a required auto-extension or copy-on-write fails + * @see cxBufferShift() + */ +cx_attr_nonnull +int cxBufferShiftRight( + CxBuffer *buffer, + size_t shift +); + +/** + * Shifts the buffer to the left. + * See cxBufferShift() for details. + * + * @param buffer the buffer + * @param shift the positive shift offset + * @retval zero success + * @retval non-zero if the buffer uses copy-on-write and the allocation fails + * @see cxBufferShift() + */ +cx_attr_nonnull +int cxBufferShiftLeft( + CxBuffer *buffer, + size_t shift +); + + +/** + * Moves the position of the buffer. + * + * The new position is relative to the @p whence argument. + * + * @li @c SEEK_SET marks the start of the buffer. + * @li @c SEEK_CUR marks the current position. + * @li @c SEEK_END marks the end of the buffer. + * + * With an offset of zero, this function sets the buffer position to zero + * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position + * unchanged (@c SEEK_CUR). + * + * @param buffer the buffer + * @param offset position offset relative to @p whence + * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END + * @retval zero success + * @retval non-zero if the position is invalid + * + */ +cx_attr_nonnull +int cxBufferSeek( + CxBuffer *buffer, + off_t offset, + int whence +); + +/** + * Clears the buffer by resetting the position and deleting the data. + * + * The data is deleted by zeroing it with a call to memset(). + * If you do not need that, you can use the faster cxBufferReset(). + * + * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function + * will not erase the data and behave exactly as cxBufferReset(). + * + * @param buffer the buffer to be cleared + * @see cxBufferReset() + */ +cx_attr_nonnull +void cxBufferClear(CxBuffer *buffer); + +/** + * Resets the buffer by resetting the position and size to zero. + * + * The data in the buffer is not deleted. If you need a safe + * reset of the buffer, use cxBufferClear(). + * + * @param buffer the buffer to be cleared + * @see cxBufferClear() + */ +cx_attr_nonnull +void cxBufferReset(CxBuffer *buffer); + +/** + * Tests, if the buffer position has exceeded the buffer size. + * + * @param buffer the buffer to test + * @retval true if the current buffer position has exceeded the last + * byte of the buffer's contents + * @retval false otherwise + */ +cx_attr_nonnull +cx_attr_nodiscard +bool cxBufferEof(const CxBuffer *buffer); + + +/** + * Ensures that the buffer has a minimum capacity. + * + * If the current capacity is not sufficient, the buffer will be extended. + * + * @param buffer the buffer + * @param capacity the minimum required capacity for this buffer + * @retval zero the capacity was already sufficient or successfully increased + * @retval non-zero on allocation failure + */ +cx_attr_nonnull +int cxBufferMinimumCapacity( + CxBuffer *buffer, + size_t capacity +); + +/** + * Writes data to a CxBuffer. + * + * If automatic flushing is not enabled, the data is simply written into the + * buffer at the current position and the position of the buffer is increased + * by the number of bytes written. + * + * If flushing is enabled and the buffer needs to flush, the data is flushed to + * the target until the target signals that it cannot take more data by + * returning zero via the respective write function. In that case, the remaining + * data in this buffer is shifted to the beginning of this buffer so that the + * newly available space can be used to append as much data as possible. This + * function only stops writing more elements, when the flush target and this + * buffer are both incapable of taking more data or all data has been written. + * If number of items that shall be written is larger than the buffer can hold, + * the first items from @c ptr are directly relayed to the flush target, if + * possible. + * The number returned by this function is only the number of elements from + * @c ptr that could be written to either the flush target or the buffer. + * + * @note The signature is compatible with the fwrite() family of functions. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to write to + * @return the total count of elements written + * @see cxBufferAppend() + * @see cxBufferRead() + */ +cx_attr_nonnull +size_t cxBufferWrite( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Appends data to a CxBuffer. + * + * The data is always appended to current data within the buffer, + * regardless of the current position. + * This is especially useful when the buffer is primarily meant for reading + * while additional data is added to the buffer occasionally. + * Consequently, the position of the buffer is unchanged after this operation. + * + * @note The signature is compatible with the fwrite() family of functions. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to write to + * @return the total count of elements written + * @see cxBufferWrite() + * @see cxBufferRead() + */ +cx_attr_nonnull +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Performs a single flush-run on the specified buffer. + * + * Does nothing when the position in the buffer is zero. + * Otherwise, the data until the current position minus + * one is considered for flushing. + * Note carefully that flushing will never exceed the + * current @em position, even when the size of the + * buffer is larger than the current position. + * + * One flush run will try to flush @c blkmax many + * blocks of size @c blksize until either the @p buffer + * has no more data to flush or the write function + * used for flushing returns zero. + * + * The buffer is shifted left for that many bytes + * the flush operation has successfully flushed. + * + * @par Example 1 + * Assume you have a buffer with size 340 and you are + * at position 200. The flush configuration is + * @c blkmax=4 and @c blksize=64 . + * Assume that the entire flush operation is successful. + * All 200 bytes on the left hand-side from the current + * position are written. + * That means, the size of the buffer is now 140 and the + * position is zero. + * + * @par Example 2 + * Same as Example 1, but now the @c blkmax is 1. + * The size of the buffer is now 276 and the position is 136. + * + * @par Example 3 + * Same as Example 1, but now assume the flush target + * only accepts 100 bytes before returning zero. + * That means, the flush operations manages to flush + * one complete block and one partial block, ending + * up with a buffer with size 240 and position 100. + * + * @remark Just returns zero when flushing was not enabled with + * cxBufferEnableFlushing(). + * + * @remark When the buffer uses copy-on-write, the memory + * is copied first, before attempting any flush. + * This is, however, considered an erroneous use of the + * buffer, because it does not make much sense to put + * readonly data into an UCX buffer for flushing, instead + * of writing it directly to the target. + * + * @param buffer the buffer + * @return the number of successfully flushed bytes + * @see cxBufferEnableFlushing() + */ +cx_attr_nonnull +size_t cxBufferFlush(CxBuffer *buffer); + +/** + * Reads data from a CxBuffer. + * + * The position of the buffer is increased by the number of bytes read. + * + * @note The signature is compatible with the fread() family of functions. + * + * @param ptr a pointer to the memory area where to store the read data + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to read from + * @return the total number of elements read + * @see cxBufferWrite() + * @see cxBufferAppend() + */ +cx_attr_nonnull +size_t cxBufferRead( + void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Writes a character to a buffer. + * + * The least significant byte of the argument is written to the buffer. If the + * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, + * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature + * is disabled or buffer extension fails, @c EOF is returned. + * + * On successful write, the position of the buffer is increased. + * + * If you just want to write a null-terminator at the current position, you + * should use cxBufferTerminate() instead. + * + * @param buffer the buffer to write to + * @param c the character to write + * @return the byte that has been written or @c EOF when the end of the stream is + * reached and automatic extension is not enabled or not possible + * @see cxBufferTerminate() + */ +cx_attr_nonnull +int cxBufferPut( + CxBuffer *buffer, + int c +); + +/** + * Writes a terminating zero to a buffer at the current position. + * + * On successful write, @em neither the position @em nor the size of the buffer is + * increased. + * + * The purpose of this function is to have the written data ready to be used as + * a C string. + * + * @param buffer the buffer to write to + * @return zero, if the terminator could be written, non-zero otherwise + */ +cx_attr_nonnull +int cxBufferTerminate(CxBuffer *buffer); + +/** + * Writes a string to a buffer. + * + * This is a convenience function for cxBufferWrite(str, 1, strlen(str), buffer). + * + * @param buffer the buffer + * @param str the zero-terminated string + * @return the number of bytes written + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +size_t cxBufferPutString( + CxBuffer *buffer, + const char *str +); + +/** + * Gets a character from a buffer. + * + * The current position of the buffer is increased after a successful read. + * + * @param buffer the buffer to read from + * @return the character or @c EOF, if the end of the buffer is reached + */ +cx_attr_nonnull +int cxBufferGet(CxBuffer *buffer); + +#ifdef __cplusplus +} +#endif + +#endif // UCX_BUFFER_H diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h new file mode 100644 index 0000000..04dc487 --- /dev/null +++ b/ucx/cx/collection.h @@ -0,0 +1,181 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file collection.h + * @brief Common definitions for various collection implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_COLLECTION_H +#define UCX_COLLECTION_H + +#include "allocator.h" +#include "iterator.h" +#include "compare.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Special constant used for creating collections that are storing pointers. + */ +#define CX_STORE_POINTERS 0 + +/** + * Base attributes of a collection. + */ +struct cx_collection_s { + /** + * The allocator to use. + */ + const CxAllocator *allocator; + /** + * The comparator function for the elements. + */ + cx_compare_func cmpfunc; + /** + * The size of each element. + */ + size_t elem_size; + /** + * The number of currently stored elements. + */ + size_t size; + /** + * An optional simple destructor for the collection's elements. + * + * @attention Read the documentation of the particular collection implementation + * whether this destructor shall only destroy the contents or also free the memory. + */ + cx_destructor_func simple_destructor; + /** + * An optional advanced destructor for the collection's elements. + * + * @attention Read the documentation of the particular collection implementation + * whether this destructor shall only destroy the contents or also free the memory. + */ + cx_destructor_func2 advanced_destructor; + /** + * The pointer to additional data that is passed to the advanced destructor. + */ + void *destructor_data; + /** + * Indicates if this list is supposed to store pointers + * instead of copies of the actual objects. + */ + bool store_pointer; +}; + +/** + * Use this macro to declare common members for a collection structure. + * + * @par Example Use + * @code + * struct MyCustomSet { + * CX_COLLECTION_BASE; + * MySetElements *data; + * } + * @endcode + */ +#define CX_COLLECTION_BASE struct cx_collection_s collection + +/** + * Sets a simple destructor function for this collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param destr the destructor function + */ +#define cxDefineDestructor(c, destr) \ + (c)->collection.simple_destructor = (cx_destructor_func) destr + +/** + * Sets a simple destructor function for this collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param destr the destructor function + */ +#define cxDefineAdvancedDestructor(c, destr, data) \ + (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \ + (c)->collection.destructor_data = data + +/** + * Invokes the simple destructor function for a specific element. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) + */ +#define cx_invoke_simple_destructor(c, e) \ + (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e)) + +/** + * Invokes the advanced destructor function for a specific element. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) + */ +#define cx_invoke_advanced_destructor(c, e) \ + (c)->collection.advanced_destructor((c)->collection.destructor_data, \ + (c)->collection.store_pointer ? (*((void **) (e))) : (e)) + + +/** + * Invokes all available destructor functions for a specific element. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * When the collection stores pointers, those pointers are directly passed + * to the destructor. Otherwise, a pointer to the element is passed. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param e the element (the type is @c void* or @c void** depending on context) + */ +#define cx_invoke_destructor(c, e) \ + if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \ + if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_COLLECTION_H diff --git a/ucx/cx/common.h b/ucx/cx/common.h new file mode 100644 index 0000000..8cb2339 --- /dev/null +++ b/ucx/cx/common.h @@ -0,0 +1,376 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file common.h + * + * @brief Common definitions and feature checks. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + * + * @mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

      + * 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 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UCX_COMMON_H +#define UCX_COMMON_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 3 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 1 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +// --------------------------------------------------------------------------- +// Common includes +// --------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Architecture Detection +// --------------------------------------------------------------------------- + +#ifndef INTPTR_MAX +#error Missing INTPTR_MAX definition +#endif +#if INTPTR_MAX == INT64_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 64 +#elif INTPTR_MAX == INT32_MAX +/** + * The address width in bits on this platform. + */ +#define CX_WORDSIZE 32 +#else +#error Unknown pointer size or missing size macros! +#endif + +// --------------------------------------------------------------------------- +// Missing Defines +// --------------------------------------------------------------------------- + +#ifndef SSIZE_MAX // not defined in glibc since C23 and MSVC +#if CX_WORDSIZE == 64 +/** + * The maximum representable value in ssize_t. + */ +#define SSIZE_MAX 0x7fffffffffffffffll +#else +#define SSIZE_MAX 0x7fffffffl +#endif +#endif + + +// --------------------------------------------------------------------------- +// Attribute definitions +// --------------------------------------------------------------------------- + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +/** + * All pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull __attribute__((__nonnull__)) + +/** + * The specified pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__))) + +/** + * The returned value is guaranteed to be non-NULL. + */ +#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__)) + +/** + * The attributed function always returns freshly allocated memory. + */ +#define cx_attr_malloc __attribute__((__malloc__)) + +#ifndef __clang__ +/** + * The pointer returned by the attributed function is supposed to be freed + * by @p freefunc. + * + * @param freefunc the function that shall be used to free the memory + * @param freefunc_arg the index of the pointer argument in @p freefunc + */ +#define cx_attr_dealloc(freefunc, freefunc_arg) \ + __attribute__((__malloc__(freefunc, freefunc_arg))) +#else +/** + * Not supported in clang. + */ +#define cx_attr_dealloc(...) +#endif // __clang__ + +/** + * Shortcut to specify #cxFree() as deallocator. + */ +#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2) + +/** + * Specifies the parameters from which the allocation size is calculated. + */ +#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__))) + + +#ifdef __clang__ +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +/** + * No support for access attribute in clang. + */ +#define cx_attr_access(mode, ...) +#else +#if __GNUC__ < 10 +/** + * No support for access attribute in GCC < 10. + */ +#define cx_attr_access(mode, ...) +#else +/** + * Helper macro to define access macros. + */ +#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__))) +#endif // __GNUC__ < 10 +#if __GNUC__ < 14 +/** + * No support for @c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +#else +/** + * The specified argument is expected to be a zero-terminated string. + * + * @param idx the index of the argument + */ +#define cx_attr_cstr_arg(idx) \ + __attribute__((__null_terminated_string_arg__(idx))) +#endif // __GNUC__ < 14 +#endif // __clang__ + + +/** + * Specifies that the function will only read through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__) + +/** + * Specifies that the function will read and write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__) + +/** + * Specifies that the function will only write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) + +#if __STDC_VERSION__ >= 202300L + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused [[maybe_unused]] + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard [[nodiscard]] + +#else // no C23 + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused __attribute__((__unused__)) + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard __attribute__((__warn_unused_result__)) + +#endif // __STDC_VERSION__ + +// --------------------------------------------------------------------------- +// Useful function pointers +// --------------------------------------------------------------------------- + +/** + * Function pointer compatible with fwrite-like functions. + */ +typedef size_t (*cx_write_func)( + const void *, + size_t, + size_t, + void * +); + +/** + * Function pointer compatible with fread-like functions. + */ +typedef size_t (*cx_read_func)( + void *, + size_t, + size_t, + void * +); + +// --------------------------------------------------------------------------- +// Utility macros +// --------------------------------------------------------------------------- + +/** + * Determines the number of members in a static C array. + * + * @attention never use this to determine the size of a dynamically allocated + * array. + * + * @param arr the array identifier + * @return the number of elements + */ +#define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0])) + +// --------------------------------------------------------------------------- +// szmul implementation +// --------------------------------------------------------------------------- + +#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) +#define CX_SZMUL_BUILTIN +#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) +#else // no GNUC or clang bultin +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a (@c size_t) first operand + * @param b (@c size_t) second operand + * @param result (@c size_t*) a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow + */ +#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) + +/** + * Implementation of cx_szmul() when no compiler builtin is available. + * + * Do not use in application code. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a variable, where the result should + * be stored + * @retval zero success + * @retval non-zero the multiplication would overflow + */ +#if __cplusplus +extern "C" +#endif +int cx_szmul_impl(size_t a, size_t b, size_t *result); +#endif // cx_szmul + + +// --------------------------------------------------------------------------- +// Fixes for MSVC incompatibilities +// --------------------------------------------------------------------------- + +#ifdef _MSC_VER +// fix missing ssize_t definition +#include +typedef SSIZE_T ssize_t; + +// fix missing _Thread_local support +#define _Thread_local __declspec(thread) +#endif // _MSC_VER + +#endif // UCX_COMMON_H diff --git a/ucx/cx/common.h.orig b/ucx/cx/common.h.orig new file mode 100644 index 0000000..e6ce33b --- /dev/null +++ b/ucx/cx/common.h.orig @@ -0,0 +1,138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file common.h + * + * \brief Common definitions and feature checks. + * + * \author Mike Becker + * \author Olaf Wintermann + * \version 3.0 + * \copyright 2-Clause BSD License + * + * \mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

      + * 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 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UCX_COMMON_H +#define UCX_COMMON_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 3 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 0 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +#define __attribute__(...) + +#include +#include +#include +#include + +/** + * Function pointer compatible with fwrite-like functions. + */ +typedef size_t (*cx_write_func)( + void const *, + size_t, + size_t, + void * +); + +/** + * Function pointer compatible with fread-like functions. + */ +typedef size_t (*cx_read_func)( + void *, + size_t, + size_t, + void * +); + +#ifdef _WIN32 + +#ifdef __MINGW32__ +#include +#endif // __MINGW32__ + +#else // !_WIN32 + +#include + +#endif // _WIN32 + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +#endif // UCX_COMMON_H diff --git a/ucx/cx/compare.h b/ucx/cx/compare.h new file mode 100644 index 0000000..98a39ff --- /dev/null +++ b/ucx/cx/compare.h @@ -0,0 +1,529 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file compare.h + * @brief A collection of simple compare functions. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_COMPARE_H +#define UCX_COMPARE_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A comparator function comparing two arbitrary values. + * + * All functions from compare.h with the cx_cmp prefix are + * compatible with this signature and can be used as + * compare function for collections, or other implementations + * that need to be type-agnostic. + * + * For simple comparisons the cx_vcmp family of functions + * can be used, but they are NOT compatible with this function + * pointer. + */ +cx_attr_nonnull +cx_attr_nodiscard +typedef int (*cx_compare_func)( + const void *left, + const void *right +); + +/** + * Compares two integers of type int. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to integer one + * @param i2 pointer to integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_int(const void *i1, const void *i2); + +/** + * Compares two ints. + * + * @param i1 integer one + * @param i2 integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int(int i1, int i2); + +/** + * Compares two integers of type long int. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to long integer one + * @param i2 pointer to long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_longint(const void *i1, const void *i2); + +/** + * Compares two long ints. + * + * @param i1 long integer one + * @param i2 long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_longint(long int i1, long int i2); + +/** + * Compares two integers of type long long. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to long long one + * @param i2 pointer to long long two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_longlong(const void *i1, const void *i2); + +/** + * Compares twolong long ints. + * + * @param i1 long long int one + * @param i2 long long int two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_longlong(long long int i1, long long int i2); + +/** + * Compares two integers of type int16_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_int16(const void *i1, const void *i2); + +/** + * Compares two integers of type int16_t. + * + * @param i1 int16_t one + * @param i2 int16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int16(int16_t i1, int16_t i2); + +/** + * Compares two integers of type int32_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to int32_t one + * @param i2 pointer to int32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_int32(const void *i1, const void *i2); + +/** + * Compares two integers of type int32_t. + * + * @param i1 int32_t one + * @param i2 int32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int32(int32_t i1, int32_t i2); + +/** + * Compares two integers of type int64_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_int64(const void *i1, const void *i2); + +/** + * Compares two integers of type int64_t. + * + * @param i1 int64_t one + * @param i2 int64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_int64(int64_t i1, int64_t i2); + +/** + * Compares two integers of type unsigned int. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uint(const void *i1, const void *i2); + +/** + * Compares two unsigned ints. + * + * @param i1 unsigned integer one + * @param i2 unsigned integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint(unsigned int i1, unsigned int i2); + +/** + * Compares two integers of type unsigned long int. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to unsigned long integer one + * @param i2 pointer to unsigned long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ulongint(const void *i1, const void *i2); + +/** + * Compares two unsigned long ints. + * + * @param i1 unsigned long integer one + * @param i2 unsigned long integer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); + +/** + * Compares two integers of type unsigned long long. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to unsigned long long one + * @param i2 pointer to unsigned long long two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ulonglong(const void *i1, const void *i2); + +/** + * Compares two unsigned long long ints. + * + * @param i1 unsigned long long one + * @param i2 unsigned long long two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); + +/** + * Compares two integers of type uint16_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uint16(const void *i1, const void *i2); + +/** + * Compares two integers of type uint16_t. + * + * @param i1 uint16_t one + * @param i2 uint16_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint16(uint16_t i1, uint16_t i2); + +/** + * Compares two integers of type uint32_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uint32(const void *i1, const void *i2); + +/** + * Compares two integers of type uint32_t. + * + * @param i1 uint32_t one + * @param i2 uint32_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint32(uint32_t i1, uint32_t i2); + +/** + * Compares two integers of type uint64_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to uint64_t one + * @param i2 pointer to uint64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uint64(const void *i1, const void *i2); + +/** + * Compares two integers of type uint64_t. + * + * @param i1 uint64_t one + * @param i2 uint64_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uint64(uint64_t i1, uint64_t i2); + +/** + * Compares two real numbers of type float with precision 1e-6f. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param f1 pointer to float one + * @param f2 pointer to float two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_float(const void *f1, const void *f2); + +/** + * Compares two real numbers of type float with precision 1e-6f. + * + * @param f1 float one + * @param f2 float two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_float(float f1, float f2); + +/** + * Compares two real numbers of type double with precision 1e-14. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param d1 pointer to double one + * @param d2 pointer to double two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_double(const void *d1, const void *d2); + +/** + * Convenience function + * + * @param d1 double one + * @param d2 double two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_double(double d1, double d2); + +/** + * Compares the integer representation of two pointers. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param ptr1 pointer to pointer one (const intptr_t*) + * @param ptr2 pointer to pointer two (const intptr_t*) + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_intptr(const void *ptr1, const void *ptr2); + +/** + * Compares the integer representation of two pointers. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); + +/** + * Compares the unsigned integer representation of two pointers. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param ptr1 pointer to pointer one (const uintptr_t*) + * @param ptr2 pointer to pointer two (const uintptr_t*) + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uintptr(const void *ptr1, const void *ptr2); + +/** + * Compares the unsigned integer representation of two pointers. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); + +/** + * Compares the pointers specified in the arguments without de-referencing. + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ptr(const void *ptr1, const void *ptr2); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //UCX_COMPARE_H diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h new file mode 100644 index 0000000..f22721e --- /dev/null +++ b/ucx/cx/hash_key.h @@ -0,0 +1,148 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file hash_key.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + + +#ifndef UCX_HASH_KEY_H +#define UCX_HASH_KEY_H + +#include "common.h" +#include "string.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Internal structure for a key within a hash map. */ +struct cx_hash_key_s { + /** The key data. */ + const void *data; + /** + * The key data length. + */ + size_t len; + /** The hash value of the key data. */ + unsigned hash; +}; + +/** + * Type for a hash key. + */ +typedef struct cx_hash_key_s CxHashKey; + +/** + * Computes a murmur2 32-bit hash. + * + * You need to initialize @c data and @c len in the key struct. + * The hash is then directly written to that struct. + * + * Usually you should not need this function. + * Use cx_hash_key(), instead. + * + * @note If @c data is @c NULL, the hash is defined as 1574210520. + * + * @param key the key, the hash shall be computed for + * @see cx_hash_key() + */ +cx_attr_nonnull +void cx_hash_murmur(CxHashKey *key); + +/** + * Computes a hash key from a string. + * + * The string needs to be zero-terminated. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_cstr_arg(1) +CxHashKey cx_hash_key_str(const char *str); + +/** + * Computes a hash key from a byte array. + * + * @param bytes the array + * @param len the length + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_access_r(1, 2) +CxHashKey cx_hash_key_bytes( + const unsigned char *bytes, + size_t len +); + +/** + * Computes a hash key for an arbitrary object. + * + * The computation uses the in-memory representation that might not be + * the same on different platforms. Therefore, this hash should not be + * used for data exchange with different machines. + * + * @param obj a pointer to an arbitrary object + * @param len the length of object in memory + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_access_r(1, 2) +CxHashKey cx_hash_key( + const void *obj, + size_t len +); + +/** + * Computes a hash key from a UCX string. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +static inline CxHashKey cx_hash_key_cxstr(cxstring str) { + return cx_hash_key(str.ptr, str.length); +} + +/** + * Computes a hash key from a UCX string. + * + * @param str (@c cxstring or @c cxmutstr) the string + * @return (@c CxHashKey) the hash key + */ +#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_HASH_KEY_H diff --git a/ucx/cx/hash_map.h b/ucx/cx/hash_map.h new file mode 100644 index 0000000..08d06a9 --- /dev/null +++ b/ucx/cx/hash_map.h @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file hash_map.h + * @brief Hash map implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_HASH_MAP_H +#define UCX_HASH_MAP_H + +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Internal structure for an element of a hash map. */ +struct cx_hash_map_element_s; + +/** + * Internal structure for a hash map. + */ +struct cx_hash_map_s { + /** + * Base structure for maps. + */ + struct cx_map_s base; + /** + * The buckets of this map, each containing a linked list of elements. + */ + struct cx_hash_map_element_s **buckets; + /** + * The number of buckets. + */ + size_t bucket_count; +}; + + +/** + * Creates a new hash map with the specified number of buckets. + * + * If @p buckets is zero, an implementation defined default will be used. + * + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if + * cxMapStorePointers() was called immediately after creation. + * + * @note Iterators provided by this hash map implementation provide the remove operation. + * The index value of an iterator is incremented when the iterator advanced without removal. + * In other words, when the iterator is finished, @c index==size . + * + * @param allocator the allocator to use + * (if @c NULL, a default stdlib allocator will be used) + * @param itemsize the size of one element + * @param buckets the initial number of buckets in this hash map + * @return a pointer to the new hash map + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMapFree, 1) +CxMap *cxHashMapCreate( + const CxAllocator *allocator, + size_t itemsize, + size_t buckets +); + +/** + * Creates a new hash map with a default number of buckets. + * + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if + * cxMapStorePointers() was called immediately after creation. + * + * @note Iterators provided by this hash map implementation provide the remove operation. + * The index value of an iterator is incremented when the iterator advanced without removal. + * In other words, when the iterator is finished, @c index==size . + * + * @param itemsize (@c size_t) the size of one element + * @return (@c CxMap*) a pointer to the new hash map + */ +#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0) + +/** + * Increases the number of buckets, if necessary. + * + * The load threshold is @c 0.75*buckets. If the element count exceeds the load + * threshold, the map will be rehashed. Otherwise, no action is performed and + * this function simply returns 0. + * + * The rehashing process ensures, that the number of buckets is at least + * 2.5 times the element count. So there is enough room for additional + * elements without the need of another soon rehashing. + * + * You can use this function after filling a map to increase access performance. + * + * @note If the specified map is not a hash map, the behavior is undefined. + * + * @param map the map to rehash + * @retval zero success + * @retval non-zero if a memory allocation error occurred + */ +cx_attr_nonnull +int cxMapRehash(CxMap *map); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_HASH_MAP_H diff --git a/ucx/cx/iterator.h b/ucx/cx/iterator.h new file mode 100644 index 0000000..1b83b26 --- /dev/null +++ b/ucx/cx/iterator.h @@ -0,0 +1,332 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file iterator.h + * @brief Interface for iterator implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_ITERATOR_H +#define UCX_ITERATOR_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Common data for all iterators. + */ +struct cx_iterator_base_s { + /** + * True iff the iterator points to valid data. + */ + cx_attr_nonnull + bool (*valid)(const void *); + + /** + * Returns a pointer to the current element. + * + * When valid returns false, the behavior of this function is undefined. + */ + cx_attr_nonnull + cx_attr_nodiscard + void *(*current)(const void *); + + /** + * Original implementation in case the function needs to be wrapped. + */ + cx_attr_nonnull + cx_attr_nodiscard + void *(*current_impl)(const void *); + + /** + * Advances the iterator. + * + * When valid returns false, the behavior of this function is undefined. + */ + cx_attr_nonnull + void (*next)(void *); + /** + * Indicates whether this iterator may remove elements. + */ + bool mutating; + /** + * Internal flag for removing the current element when advancing. + */ + bool remove; +}; + +/** + * Declares base attributes for an iterator. + * Must be the first member of an iterator structure. + */ +#define CX_ITERATOR_BASE struct cx_iterator_base_s base + +/** + * Internal iterator struct - use CxIterator. + */ +struct cx_iterator_s { + /** + * Inherited common data for all iterators. + */ + CX_ITERATOR_BASE; + + /** + * Handle for the current element. + */ + void *elem_handle; + + /** + * Handle for the source collection, if any. + */ + union { + /** + * Access for mutating iterators. + */ + void *m; + /** + * Access for normal iterators. + */ + const void *c; + } src_handle; + + /** + * Field for storing a key-value pair. + * May be used by iterators that iterate over k/v-collections. + */ + struct { + /** + * A pointer to the key. + */ + const void *key; + /** + * A pointer to the value. + */ + void *value; + } kv_data; + + /** + * Field for storing a slot number. + * May be used by iterators that iterate over multi-bucket collections. + */ + size_t slot; + + /** + * If the iterator is position-aware, contains the index of the element in the underlying collection. + * Otherwise, this field is usually uninitialized. + */ + size_t index; + + /** + * The size of an individual element. + */ + size_t elem_size; + + /** + * May contain the total number of elements, if known. + * Shall be set to @c SIZE_MAX when the total number is unknown during iteration. + */ + size_t elem_count; +}; + +/** + * Iterator type. + * + * An iterator points to a certain element in a (possibly unbounded) chain of elements. + * Iterators that are based on collections (which have a defined "first" element), are supposed + * to be "position-aware", which means that they keep track of the current index within the collection. + * + * @note Objects that are pointed to by an iterator are always mutable through that iterator. However, + * any concurrent mutation of the collection other than by this iterator makes this iterator invalid, + * and it must not be used anymore. + */ +typedef struct cx_iterator_s CxIterator; + +/** + * Checks if the iterator points to valid data. + * + * This is especially false for past-the-end iterators. + * + * @param iter the iterator + * @retval true if the iterator points to valid data + * @retval false if the iterator already moved past the end + */ +#define cxIteratorValid(iter) (iter).base.valid(&(iter)) + +/** + * Returns a pointer to the current element. + * + * The behavior is undefined if this iterator is invalid. + * + * @param iter the iterator + * @return a pointer to the current element + * @see cxIteratorValid() + */ +#define cxIteratorCurrent(iter) (iter).base.current(&iter) + +/** + * Advances the iterator to the next element. + * + * @param iter the iterator + */ +#define cxIteratorNext(iter) (iter).base.next(&iter) + +/** + * Flags the current element for removal, if this iterator is mutating. + * + * Does nothing for non-mutating iterators. + * + * @param iter the iterator + */ +#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating + +/** + * Obtains a reference to an arbitrary iterator. + * + * This is useful for APIs that expect some iterator as an argument. + * + * @param iter the iterator + * @return (@c CxIterator*) a pointer to the iterator + */ +#define cxIteratorRef(iter) &((iter).base) + +/** + * Loops over an iterator. + * + * @param type the type of the elements + * @param elem the name of the iteration variable + * @param iter the iterator + */ +#define cx_foreach(type, elem, iter) \ +for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter)) + + +/** + * Creates an iterator for the specified plain array. + * + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. + * + * This iterator yields the addresses of the array elements. + * If you want to iterator over an array of pointers, you can + * use cxIteratorPtr() to create an iterator which directly + * yields the stored pointers. + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_size the size of one array element + * @param elem_count the number of elements in the array + * @return an iterator for the specified array + * @see cxIteratorPtr() + */ +cx_attr_nodiscard +CxIterator cxIterator( + const void *array, + size_t elem_size, + size_t elem_count +); + +/** + * Creates a mutating iterator for the specified plain array. + * + * While the iterator is in use, the array may only be altered by removing + * elements through #cxIteratorFlagRemoval(). Every other change to the array + * will bring this iterator to an undefined state. + * + * When @p remove_keeps_order is set to @c false, removing an element will only + * move the last element to the position of the removed element, instead of + * moving all subsequent elements by one. Usually, when the order of elements is + * not important, this parameter should be set to @c false. + * + * The @p array can be @c NULL in which case the iterator will be immediately + * initialized such that #cxIteratorValid() returns @c false. + * + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_size the size of one array element + * @param elem_count the number of elements in the array + * @param remove_keeps_order @c true if the order of elements must be preserved + * when removing an element + * @return an iterator for the specified array + */ +cx_attr_nodiscard +CxIterator cxMutIterator( + void *array, + size_t elem_size, + size_t elem_count, + bool remove_keeps_order +); + +/** + * Creates an iterator for the specified plain pointer array. + * + * This iterator assumes that every element in the array is a pointer + * and yields exactly those pointers during iteration (while in contrast + * an iterator created with cxIterator() would return the addresses + * of those pointers within the array). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @return an iterator for the specified array + * @see cxIterator() + */ +cx_attr_nodiscard +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +); + +/** + * Creates a mutating iterator for the specified plain pointer array. + * + * This is the mutating variant of cxIteratorPtr(). See also + * cxMutIterator(). + * + * @param array a pointer to the array (can be @c NULL) + * @param elem_count the number of elements in the array + * @param remove_keeps_order @c true if the order of elements must be preserved + * when removing an element + * @return an iterator for the specified array + * @see cxMutIterator() + * @see cxIteratorPtr() + */ +cx_attr_nodiscard +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_ITERATOR_H diff --git a/ucx/cx/json.h b/ucx/cx/json.h new file mode 100644 index 0000000..7c97f23 --- /dev/null +++ b/ucx/cx/json.h @@ -0,0 +1,1357 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file json.h + * @brief Interface for parsing data from JSON files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_JSON_H +#define UCX_JSON_H + +#include "common.h" +#include "allocator.h" +#include "string.h" +#include "buffer.h" +#include "array_list.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The type of the parsed token. + */ +enum cx_json_token_type { + /** + * No complete token parsed, yet. + */ + CX_JSON_NO_TOKEN, + /** + * The presumed token contains syntactical errors. + */ + CX_JSON_TOKEN_ERROR, + /** + * A "begin of array" '[' token. + */ + CX_JSON_TOKEN_BEGIN_ARRAY, + /** + * A "begin of object" '{' token. + */ + CX_JSON_TOKEN_BEGIN_OBJECT, + /** + * An "end of array" ']' token. + */ + CX_JSON_TOKEN_END_ARRAY, + /** + * An "end of object" '}' token. + */ + CX_JSON_TOKEN_END_OBJECT, + /** + * A colon ':' token separating names and values. + */ + CX_JSON_TOKEN_NAME_SEPARATOR, + /** + * A comma ',' token separating object entries or array elements. + */ + CX_JSON_TOKEN_VALUE_SEPARATOR, + /** + * A string token. + */ + CX_JSON_TOKEN_STRING, + /** + * A number token that can be represented as integer. + */ + CX_JSON_TOKEN_INTEGER, + /** + * A number token that cannot be represented as integer. + */ + CX_JSON_TOKEN_NUMBER, + /** + * A literal token. + */ + CX_JSON_TOKEN_LITERAL, + /** + * A white-space token. + */ + CX_JSON_TOKEN_SPACE +}; + +/** + * The type of some JSON value. + */ +enum cx_json_value_type { + /** + * Reserved. + */ + CX_JSON_NOTHING, // this allows us to always return non-NULL values + /** + * A JSON object. + */ + CX_JSON_OBJECT, + /** + * A JSON array. + */ + CX_JSON_ARRAY, + /** + * A string. + */ + CX_JSON_STRING, + /** + * A number that contains an integer. + */ + CX_JSON_INTEGER, + /** + * A number, not necessarily an integer. + */ + CX_JSON_NUMBER, + /** + * A literal (true, false, null). + */ + CX_JSON_LITERAL +}; + +/** + * JSON literal types. + */ +enum cx_json_literal { + /** + * The @c null literal. + */ + CX_JSON_NULL, + /** + * The @c true literal. + */ + CX_JSON_TRUE, + /** + * The @c false literal. + */ + CX_JSON_FALSE +}; + +/** + * Type alias for the token type enum. + */ +typedef enum cx_json_token_type CxJsonTokenType; +/** + * Type alias for the value type enum. + */ +typedef enum cx_json_value_type CxJsonValueType; + +/** + * Type alias for the JSON parser interface. + */ +typedef struct cx_json_s CxJson; + +/** + * Type alias for the token struct. + */ +typedef struct cx_json_token_s CxJsonToken; + +/** + * Type alias for the JSON value struct. + */ +typedef struct cx_json_value_s CxJsonValue; + +/** + * Type alias for the JSON array struct. + */ +typedef struct cx_json_array_s CxJsonArray; +/** + * Type alias for the JSON object struct. + */ +typedef struct cx_json_object_s CxJsonObject; +/** + * Type alias for a JSON string. + */ +typedef struct cx_mutstr_s CxJsonString; +/** + * Type alias for a number that can be represented as 64-bit signed integer. + */ +typedef int64_t CxJsonInteger; +/** + * Type alias for number that is not an integer. + */ +typedef double CxJsonNumber; +/** + * Type alias for a JSON literal. + */ +typedef enum cx_json_literal CxJsonLiteral; + +/** + * Type alias for a key/value pair in a JSON object. + */ +typedef struct cx_json_obj_value_s CxJsonObjValue; + +/** + * JSON array structure. + */ +struct cx_json_array_s { + /** + * The array data. + */ + CX_ARRAY_DECLARE(CxJsonValue*, array); +}; + +/** + * JSON object structure. + */ +struct cx_json_object_s { + /** + * The key/value entries. + */ + CX_ARRAY_DECLARE(CxJsonObjValue, values); + /** + * The original indices to reconstruct the order in which the members were added. + */ + size_t *indices; +}; + +/** + * Structure for a key/value entry in a JSON object. + */ +struct cx_json_obj_value_s { + /** + * The key (or name in JSON terminology) of the value. + */ + cxmutstr name; + /** + * The value. + */ + CxJsonValue *value; +}; + +/** + * Structure for a JSON value. + */ +struct cx_json_value_s { + /** + * The allocator with which the value was allocated. + * + * Required for recursively deallocating memory of objects and arrays. + */ + const CxAllocator *allocator; + /** + * The type of this value. + * + * Specifies how the @c value union shall be resolved. + */ + CxJsonValueType type; + /** + * The value data. + */ + union { + /** + * The array data if type is #CX_JSON_ARRAY. + */ + CxJsonArray array; + /** + * The object data if type is #CX_JSON_OBJECT. + */ + CxJsonObject object; + /** + * The string data if type is #CX_JSON_STRING. + */ + CxJsonString string; + /** + * The integer if type is #CX_JSON_INTEGER. + */ + CxJsonInteger integer; + /** + * The number if type is #CX_JSON_NUMBER. + */ + CxJsonNumber number; + /** + * The literal type if type is #CX_JSON_LITERAL. + */ + CxJsonLiteral literal; + } value; +}; + +/** + * Internally used structure for a parsed token. + * + * You should never need to use this in your code. + */ +struct cx_json_token_s { + /** + * The token type. + */ + CxJsonTokenType tokentype; + /** + * True, iff the @c content must be passed to cx_strfree(). + */ + bool allocated; + /** + * The token text, if any. + * + * This is not necessarily set when the token type already + * uniquely identifies the content. + */ + cxmutstr content; +}; + +/** + * The JSON parser interface. + */ +struct cx_json_s { + /** + * The allocator used for produced JSON values. + */ + const CxAllocator *allocator; + /** + * The input buffer. + */ + CxBuffer buffer; + + /** + * Used internally. + * + * Remembers the prefix of the last uncompleted token. + */ + CxJsonToken uncompleted; + + /** + * A pointer to an intermediate state of the currently parsed value. + * + * Never access this value manually. + */ + CxJsonValue *parsed; + + /** + * A pointer to an intermediate state of a currently parsed object member. + * + * Never access this value manually. + */ + CxJsonObjValue uncompleted_member; + + /** + * State stack. + */ + CX_ARRAY_DECLARE_SIZED(int, states, unsigned); + + /** + * Value buffer stack. + */ + CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned); + + /** + * Internally reserved memory for the state stack. + */ + int states_internal[8]; + + /** + * Internally reserved memory for the value buffer stack. + */ + CxJsonValue* vbuf_internal[8]; + + /** + * Used internally. + */ + bool tokenizer_escape; // TODO: check if it can be replaced with look-behind +}; + +/** + * Status codes for the json interface. + */ +enum cx_json_status { + /** + * Everything is fine. + */ + CX_JSON_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_JSON_NO_DATA, + /** + * The input ends unexpectedly. + * + * Refill the buffer with cxJsonFill() to complete the json data. + */ + CX_JSON_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_JSON_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_JSON_OK, + /** + * The input buffer has never been filled. + */ + CX_JSON_NULL_DATA, + /** + * Allocating memory for the internal buffer failed. + */ + CX_JSON_BUFFER_ALLOC_FAILED, + /** + * Allocating memory for a json value failed. + */ + CX_JSON_VALUE_ALLOC_FAILED, + /** + * A number value is incorrectly formatted. + */ + CX_JSON_FORMAT_ERROR_NUMBER, + /** + * The tokenizer found something unexpected. + */ + CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN +}; + +/** + * Typedef for the json status enum. + */ +typedef enum cx_json_status CxJsonStatus; + +/** + * The JSON writer settings. + */ +struct cx_json_writer_s { + /** + * Set true to enable pretty output. + */ + bool pretty; + /** + * Set false to output the members in the order in which they were added. + */ + bool sort_members; + /** + * The maximum number of fractional digits in a number value. + */ + uint8_t frac_max_digits; + /** + * Set true to use spaces instead of tab characters. + * Indentation is only used in pretty output. + */ + bool indent_space; + /** + * If @c indent_space is true, this is the number of spaces per tab. + * Indentation is only used in pretty output. + */ + uint8_t indent; +}; + +/** + * Typedef for the json writer. + */ +typedef struct cx_json_writer_s CxJsonWriter; + +/** + * Creates a default writer configuration for compact output. + * + * @return new JSON writer settings + */ +cx_attr_nodiscard +CxJsonWriter cxJsonWriterCompact(void); + +/** + * Creates a default writer configuration for pretty output. + * + * @param use_spaces false if you want tabs, true if you want four spaces instead + * @return new JSON writer settings + */ +cx_attr_nodiscard +CxJsonWriter cxJsonWriterPretty(bool use_spaces); + +/** + * Writes a JSON value to a buffer or stream. + * + * This function blocks until all data is written or an error when trying + * to write data occurs. + * The write operation is not atomic in the sense that it might happen + * that the data is only partially written when an error occurs with no + * way to indicate how much data was written. + * To avoid this problem, you can use a CxBuffer as @p target which is + * unlikely to fail a write operation and either use the buffer's flush + * feature to relay the data or use the data in the buffer manually to + * write it to the actual target. + * + * @param target the buffer or stream where to write to + * @param value the value that shall be written + * @param wfunc the write function to use + * @param settings formatting settings (or @c NULL to use a compact default) + * @retval zero success + * @retval non-zero when no or not all data could be written + */ +cx_attr_nonnull_arg(1, 2, 3) +int cxJsonWrite( + void* target, + const CxJsonValue* value, + cx_write_func wfunc, + const CxJsonWriter* settings +); + +/** + * Initializes the json interface. + * + * @param json the json interface + * @param allocator the allocator that shall be used for the produced values + * @see cxJsonDestroy() + */ +cx_attr_nonnull_arg(1) +void cxJsonInit(CxJson *json, const CxAllocator *allocator); + +/** + * Destroys the json interface. + * + * @param json the json interface + * @see cxJsonInit() + */ +cx_attr_nonnull +void cxJsonDestroy(CxJson *json); + +/** + * Destroys and re-initializes the json interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param json the json interface + */ +cx_attr_nonnull +static inline void cxJsonReset(CxJson *json) { + const CxAllocator *allocator = json->allocator; + cxJsonDestroy(json); + cxJsonInit(json, allocator); +} + +/** + * Fills the input buffer. + * + * @remark The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param buf the source buffer + * @param len the length of the source buffer + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonFilln(CxJson *json, const char *buf, size_t len); + +#ifdef __cplusplus +} // extern "C" + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxJsonFill( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxJsonFill( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer. + * + * The JSON interface tries to avoid copying the input data. + * When you use this function and cxJsonNext() interleaving, + * no copies are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxJsonNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param json the json interface + * @param str the source string + * @retval zero success + * @retval non-zero internal allocation error + * @see cxJsonFilln() + */ +#define cxJsonFill(json, str) _Generic((str), \ + cxstring: cx_json_fill_cxstr, \ + cxmutstr: cx_json_fill_mutstr, \ + char*: cx_json_fill_str, \ + const char*: cx_json_fill_str) \ + (json, str) + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_cxstr( + CxJson *json, + cxstring str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +static inline int cx_json_fill_mutstr( + CxJson *json, + cxmutstr str +) { + return cxJsonFilln(json, str.ptr, str.length); +} + +/** + * @copydoc cxJsonFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_json_fill_str( + CxJson *json, + const char *str +) { + return cxJsonFilln(json, str, strlen(str)); +} +#endif + +/** + * Creates a new (empty) JSON object. + * + * @param allocator the allocator to use + * @return the new JSON object or @c NULL if allocation fails + * @see cxJsonObjPutObj() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); + +/** + * Creates a new (empty) JSON array. + * + * @param allocator the allocator to use + * @return the new JSON array or @c NULL if allocation fails + * @see cxJsonObjPutArr() + * @see cxJsonArrAddValues() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); + +/** + * Creates a new JSON number value. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutNumber() + * @see cxJsonArrAddNumbers() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); + +/** + * Creates a new JSON number value based on an integer. + * + * @param allocator the allocator to use + * @param num the numeric value + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutInteger() + * @see cxJsonArrAddIntegers() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateString() + * @see cxJsonObjPutString() + * @see cxJsonArrAddStrings() + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(2) +cx_attr_cstr_arg(2) +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); + +/** + * Creates a new JSON string. + * + * @param allocator the allocator to use + * @param str the string data + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonCreateCxString() + * @see cxJsonObjPutCxString() + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); + +/** + * Creates a new JSON literal. + * + * @param allocator the allocator to use + * @param lit the type of literal + * @return the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutLiteral() + * @see cxJsonArrAddLiterals() + */ +cx_attr_nodiscard +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); + +/** + * Adds number values to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); + +/** + * Adds number values, of which all are integers, to a JSON array. + * + * @param arr the JSON array + * @param num the array of values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddCxStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); + +/** + * Adds strings to a JSON array. + * + * The strings will be copied with the allocator of the array. + * + * @param arr the JSON array + * @param str the array of strings + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + * @see cxJsonArrAddStrings() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); + +/** + * Adds literals to a JSON array. + * + * @param arr the JSON array + * @param lit the array of literal types + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); + +/** + * Add arbitrary values to a JSON array. + * + * @attention In contrast to all other add functions, this function adds the values + * directly to the array instead of copying them. + * + * @param arr the JSON array + * @param val the values + * @param count the number of elements + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); + +/** + * Adds or replaces a value within a JSON object. + * + * The value will be directly added and not copied. + * + * @note If a value with the specified @p name already exists, + * it will be (recursively) freed with its own allocator. + * + * @param obj the JSON object + * @param name the name of the value + * @param child the value + * @retval zero success + * @retval non-zero allocation failure + */ +cx_attr_nonnull +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); + +/** + * Creates a new JSON object and adds it to an existing object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateObj() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON array and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateArr() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON number and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateNumber() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); + +/** + * Creates a new JSON number, based on an integer, and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param num the numeric value + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateInteger() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateString() + */ +cx_attr_nonnull +cx_attr_cstr_arg(3) +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); + +/** + * Creates a new JSON string and adds it to an object. + * + * The string data is copied. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param str the string data + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateCxString() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); + +/** + * Creates a new JSON literal and adds it to an object. + * + * @param obj the target JSON object + * @param name the name of the new value + * @param lit the type of literal + * @return the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateLiteral() + */ +cx_attr_nonnull +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); + +/** + * Recursively deallocates the memory of a JSON value. + * + * @remark The type of each deallocated value will be changed + * to #CX_JSON_NOTHING and values of such type will be skipped + * by the de-allocation. That means, this function protects + * you from double-frees when you are accidentally freeing + * a nested value and then the parent value (or vice versa). + * + * @param value the value + */ +void cxJsonValueFree(CxJsonValue *value); + +/** + * Tries to obtain the next JSON value. + * + * Before this function can be called, the input buffer needs + * to be filled with cxJsonFill(). + * + * When this function returns #CX_JSON_INCOMPLETE_DATA, you can + * add the missing data with another invocation of cxJsonFill() + * and then repeat the call to cxJsonNext(). + * + * @param json the json interface + * @param value a pointer where the next value shall be stored + * @retval CX_JSON_NO_ERROR successfully retrieve the @p value + * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from + * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read + * and more data needs to be filled + * @retval CX_JSON_NULL_DATA the buffer was never initialized + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +cx_attr_nonnull +cx_attr_access_w(2) +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); + +/** + * Checks if the specified value is a JSON object. + * + * @param value a pointer to the value + * @retval true the value is a JSON object + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsObject(const CxJsonValue *value) { + return value->type == CX_JSON_OBJECT; +} + +/** + * Checks if the specified value is a JSON array. + * + * @param value a pointer to the value + * @retval true the value is a JSON array + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsArray(const CxJsonValue *value) { + return value->type == CX_JSON_ARRAY; +} + +/** + * Checks if the specified value is a string. + * + * @param value a pointer to the value + * @retval true the value is a string + * @retval false otherwise + */ +cx_attr_nonnull +static inline bool cxJsonIsString(const CxJsonValue *value) { + return value->type == CX_JSON_STRING; +} + +/** + * Checks if the specified value is a JSON number. + * + * This function will return true for both floating point and + * integer numbers. + * + * @param value a pointer to the value + * @retval true the value is a JSON number + * @retval false otherwise + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline bool cxJsonIsNumber(const CxJsonValue *value) { + return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is an integer number. + * + * @param value a pointer to the value + * @retval true the value is an integer number + * @retval false otherwise + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline bool cxJsonIsInteger(const CxJsonValue *value) { + return value->type == CX_JSON_INTEGER; +} + +/** + * Checks if the specified value is a JSON literal. + * + * JSON literals are @c true, @c false, and @c null. + * + * @param value a pointer to the value + * @retval true the value is a JSON literal + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + * @see cxJsonIsNull() + */ +cx_attr_nonnull +static inline bool cxJsonIsLiteral(const CxJsonValue *value) { + return value->type == CX_JSON_LITERAL; +} + +/** + * Checks if the specified value is a Boolean literal. + * + * @param value a pointer to the value + * @retval true the value is either @c true or @c false + * @retval false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsBool(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; +} + +/** + * Checks if the specified value is @c true. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsFalse(v). + * + * @param value a pointer to the value + * @retval true the value is @c true + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsFalse() + */ +cx_attr_nonnull +static inline bool cxJsonIsTrue(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; +} + +/** + * Checks if the specified value is @c false. + * + * @remark Be advised, that this is not the same as + * testing @c !cxJsonIsTrue(v). + * + * @param value a pointer to the value + * @retval true the value is @c false + * @retval false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsTrue() + */ +cx_attr_nonnull +static inline bool cxJsonIsFalse(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; +} + +/** + * Checks if the specified value is @c null. + * + * @param value a pointer to the value + * @retval true the value is @c null + * @retval false otherwise + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonIsNull(const CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; +} + +/** + * Obtains a C string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as C string + * @see cxJsonIsString() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline char *cxJsonAsString(const CxJsonValue *value) { + return value->value.string.ptr; +} + +/** + * Obtains a UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { + return cx_strcast(value->value.string); +} + +/** + * Obtains a mutable UCX string from the given JSON value. + * + * If the @p value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as mutable UCX string + * @see cxJsonIsString() + */ +cx_attr_nonnull +static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { + return value->value.string; +} + +/** + * Obtains a double-precision floating point value from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + */ +cx_attr_nonnull +static inline double cxJsonAsDouble(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return (double) value->value.integer; + } else { + return value->value.number; + } +} + +/** + * Obtains a 64-bit signed integer from the given JSON value. + * + * If the @p value is not a JSON number, the behavior is undefined. + * If it is a JSON number, but not an integer, the value will be + * converted to an integer, possibly losing precision. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + * @see cxJsonIsInteger() + */ +cx_attr_nonnull +static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return value->value.integer; + } else { + return (int64_t) value->value.number; + } +} + +/** + * Obtains a Boolean value from the given JSON value. + * + * If the @p value is not a JSON literal, the behavior is undefined. + * The @c null literal is interpreted as @c false. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsLiteral() + */ +cx_attr_nonnull +static inline bool cxJsonAsBool(const CxJsonValue *value) { + return value->value.literal == CX_JSON_TRUE; +} + +/** + * Returns the size of a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return the size of the array + * @see cxJsonIsArray() + */ +cx_attr_nonnull +static inline size_t cxJsonArrSize(const CxJsonValue *value) { + return value->value.array.array_size; +} + +/** + * Returns an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function guarantees to return a value. If the index is + * out of bounds, the returned value will be of type + * #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON value + * @param index the index in the array + * @return the value at the specified index + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); + +/** + * Returns an iterator over the JSON array elements. + * + * The iterator yields values of type @c CxJsonValue* . + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the array elements + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxJsonArrIter(const CxJsonValue *value); + +/** + * Returns an iterator over the JSON object members. + * + * The iterator yields values of type @c CxJsonObjValue* which + * contain the name and value of the member. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * @param value the JSON value + * @return an iterator over the object members + * @see cxJsonIsObject() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxJsonObjIter(const CxJsonValue *value); + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); + +#ifdef __cplusplus +} // extern "C" + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { + return cx_json_obj_get_cxstr(value, name); +} + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} + +extern "C" { +#else +/** + * Returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function guarantees to return a JSON value. If the + * object does not contain @p name, the returned JSON value + * will be of type #CX_JSON_NOTHING, but never @c NULL. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key + * @see cxJsonIsObject() + */ +#define cxJsonObjGet(value, name) _Generic((name), \ + cxstring: cx_json_obj_get_cxstr, \ + cxmutstr: cx_json_obj_get_mutstr, \ + char*: cx_json_obj_get_str, \ + const char*: cx_json_obj_get_str) \ + (value, name) + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_cstr_arg(2) +static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { + return cx_json_obj_get_cxstr(value, cx_str(name)); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_JSON_H */ + diff --git a/ucx/cx/linked_list.h b/ucx/cx/linked_list.h new file mode 100644 index 0000000..95d623a --- /dev/null +++ b/ucx/cx/linked_list.h @@ -0,0 +1,545 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file linked_list.h + * @brief Linked list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_LINKED_LIST_H +#define UCX_LINKED_LIST_H + +#include "common.h" +#include "list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The maximum item size that uses SBO swap instead of relinking. + * + */ +extern const unsigned cx_linked_list_swap_sbo_size; + +/** + * Allocates a linked list for storing elements with @p elem_size bytes each. + * + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if + * cxListStorePointers() was called immediately after creation and the compare + * function will be automatically set to cx_cmp_ptr(), if none is given. + * + * @param allocator the allocator for allocating the list nodes + * (if @c NULL, a default stdlib allocator will be used) + * @param comparator the comparator for the elements + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @return the created list + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) +CxList *cxLinkedListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +); + +/** + * Allocates a linked list for storing elements with @p elem_size bytes each. + * + * The list will use cxDefaultAllocator and no comparator function. If you want + * to call functions that need a comparator, you must either set one immediately + * after list creation or use cxLinkedListCreate(). + * + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if + * cxListStorePointers() was called immediately after creation and the compare + * function will be automatically set to cx_cmp_ptr(). + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list + */ +#define cxLinkedListCreateSimple(elem_size) \ + cxLinkedListCreate(NULL, NULL, elem_size) + +/** + * Finds the node at a certain index. + * + * This function can be used to start at an arbitrary position within the list. + * If the search index is large than the start index, @p loc_advance must denote + * the location of some sort of @c next pointer (i.e. a pointer to the next node). + * But it is also possible that the search index is smaller than the start index + * (e.g. in cases where traversing a list backwards is faster) in which case + * @p loc_advance must denote the location of some sort of @c prev pointer + * (i.e. a pointer to the previous node). + * + * @param start a pointer to the start node + * @param start_index the start index + * @param loc_advance the location of the pointer to advance + * @param index the search index + * @return the node found at the specified index + */ +cx_attr_nonnull +cx_attr_nodiscard +void *cx_linked_list_at( + const void *start, + size_t start_index, + ptrdiff_t loc_advance, + size_t index +); + +/** + * Finds the index of an element within a linked list. + * + * @param start a pointer to the start node + * @param loc_advance the location of the pointer to advance + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func a compare function to compare @p elem against the node data + * @param elem a pointer to the element to find + * @return the index of the element or a negative value if it could not be found + */ +cx_attr_nonnull +ssize_t cx_linked_list_find( + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +); + +/** + * Finds the node containing an element within a linked list. + * + * @param result a pointer to the memory where the node pointer (or @c NULL if the element + * could not be found) shall be stored to + * @param start a pointer to the start node + * @param loc_advance the location of the pointer to advance + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func a compare function to compare @p elem against the node data + * @param elem a pointer to the element to find + * @return the index of the element or a negative value if it could not be found + */ +cx_attr_nonnull +ssize_t cx_linked_list_find_node( + void **result, + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +); + +/** + * Finds the first node in a linked list. + * + * The function starts with the pointer denoted by @p node and traverses the list + * along a prev pointer whose location within the node struct is + * denoted by @p loc_prev. + * + * @param node a pointer to a node in the list + * @param loc_prev the location of the @c prev pointer + * @return a pointer to the first node + */ +cx_attr_nonnull +cx_attr_returns_nonnull +void *cx_linked_list_first( + const void *node, + ptrdiff_t loc_prev +); + +/** + * Finds the last node in a linked list. + * + * The function starts with the pointer denoted by @p node and traverses the list + * along a next pointer whose location within the node struct is + * denoted by @p loc_next. + * + * @param node a pointer to a node in the list + * @param loc_next the location of the @c next pointer + * @return a pointer to the last node + */ +cx_attr_nonnull +cx_attr_returns_nonnull +void *cx_linked_list_last( + const void *node, + ptrdiff_t loc_next +); + +/** + * Finds the predecessor of a node in case it is not linked. + * + * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined. + * + * @param begin the node where to start the search + * @param loc_next the location of the @c next pointer + * @param node the successor of the node to find + * @return the node or @c NULL if @p node has no predecessor + */ +cx_attr_nonnull +void *cx_linked_list_prev( + const void *begin, + ptrdiff_t loc_next, + const void *node +); + +/** + * Adds a new node to a linked list. + * The node must not be part of any list already. + * + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. + * + * @param begin a pointer to the beginning node pointer (if your list has one) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be appended + */ +cx_attr_nonnull_arg(5) +void cx_linked_list_add( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +); + +/** + * Prepends a new node to a linked list. + * The node must not be part of any list already. + * + * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. + * + * @param begin a pointer to the beginning node pointer (if your list has one) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be prepended + */ +cx_attr_nonnull_arg(5) +void cx_linked_list_prepend( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +); + +/** + * Links two nodes. + * + * @param left the new predecessor of @p right + * @param right the new successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + */ +cx_attr_nonnull +void cx_linked_list_link( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Unlinks two nodes. + * + * If right is not the successor of left, the behavior is undefined. + * + * @param left the predecessor of @p right + * @param right the successor of @p left + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + */ +cx_attr_nonnull +void cx_linked_list_unlink( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Inserts a new node after a given node of a linked list. + * The new node must not be part of any list already. + * + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list. + * + * @param begin a pointer to the beginning node pointer (if your list has one) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL if you want to prepend the node to the list) + * @param new_node a pointer to the node that shall be inserted + */ +cx_attr_nonnull_arg(6) +void cx_linked_list_insert( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *new_node +); + +/** + * Inserts a chain of nodes after a given node of a linked list. + * The chain must not be part of any list already. + * + * If you do not explicitly specify the end of the chain, it will be determined by traversing + * the @c next pointer. + * + * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or + * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need + * to provide a valid @p loc_prev location. + * Then the chain will be prepended to the list. + * + * @param begin a pointer to the beginning node pointer (if your list has one) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node after which to insert (@c NULL to prepend the chain to the list) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) + */ +cx_attr_nonnull_arg(6) +void cx_linked_list_insert_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *insert_begin, + void *insert_end +); + +/** + * Inserts a node into a sorted linked list. + * The new node must not be part of any list already. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + */ +cx_attr_nonnull_arg(1, 5, 6) +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +); + +/** + * Inserts a chain of nodes into a sorted linked list. + * The chain must not be part of any list already. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_chain(), the source chain + * will be broken and inserted into the target list so that the resulting list + * will be sorted according to @p cmp_func. That means, each node in the source + * chain may be re-linked with nodes from the target list. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + */ +cx_attr_nonnull_arg(1, 5, 6) +void cx_linked_list_insert_sorted_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +); + +/** + * Removes a chain of nodes from the linked list. + * + * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * addresses are provided, the pointers are adjusted accordingly. + * + * The following combinations of arguments are valid (more arguments are optional): + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) + * + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used + * to traverse to a former adjacent node in the list, or within the chain. + * + * @param begin a pointer to the beginning node pointer (optional) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the start node of the chain + * @param num the number of nodes to remove + * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) + */ +cx_attr_nonnull_arg(5) +size_t cx_linked_list_remove_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + size_t num +); + +/** + * Removes a node from the linked list. + * + * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * addresses are provided, the pointers are adjusted accordingly. + * + * The following combinations of arguments are valid (more arguments are optional): + * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) + * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) + * + * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used + * to traverse to a former adjacent node in the list. + * + * @param begin a pointer to the beginning node pointer (optional) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param node the node to remove + */ +cx_attr_nonnull_arg(5) +static inline void cx_linked_list_remove( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node +) { + cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); +} + +/** + * Determines the size of a linked list starting with @p node. + * + * @param node the first node + * @param loc_next the location of the @c next pointer within the node struct + * @return the size of the list or zero if @p node is @c NULL + */ +size_t cx_linked_list_size( + const void *node, + ptrdiff_t loc_next +); + +/** + * Sorts a linked list based on a comparison function. + * + * This function can work with linked lists of the following structure: + * @code + * typedef struct node node; + * struct node { + * node* prev; + * node* next; + * my_payload data; + * } + * @endcode + * + * @note This is a recursive function with at most logarithmic recursion depth. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func the compare function defining the sort order + */ +cx_attr_nonnull_arg(1, 6) +void cx_linked_list_sort( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + ptrdiff_t loc_data, + cx_compare_func cmp_func +); + + +/** + * Compares two lists element wise. + * + * @attention Both list must have the same structure. + * + * @param begin_left the beginning of the left list (@c NULL denotes an empty list) + * @param begin_right the beginning of the right list (@c NULL denotes an empty list) + * @param loc_advance the location of the pointer to advance + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func the function to compare the elements + * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the + * right list, positive if the left list is larger than the right list, zero if both lists are equal. + */ +cx_attr_nonnull_arg(5) +int cx_linked_list_compare( + const void *begin_left, + const void *begin_right, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func +); + +/** + * Reverses the order of the nodes in a linked list. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + */ +cx_attr_nonnull_arg(1) +void cx_linked_list_reverse( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_LINKED_LIST_H diff --git a/ucx/cx/list.h b/ucx/cx/list.h new file mode 100644 index 0000000..8f6c539 --- /dev/null +++ b/ucx/cx/list.h @@ -0,0 +1,905 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file list.h + * @brief Interface for list implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_LIST_H +#define UCX_LIST_H + +#include "common.h" +#include "collection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * List class type. + */ +typedef struct cx_list_class_s cx_list_class; + +/** + * Structure for holding the base data of a list. + */ +struct cx_list_s { + /** + * Common members for collections. + */ + CX_COLLECTION_BASE; + /** + * The list class definition. + */ + const cx_list_class *cl; + /** + * The actual implementation in case the list class is delegating. + */ + const cx_list_class *climpl; +}; + +/** + * The class definition for arbitrary lists. + */ +struct cx_list_class_s { + /** + * Destructor function. + * + * Implementations SHALL invoke the content destructor functions if provided + * and SHALL deallocate the entire list memory. + */ + void (*deallocate)(struct cx_list_s *list); + + /** + * Member function for inserting a single element. + * Implementors SHOULD see to performant implementations for corner cases. + */ + int (*insert_element)( + struct cx_list_s *list, + size_t index, + const void *data + ); + + /** + * Member function for inserting multiple elements. + * Implementors SHOULD see to performant implementations for corner cases. + * + * @see cx_list_default_insert_array() + */ + size_t (*insert_array)( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n + ); + + /** + * Member function for inserting sorted elements into a sorted list. + * + * @see cx_list_default_insert_sorted() + */ + size_t (*insert_sorted)( + struct cx_list_s *list, + const void *sorted_data, + size_t n + ); + + /** + * Member function for inserting an element relative to an iterator position. + */ + int (*insert_iter)( + struct cx_iterator_s *iter, + const void *elem, + int prepend + ); + + /** + * Member function for removing elements. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return the actual number of elements removed, which + * might be lower than @p num when going out of bounds. + */ + size_t (*remove)( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf + ); + + /** + * Member function for removing all elements. + */ + void (*clear)(struct cx_list_s *list); + + /** + * Member function for swapping two elements. + * + * @see cx_list_default_swap() + */ + int (*swap)( + struct cx_list_s *list, + size_t i, + size_t j + ); + + /** + * Member function for element lookup. + */ + void *(*at)( + const struct cx_list_s *list, + size_t index + ); + + /** + * Member function for finding and optionally removing an element. + */ + ssize_t (*find_remove)( + struct cx_list_s *list, + const void *elem, + bool remove + ); + + /** + * Member function for sorting the list. + * + * @see cx_list_default_sort() + */ + void (*sort)(struct cx_list_s *list); + + /** + * Optional member function for comparing this list + * to another list of the same type. + * If set to @c NULL, comparison won't be optimized. + */ + cx_attr_nonnull + int (*compare)( + const struct cx_list_s *list, + const struct cx_list_s *other + ); + + /** + * Member function for reversing the order of the items. + */ + void (*reverse)(struct cx_list_s *list); + + /** + * Member function for returning an iterator pointing to the specified index. + */ + struct cx_iterator_s (*iterator)( + const struct cx_list_s *list, + size_t index, + bool backward + ); +}; + +/** + * Default implementation of an array insert. + * + * This function uses the element insert function for each element of the array. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list + * @param index the index where to insert the data + * @param data a pointer to the array of data to insert + * @param n the number of elements to insert + * @return the number of elements actually inserted + */ +cx_attr_nonnull +size_t cx_list_default_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +); + +/** + * Default implementation of a sorted insert. + * + * This function uses the array insert function to insert consecutive groups + * of sorted data. + * + * The source data @em must already be sorted wrt. the list's compare function. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list + * @param sorted_data a pointer to the array of pre-sorted data to insert + * @param n the number of elements to insert + * @return the number of elements actually inserted + */ +cx_attr_nonnull +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +); + +/** + * Default unoptimized sort implementation. + * + * This function will copy all data to an array, sort the array with standard + * qsort, and then copy the data back to the list memory. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list that shall be sorted + */ +cx_attr_nonnull +void cx_list_default_sort(struct cx_list_s *list); + +/** + * Default unoptimized swap implementation. + * + * Use this in your own list class if you do not want to implement an optimized + * version for your list. + * + * @param list the list in which to swap + * @param i index of one element + * @param j index of the other element + * @retval zero success + * @retval non-zero when indices are out of bounds or memory + * allocation for the temporary buffer fails + */ +cx_attr_nonnull +int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); + +/** + * Common type for all list implementations. + */ +typedef struct cx_list_s CxList; + +/** + * Advises the list to store copies of the objects (default mode of operation). + * + * Retrieving objects from this list will yield pointers to the copies stored + * within this list. + * + * @param list the list + * @see cxListStorePointers() + */ +cx_attr_nonnull +void cxListStoreObjects(CxList *list); + +/** + * Advises the list to only store pointers to the objects. + * + * Retrieving objects from this list will yield the original pointers stored. + * + * @note This function forcibly sets the element size to the size of a pointer. + * Invoking this function on a non-empty list that already stores copies of + * objects is undefined. + * + * @param list the list + * @see cxListStoreObjects() + */ +cx_attr_nonnull +void cxListStorePointers(CxList *list); + +/** + * Returns true, if this list is storing pointers instead of the actual data. + * + * @param list + * @return true, if this list is storing pointers + * @see cxListStorePointers() + */ +cx_attr_nonnull +static inline bool cxListIsStoringPointers(const CxList *list) { + return list->collection.store_pointer; +} + +/** + * Returns the number of elements currently stored in the list. + * + * @param list the list + * @return the number of currently stored elements + */ +cx_attr_nonnull +static inline size_t cxListSize(const CxList *list) { + return list->collection.size; +} + +/** + * Adds an item to the end of the list. + * + * @param list the list + * @param elem a pointer to the element to add + * @retval zero success + * @retval non-zero memory allocation failure + * @see cxListAddArray() + */ +cx_attr_nonnull +static inline int cxListAdd( + CxList *list, + const void *elem +) { + return list->cl->insert_element(list, list->collection.size, elem); +} + +/** + * Adds multiple items to the end of the list. + * + * This method is more efficient than invoking cxListAdd() multiple times. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * If this list is storing pointers instead of objects @p array is expected to + * be an array of pointers. + * + * @param list the list + * @param array a pointer to the elements to add + * @param n the number of elements to add + * @return the number of added elements + */ +cx_attr_nonnull +static inline size_t cxListAddArray( + CxList *list, + const void *array, + size_t n +) { + return list->cl->insert_array(list, list->collection.size, array, n); +} + +/** + * Inserts an item at the specified index. + * + * If @p index equals the list @c size, this is effectively cxListAdd(). + * + * @param list the list + * @param index the index the element shall have + * @param elem a pointer to the element to add + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxListInsertAfter() + * @see cxListInsertBefore() + */ +cx_attr_nonnull +static inline int cxListInsert( + CxList *list, + size_t index, + const void *elem +) { + return list->cl->insert_element(list, index, elem); +} + +/** + * Inserts an item into a sorted list. + * + * @param list the list + * @param elem a pointer to the element to add + * @retval zero success + * @retval non-zero memory allocation failure + */ +cx_attr_nonnull +static inline int cxListInsertSorted( + CxList *list, + const void *elem +) { + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_sorted(list, data, 1) == 0; +} + +/** + * Inserts multiple items to the list at the specified index. + * If @p index equals the list size, this is effectively cxListAddArray(). + * + * This method is usually more efficient than invoking cxListInsert() + * multiple times. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * If this list is storing pointers instead of objects @p array is expected to + * be an array of pointers. + * + * @param list the list + * @param index the index where to add the elements + * @param array a pointer to the elements to add + * @param n the number of elements to add + * @return the number of added elements + */ +cx_attr_nonnull +static inline size_t cxListInsertArray( + CxList *list, + size_t index, + const void *array, + size_t n +) { + return list->cl->insert_array(list, index, array, n); +} + +/** + * Inserts a sorted array into a sorted list. + * + * This method is usually more efficient than inserting each element separately, + * because consecutive chunks of sorted data are inserted in one pass. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * If this list is storing pointers instead of objects @p array is expected to + * be an array of pointers. + * + * @param list the list + * @param array a pointer to the elements to add + * @param n the number of elements to add + * @return the number of added elements + */ +cx_attr_nonnull +static inline size_t cxListInsertSortedArray( + CxList *list, + const void *array, + size_t n +) { + return list->cl->insert_sorted(list, array, n); +} + +/** + * Inserts an element after the current location of the specified iterator. + * + * The used iterator remains operational, but all other active iterators should + * be considered invalidated. + * + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. + * + * @param iter an iterator + * @param elem the element to insert + * @retval zero success + * @retval non-zero memory allocation failure + * @see cxListInsert() + * @see cxListInsertBefore() + */ +cx_attr_nonnull +static inline int cxListInsertAfter( + CxIterator *iter, + const void *elem +) { + return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 0); +} + +/** + * Inserts an element before the current location of the specified iterator. + * + * The used iterator remains operational, but all other active iterators should + * be considered invalidated. + * + * If @p iter is not a list iterator, the behavior is undefined. + * If @p iter is a past-the-end iterator, the new element gets appended to the list. + * + * @param iter an iterator + * @param elem the element to insert + * @retval zero success + * @retval non-zero memory allocation failure + * @see cxListInsert() + * @see cxListInsertAfter() + */ +cx_attr_nonnull +static inline int cxListInsertBefore( + CxIterator *iter, + const void *elem +) { + return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 1); +} + +/** + * Removes the element at the specified index. + * + * If an element destructor function is specified, it is called before + * removing the element. + * + * @param list the list + * @param index the index of the element + * @retval zero success + * @retval non-zero index out of bounds + */ +cx_attr_nonnull +static inline int cxListRemove( + CxList *list, + size_t index +) { + return list->cl->remove(list, index, 1, NULL) == 0; +} + +/** + * Removes and returns the element at the specified index. + * + * No destructor is called and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * + * @param list the list + * @param index the index of the element + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero index out of bounds + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxListRemoveAndGet( + CxList *list, + size_t index, + void *targetbuf +) { + return list->cl->remove(list, index, 1, targetbuf) == 0; +} + +/** + * Removes multiple element starting at the specified index. + * + * If an element destructor function is specified, it is called for each + * element. It is guaranteed that the destructor is called before removing + * the element, however, due to possible optimizations it is neither guaranteed + * that the destructors are invoked for all elements before starting to remove + * them, nor that the element is removed immediately after the destructor call + * before proceeding to the next element. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @return the actual number of removed elements + */ +cx_attr_nonnull +static inline size_t cxListRemoveArray( + CxList *list, + size_t index, + size_t num +) { + return list->cl->remove(list, index, num, NULL); +} + +/** + * Removes and returns multiple element starting at the specified index. + * + * No destructor is called and instead the elements are copied to the + * @p targetbuf which MUST be large enough to hold all removed elements. + * + * @param list the list + * @param index the index of the element + * @param num the number of elements to remove + * @param targetbuf a buffer where to copy the elements + * @return the actual number of removed elements + */ +cx_attr_nonnull +cx_attr_access_w(4) +static inline size_t cxListRemoveArrayAndGet( + CxList *list, + size_t index, + size_t num, + void *targetbuf +) { + return list->cl->remove(list, index, num, targetbuf); +} + +/** + * Removes all elements from this list. + * + * If element destructor functions are specified, they are called for each + * element before removing them. + * + * @param list the list + */ +cx_attr_nonnull +static inline void cxListClear(CxList *list) { + list->cl->clear(list); +} + +/** + * Swaps two items in the list. + * + * Implementations should only allocate temporary memory for the swap, if + * it is necessary. + * + * @param list the list + * @param i the index of the first element + * @param j the index of the second element + * @retval zero success + * @retval non-zero one of the indices is out of bounds + * or the swap needed extra memory but allocation failed + */ +cx_attr_nonnull +static inline int cxListSwap( + CxList *list, + size_t i, + size_t j +) { + return list->cl->swap(list, i, j); +} + +/** + * Returns a pointer to the element at the specified index. + * + * @param list the list + * @param index the index of the element + * @return a pointer to the element or @c NULL if the index is out of bounds + */ +cx_attr_nonnull +static inline void *cxListAt( + const CxList *list, + size_t index +) { + return list->cl->at(list, index); +} + +/** + * Returns an iterator pointing to the item at the specified index. + * + * The returned iterator is position-aware. + * + * If the index is out of range, a past-the-end iterator will be returned. + * + * @param list the list + * @param index the index where the iterator shall point at + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListIteratorAt( + const CxList *list, + size_t index +) { + return list->cl->iterator(list, index, false); +} + +/** + * Returns a backwards iterator pointing to the item at the specified index. + * + * The returned iterator is position-aware. + * + * If the index is out of range, a past-the-end iterator will be returned. + * + * @param list the list + * @param index the index where the iterator shall point at + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListBackwardsIteratorAt( + const CxList *list, + size_t index +) { + return list->cl->iterator(list, index, true); +} + +/** + * Returns a mutating iterator pointing to the item at the specified index. + * + * The returned iterator is position-aware. + * + * If the index is out of range, a past-the-end iterator will be returned. + * + * @param list the list + * @param index the index where the iterator shall point at + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxListMutIteratorAt( + CxList *list, + size_t index +); + +/** + * Returns a mutating backwards iterator pointing to the item at the + * specified index. + * + * The returned iterator is position-aware. + * + * If the index is out of range, a past-the-end iterator will be returned. + * + * @param list the list + * @param index the index where the iterator shall point at + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxListMutBackwardsIteratorAt( + CxList *list, + size_t index +); + +/** + * Returns an iterator pointing to the first item of the list. + * + * The returned iterator is position-aware. + * + * If the list is empty, a past-the-end iterator will be returned. + * + * @param list the list + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListIterator(const CxList *list) { + return list->cl->iterator(list, 0, false); +} + +/** + * Returns a mutating iterator pointing to the first item of the list. + * + * The returned iterator is position-aware. + * + * If the list is empty, a past-the-end iterator will be returned. + * + * @param list the list + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListMutIterator(CxList *list) { + return cxListMutIteratorAt(list, 0); +} + + +/** + * Returns a backwards iterator pointing to the last item of the list. + * + * The returned iterator is position-aware. + * + * If the list is empty, a past-the-end iterator will be returned. + * + * @param list the list + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListBackwardsIterator(const CxList *list) { + return list->cl->iterator(list, list->collection.size - 1, true); +} + +/** + * Returns a mutating backwards iterator pointing to the last item of the list. + * + * The returned iterator is position-aware. + * + * If the list is empty, a past-the-end iterator will be returned. + * + * @param list the list + * @return a new iterator + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxListMutBackwardsIterator(CxList *list) { + return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); +} + +/** + * Returns the index of the first element that equals @p elem. + * + * Determining equality is performed by the list's comparator function. + * + * @param list the list + * @param elem the element to find + * @return the index of the element or a negative + * value when the element is not found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline ssize_t cxListFind( + const CxList *list, + const void *elem +) { + return list->cl->find_remove((CxList*)list, elem, false); +} + +/** + * Removes and returns the index of the first element that equals @p elem. + * + * Determining equality is performed by the list's comparator function. + * + * @param list the list + * @param elem the element to find and remove + * @return the index of the now removed element or a negative + * value when the element is not found or could not be removed + */ +cx_attr_nonnull +static inline ssize_t cxListFindRemove( + CxList *list, + const void *elem +) { + return list->cl->find_remove(list, elem, true); +} + +/** + * Sorts the list. + * + * @remark The underlying sort algorithm is implementation defined. + * + * @param list the list + */ +cx_attr_nonnull +static inline void cxListSort(CxList *list) { + list->cl->sort(list); +} + +/** + * Reverses the order of the items. + * + * @param list the list + */ +cx_attr_nonnull +static inline void cxListReverse(CxList *list) { + list->cl->reverse(list); +} + +/** + * Compares a list to another list of the same type. + * + * First, the list sizes are compared. + * If they match, the lists are compared element-wise. + * + * @param list the list + * @param other the list to compare to + * @retval zero both lists are equal element wise + * @retval negative the first list is smaller + * or the first non-equal element in the first list is smaller + * @retval positive the first list is larger + * or the first non-equal element in the first list is larger + */ +cx_attr_nonnull +cx_attr_nodiscard +int cxListCompare( + const CxList *list, + const CxList *other +); + +/** + * Deallocates the memory of the specified list structure. + * + * Also calls the content destructor functions for each element, if specified. + * + * @param list the list which shall be freed + */ +void cxListFree(CxList *list); + +/** + * A shared instance of an empty list. + * + * Writing to that list is not allowed. + * + * You can use this is a placeholder for initializing CxList pointers + * for which you do not want to reserve memory right from the beginning. + */ +extern CxList *const cxEmptyList; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_LIST_H diff --git a/ucx/cx/map.h b/ucx/cx/map.h new file mode 100644 index 0000000..22a01e9 --- /dev/null +++ b/ucx/cx/map.h @@ -0,0 +1,778 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file map.h + * @brief Interface for map implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_MAP_H +#define UCX_MAP_H + +#include "common.h" +#include "collection.h" +#include "string.h" +#include "hash_key.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Type for the UCX map. */ +typedef struct cx_map_s CxMap; + +/** Type for a map entry. */ +typedef struct cx_map_entry_s CxMapEntry; + +/** Type for map class definitions. */ +typedef struct cx_map_class_s cx_map_class; + +/** Structure for the UCX map. */ +struct cx_map_s { + /** + * Base attributes. + */ + CX_COLLECTION_BASE; + /** The map class definition. */ + cx_map_class *cl; +}; + +/** + * The type of iterator for a map. + */ +enum cx_map_iterator_type { + /** + * Iterates over key/value pairs. + */ + CX_MAP_ITERATOR_PAIRS, + /** + * Iterates over keys only. + */ + CX_MAP_ITERATOR_KEYS, + /** + * Iterates over values only. + */ + CX_MAP_ITERATOR_VALUES +}; + +/** + * The class definition for arbitrary maps. + */ +struct cx_map_class_s { + /** + * Deallocates the entire memory. + */ + void (*deallocate)(struct cx_map_s *map); + + /** + * Removes all elements. + */ + void (*clear)(struct cx_map_s *map); + + /** + * Add or overwrite an element. + */ + int (*put)( + CxMap *map, + CxHashKey key, + void *value + ); + + /** + * Returns an element. + */ + void *(*get)( + const CxMap *map, + CxHashKey key + ); + + /** + * Removes an element. + * + * Implementations SHALL check if @p targetbuf is set and copy the elements + * to the buffer without invoking any destructor. + * When @p targetbuf is not set, the destructors SHALL be invoked. + * + * The function SHALL return zero when the @p key was found and + * non-zero, otherwise. + */ + int (*remove)( + CxMap *map, + CxHashKey key, + void *targetbuf + ); + + /** + * Creates an iterator for this map. + */ + CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); +}; + +/** + * A map entry. + */ +struct cx_map_entry_s { + /** + * A pointer to the key. + */ + const CxHashKey *key; + /** + * A pointer to the value. + */ + void *value; +}; + +/** + * A shared instance of an empty map. + * + * Writing to that map is not allowed. + * + * You can use this is a placeholder for initializing CxMap pointers + * for which you do not want to reserve memory right from the beginning. + */ +extern CxMap *const cxEmptyMap; + +/** + * Advises the map to store copies of the objects (default mode of operation). + * + * Retrieving objects from this map will yield pointers to the copies stored + * within this list. + * + * @param map the map + * @see cxMapStorePointers() + */ +cx_attr_nonnull +static inline void cxMapStoreObjects(CxMap *map) { + map->collection.store_pointer = false; +} + +/** + * Advises the map to only store pointers to the objects. + * + * Retrieving objects from this list will yield the original pointers stored. + * + * @note This function forcibly sets the element size to the size of a pointer. + * Invoking this function on a non-empty map that already stores copies of + * objects is undefined. + * + * @param map the map + * @see cxMapStoreObjects() + */ +cx_attr_nonnull +static inline void cxMapStorePointers(CxMap *map) { + map->collection.store_pointer = true; + map->collection.elem_size = sizeof(void *); +} + +/** + * Returns true, if this map is storing pointers instead of the actual data. + * + * @param map + * @return true, if this map is storing pointers + * @see cxMapStorePointers() + */ +cx_attr_nonnull +static inline bool cxMapIsStoringPointers(const CxMap *map) { + return map->collection.store_pointer; +} + +/** + * Deallocates the memory of the specified map. + * + * Also calls the content destructor functions for each element, if specified. + * + * @param map the map to be freed + */ +void cxMapFree(CxMap *map); + + +/** + * Clears a map by removing all elements. + * + * Also calls the content destructor functions for each element, if specified. + * + * @param map the map to be cleared + */ +cx_attr_nonnull +static inline void cxMapClear(CxMap *map) { + map->cl->clear(map); +} + +/** + * Returns the number of elements in this map. + * + * @param map the map + * @return the number of stored elements + */ +cx_attr_nonnull +static inline size_t cxMapSize(const CxMap *map) { + return map->collection.size; +} + +/** + * Creates a value iterator for a map. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored values + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxMapIteratorValues(const CxMap *map) { + return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); +} + +/** + * Creates a key iterator for a map. + * + * The elements of the iterator are keys of type CxHashKey. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored keys + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxMapIteratorKeys(const CxMap *map) { + return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); +} + +/** + * Creates an iterator for a map. + * + * The elements of the iterator are key/value pairs of type CxMapEntry. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored entries + * @see cxMapIteratorKeys() + * @see cxMapIteratorValues() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxIterator cxMapIterator(const CxMap *map) { + return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); +} + + +/** + * Creates a mutating iterator over the values of a map. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored values + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxMapMutIteratorValues(CxMap *map); + +/** + * Creates a mutating iterator over the keys of a map. + * + * The elements of the iterator are keys of type CxHashKey. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored keys + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxMapMutIteratorKeys(CxMap *map); + +/** + * Creates a mutating iterator for a map. + * + * The elements of the iterator are key/value pairs of type CxMapEntry. + * + * @note An iterator iterates over all elements successively. Therefore, the order + * highly depends on the map implementation and may change arbitrarily when the contents change. + * + * @param map the map to create the iterator for + * @return an iterator for the currently stored entries + * @see cxMapMutIteratorKeys() + * @see cxMapMutIteratorValues() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxIterator cxMapMutIterator(CxMap *map); + +#ifdef __cplusplus +} // end the extern "C" block here, because we want to start overloading +cx_attr_nonnull +static inline int cxMapPut( + CxMap *map, + CxHashKey const &key, + void *value +) { + return map->cl->put(map, key, value); +} + +cx_attr_nonnull +static inline int cxMapPut( + CxMap *map, + cxstring const &key, + void *value +) { + return map->cl->put(map, cx_hash_key_cxstr(key), value); +} + +cx_attr_nonnull +static inline int cxMapPut( + CxMap *map, + cxmutstr const &key, + void *value +) { + return map->cl->put(map, cx_hash_key_cxstr(key), value); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxMapPut( + CxMap *map, + const char *key, + void *value +) { + return map->cl->put(map, cx_hash_key_str(key), value); +} + +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxMapGet( + const CxMap *map, + CxHashKey const &key +) { + return map->cl->get(map, key); +} + +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxMapGet( + const CxMap *map, + cxstring const &key +) { + return map->cl->get(map, cx_hash_key_cxstr(key)); +} + +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxMapGet( + const CxMap *map, + cxmutstr const &key +) { + return map->cl->get(map, cx_hash_key_cxstr(key)); +} + +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) +static inline void *cxMapGet( + const CxMap *map, + const char *key +) { + return map->cl->get(map, cx_hash_key_str(key)); +} + +cx_attr_nonnull +static inline int cxMapRemove( + CxMap *map, + CxHashKey const &key +) { + return map->cl->remove(map, key, nullptr); +} + +cx_attr_nonnull +static inline int cxMapRemove( + CxMap *map, + cxstring const &key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); +} + +cx_attr_nonnull +static inline int cxMapRemove( + CxMap *map, + cxmutstr const &key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxMapRemove( + CxMap *map, + const char *key +) { + return map->cl->remove(map, cx_hash_key_str(key), nullptr); +} + +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( + CxMap *map, + CxHashKey key, + void *targetbuf +) { + return map->cl->remove(map, key, targetbuf); +} + +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( + CxMap *map, + cxstring key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} + +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cxMapRemoveAndGet( + CxMap *map, + cxmutstr key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} + +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cxMapRemoveAndGet( + CxMap *map, + const char *key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_str(key), targetbuf); +} + +#else // __cplusplus + +/** + * @copydoc cxMapPut() + */ +cx_attr_nonnull +static inline int cx_map_put( + CxMap *map, + CxHashKey key, + void *value +) { + return map->cl->put(map, key, value); +} + +/** + * @copydoc cxMapPut() + */ +cx_attr_nonnull +static inline int cx_map_put_cxstr( + CxMap *map, + cxstring key, + void *value +) { + return map->cl->put(map, cx_hash_key_cxstr(key), value); +} + +/** + * @copydoc cxMapPut() + */ +cx_attr_nonnull +static inline int cx_map_put_mustr( + CxMap *map, + cxmutstr key, + void *value +) { + return map->cl->put(map, cx_hash_key_cxstr(key), value); +} + +/** + * @copydoc cxMapPut() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_map_put_str( + CxMap *map, + const char *key, + void *value +) { + return map->cl->put(map, cx_hash_key_str(key), value); +} + +/** + * Puts a key/value-pair into the map. + * + * A possible existing value will be overwritten. + * + * If this map is storing pointers, the @p value pointer is written + * to the map. Otherwise, the memory is copied from @p value with + * memcpy(). + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero value on memory allocation failure + */ +#define cxMapPut(map, key, value) _Generic((key), \ + CxHashKey: cx_map_put, \ + cxstring: cx_map_put_cxstr, \ + cxmutstr: cx_map_put_mustr, \ + char*: cx_map_put_str, \ + const char*: cx_map_put_str) \ + (map, key, value) + +/** + * @copydoc cxMapGet() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cx_map_get( + const CxMap *map, + CxHashKey key +) { + return map->cl->get(map, key); +} + +/** + * @copydoc cxMapGet() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cx_map_get_cxstr( + const CxMap *map, + cxstring key +) { + return map->cl->get(map, cx_hash_key_cxstr(key)); +} + +/** + * @copydoc cxMapGet() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cx_map_get_mustr( + const CxMap *map, + cxmutstr key +) { + return map->cl->get(map, cx_hash_key_cxstr(key)); +} + +/** + * @copydoc cxMapGet() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) +static inline void *cx_map_get_str( + const CxMap *map, + const char *key +) { + return map->cl->get(map, cx_hash_key_str(key)); +} + +/** + * Retrieves a value by using a key. + * + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @return (@c void*) the value + */ +#define cxMapGet(map, key) _Generic((key), \ + CxHashKey: cx_map_get, \ + cxstring: cx_map_get_cxstr, \ + cxmutstr: cx_map_get_mustr, \ + char*: cx_map_get_str, \ + const char*: cx_map_get_str) \ + (map, key) + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove( + CxMap *map, + CxHashKey key +) { + return map->cl->remove(map, key, NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove_cxstr( + CxMap *map, + cxstring key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove_mustr( + CxMap *map, + cxmutstr key +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_map_remove_str( + CxMap *map, + const char *key +) { + return map->cl->remove(map, cx_hash_key_str(key), NULL); +} + +/** + * Removes a key/value-pair from the map by using the key. + * + * Always invokes the destructors functions, if any, on the removed element. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapRemoveAndGet() + */ +#define cxMapRemove(map, key) _Generic((key), \ + CxHashKey: cx_map_remove, \ + cxstring: cx_map_remove_cxstr, \ + cxmutstr: cx_map_remove_mustr, \ + char*: cx_map_remove_str, \ + const char*: cx_map_remove_str) \ + (map, key) + +/** + * @copydoc cxMapRemoveAndGet() + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get( + CxMap *map, + CxHashKey key, + void *targetbuf +) { + return map->cl->remove(map, key, targetbuf); +} + +/** + * @copydoc cxMapRemoveAndGet() + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_cxstr( + CxMap *map, + cxstring key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} + +/** + * @copydoc cxMapRemoveAndGet() + */ +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_mustr( + CxMap *map, + cxmutstr key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); +} + +/** + * @copydoc cxMapRemoveAndGet() + */ +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cx_map_remove_and_get_str( + CxMap *map, + const char *key, + void *targetbuf +) { + return map->cl->remove(map, cx_hash_key_str(key), targetbuf); +} + +/** + * Removes a key/value-pair from the map by using the key. + * + * This function will copy the contents of the removed element + * to the target buffer must be guaranteed to be large enough + * to hold the element (the map's element size). + * The destructor functions, if any, will @em not be called. + * + * If this map is storing pointers, the element is the pointer itself + * and not the object it points to. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param targetbuf (@c void*) the buffer where the element shall be copied to + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapStorePointers() + * @see cxMapRemove() + */ +#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ + CxHashKey: cx_map_remove_and_get, \ + cxstring: cx_map_remove_and_get_cxstr, \ + cxmutstr: cx_map_remove_and_get_mustr, \ + char*: cx_map_remove_and_get_str, \ + const char*: cx_map_remove_and_get_str) \ + (map, key, targetbuf) + +#endif // __cplusplus + +#endif // UCX_MAP_H diff --git a/ucx/cx/mempool.h b/ucx/cx/mempool.h new file mode 100644 index 0000000..fc4b66e --- /dev/null +++ b/ucx/cx/mempool.h @@ -0,0 +1,158 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file mempool.h + * @brief Interface for memory pool implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_MEMPOOL_H +#define UCX_MEMPOOL_H + +#include "common.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Internal structure for pooled memory. */ +struct cx_mempool_memory_s; + +/** + * The basic structure of a memory pool. + * Should be the first member of an actual memory pool implementation. + */ +struct cx_mempool_s { + /** The provided allocator. */ + const CxAllocator *allocator; + + /** + * A destructor that shall be automatically registered for newly allocated memory. + * This destructor MUST NOT free the memory. + */ + cx_destructor_func auto_destr; + + /** Array of pooled memory. */ + struct cx_mempool_memory_s **data; + + /** Number of pooled memory items. */ + size_t size; + + /** Memory pool capacity. */ + size_t capacity; +}; + +/** + * Common type for all memory pool implementations. + */ +typedef struct cx_mempool_s CxMempool; + +/** + * Deallocates a memory pool and frees the managed memory. + * + * @param pool the memory pool to free + */ +void cxMempoolFree(CxMempool *pool); + +/** + * Creates an array-based memory pool with a shared destructor function. + * + * This destructor MUST NOT free the memory. + * + * @param capacity the initial capacity of the pool + * @param destr optional destructor function to use for allocated memory + * @return the created memory pool or @c NULL if allocation failed + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMempoolFree, 1) +CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); + +/** + * Creates a basic array-based memory pool. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL) + +/** + * Sets the destructor function for a specific allocated memory object. + * + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + * @param fnc the destructor function + */ +cx_attr_nonnull +void cxMempoolSetDestructor( + void *memory, + cx_destructor_func fnc +); + +/** + * Removes the destructor function for a specific allocated memory object. + * + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +void cxMempoolRemoveDestructor(void *memory); + +/** + * Registers foreign memory with this pool. + * + * The destructor, in contrast to memory allocated by the pool, MUST free the memory. + * + * A small portion of memory will be allocated to register the information in the pool. + * If that allocation fails, this function will return non-zero. + * + * @param pool the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) + * @param destr the destructor function + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +int cxMempoolRegister( + CxMempool *pool, + void *memory, + cx_destructor_func destr +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_MEMPOOL_H diff --git a/ucx/cx/printf.h b/ucx/cx/printf.h new file mode 100644 index 0000000..722bb64 --- /dev/null +++ b/ucx/cx/printf.h @@ -0,0 +1,395 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file printf.h + * @brief Wrapper for write functions with a printf-like interface. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_PRINTF_H +#define UCX_PRINTF_H + +#include "common.h" +#include "string.h" +#include + +/** + * Attribute for printf-like functions. + * @param fmt_idx index of the format string parameter + * @param arg_idx index of the first formatting argument + */ +#define cx_attr_printf(fmt_idx, arg_idx) \ + __attribute__((__format__(printf, fmt_idx, arg_idx))) + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The maximum string length that fits into stack memory. + */ +extern const unsigned cx_printf_sbo_size; + +/** + * A @c fprintf like function which writes the output to a stream by + * using a write_func. + * + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ... additional arguments + * @return the total number of bytes written or an error code from stdlib printf implementation + */ +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_printf(3, 4) +cx_attr_cstr_arg(3) +int cx_fprintf( + void *stream, + cx_write_func wfc, + const char *fmt, + ... +); + +/** + * A @c vfprintf like function which writes the output to a stream by + * using a write_func. + * + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ap argument list + * @return the total number of bytes written or an error code from stdlib printf implementation + * @see cx_fprintf() + */ +cx_attr_nonnull +cx_attr_cstr_arg(3) +int cx_vfprintf( + void *stream, + cx_write_func wfc, + const char *fmt, + va_list ap +); + +/** + * A @c asprintf like function which allocates space for a string + * the result is written to. + * + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. + * + * @param allocator the CxAllocator used for allocating the string + * @param fmt format string + * @param ... additional arguments + * @return the formatted string + * @see cx_strfree_a() + */ +cx_attr_nonnull_arg(1, 2) +cx_attr_printf(2, 3) +cx_attr_cstr_arg(2) +cxmutstr cx_asprintf_a( + const CxAllocator *allocator, + const char *fmt, + ... +); + +/** + * A @c asprintf like function which allocates space for a string + * the result is written to. + * + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. + * + * @param fmt (@c char*) format string + * @param ... additional arguments + * @return (@c cxmutstr) the formatted string + * @see cx_strfree() + */ +#define cx_asprintf(fmt, ...) \ + cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) + +/** +* A @c vasprintf like function which allocates space for a string + * the result is written to. + * + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was an error, in which case the string's pointer + * will be @c NULL. + * + * @param allocator the CxAllocator used for allocating the string + * @param fmt format string + * @param ap argument list + * @return the formatted string + * @see cx_asprintf_a() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +cxmutstr cx_vasprintf_a( + const CxAllocator *allocator, + const char *fmt, + va_list ap +); + +/** +* A @c vasprintf like function which allocates space for a string + * the result is written to. + * + * @note The resulting string is guaranteed to be zero-terminated, + * unless there was in error, in which case the string's pointer + * will be @c NULL. + * + * @param fmt (@c char*) format string + * @param ap (@c va_list) argument list + * @return (@c cxmutstr) the formatted string + * @see cx_asprintf() + */ +#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap) + +/** + * A @c printf like function which writes the output to a CxBuffer. + * + * @param buffer (@c CxBuffer*) a pointer to the buffer the data is written to + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the total number of bytes written or an error code from stdlib printf implementation + * @see cx_fprintf() + * @see cxBufferWrite() + */ +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ + (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) + + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @attention The original buffer MUST have been allocated with the same allocator! + * + * @param alloc the allocator to use + * @param str a pointer to the string buffer + * @param len a pointer to the length of the buffer + * @param fmt the format string + * @param ... additional arguments + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_printf(4, 5) +cx_attr_cstr_arg(4) +int cx_sprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + ... +); + + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @param str (@c char**) a pointer to the string buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) + +/** + * An @c sprintf like function which reallocates the string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @attention The original buffer MUST have been allocated with the same allocator! + * + * @param alloc the allocator to use + * @param str a pointer to the string buffer + * @param len a pointer to the length of the buffer + * @param fmt the format string + * @param ap argument list + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull +cx_attr_cstr_arg(4) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +int cx_vsprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + va_list ap +); + + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string + * @param ... additional arguments + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string, if successful, is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param alloc the allocator to use + * @param buf a pointer to the buffer + * @param len a pointer to the length of the buffer + * @param str a pointer where the location of the result shall be stored + * @param fmt the format string + * @param ... additional arguments + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull_arg(1, 2, 4, 5) +cx_attr_printf(5, 6) +cx_attr_cstr_arg(5) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +cx_attr_access_rw(4) +int cx_sprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + ... +); + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param buf (@c char*) a pointer to the buffer + * @param len (@c size_t*) a pointer to the length of the buffer + * @param str (@c char**) a pointer where the location of the result shall be stored + * @param fmt (@c char*) the format string + * @param ap (@c va_list) argument list + * @return (@c int) the length of produced string or an error code from stdlib printf implementation + */ +#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) + +/** + * An @c sprintf like function which allocates a new string when the buffer is not large enough. + * + * The size of the buffer will be updated in @p len when necessary. + * + * The location of the resulting string will @em always be stored to @p str. When the buffer + * was sufficiently large, @p buf itself will be stored to the location of @p str. + * + * @note The resulting string is guaranteed to be zero-terminated. + * + * @remark When a new string needed to be allocated, the contents of @p buf will be + * poisoned after the call, because this function tries to produce the string in @p buf, first. + * + * @param alloc the allocator to use + * @param buf a pointer to the buffer + * @param len a pointer to the length of the buffer + * @param str a pointer where the location of the result shall be stored + * @param fmt the format string + * @param ap argument list + * @return the length of produced string or an error code from stdlib printf implementation + */ +cx_attr_nonnull +cx_attr_cstr_arg(5) +int cx_vsprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //UCX_PRINTF_H diff --git a/ucx/cx/properties.h b/ucx/cx/properties.h new file mode 100644 index 0000000..128acee --- /dev/null +++ b/ucx/cx/properties.h @@ -0,0 +1,644 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * @brief Interface for parsing data from properties files. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "common.h" +#include "string.h" +#include "map.h" +#include "buffer.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Configures the expected characters for the properties parser. + */ +struct cx_properties_config_s { + /** + * The key/value delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The character, when appearing at the end of a line, continues that line. + * This is '\' by default. + */ + // char continuation; // TODO: line continuation in properties + + /** + * 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; +}; + +/** + * Typedef for the properties config. + */ +typedef struct cx_properties_config_s CxPropertiesConfig; + +/** + * Default properties configuration. + */ +extern const CxPropertiesConfig cx_properties_config_default; + +/** + * Status codes for the properties interface. + */ +enum cx_properties_status { + /** + * Everything is fine. + */ + CX_PROPERTIES_NO_ERROR, + /** + * The input buffer does not contain more data. + */ + CX_PROPERTIES_NO_DATA, + /** + * The input ends unexpectedly. + * + * This either happens when the last line does not terminate with a line + * break, or when the input ends with a parsed key but no value. + */ + CX_PROPERTIES_INCOMPLETE_DATA, + /** + * Not used as a status and never returned by any function. + * + * You can use this enumerator to check for all "good" status results + * by checking if the status is less than @c CX_PROPERTIES_OK. + * + * A "good" status means, that you can refill data and continue parsing. + */ + CX_PROPERTIES_OK, + /** + * Input buffer is @c NULL. + */ + CX_PROPERTIES_NULL_INPUT, + /** + * The line contains a delimiter, but no key. + */ + CX_PROPERTIES_INVALID_EMPTY_KEY, + /** + * The line contains data, but no delimiter. + */ + CX_PROPERTIES_INVALID_MISSING_DELIMITER, + /** + * More internal buffer was needed, but could not be allocated. + */ + CX_PROPERTIES_BUFFER_ALLOC_FAILED, + /** + * Initializing the properties source failed. + * + * @see cx_properties_read_init_func + */ + CX_PROPERTIES_READ_INIT_FAILED, + /** + * Reading from a properties source failed. + * + * @see cx_properties_read_func + */ + CX_PROPERTIES_READ_FAILED, + /** + * Sinking a k/v-pair failed. + * + * @see cx_properties_sink_func + */ + CX_PROPERTIES_SINK_FAILED, +}; + +/** + * Typedef for the properties status enum. + */ +typedef enum cx_properties_status CxPropertiesStatus; + +/** + * Interface for working with properties data. + */ +struct cx_properties_s { + /** + * The configuration. + */ + CxPropertiesConfig config; + + /** + * The text input buffer. + */ + CxBuffer input; + + /** + * Internal buffer. + */ + CxBuffer buffer; +}; + +/** + * Typedef for the properties interface. + */ +typedef struct cx_properties_s CxProperties; + + +/** + * Typedef for a properties sink. + */ +typedef struct cx_properties_sink_s CxPropertiesSink; + +/** + * A function that consumes a k/v-pair in a sink. + * + * The sink could be e.g. a map and the sink function would be calling + * a map function to store the k/v-pair. + * + * @param prop the properties interface that wants to sink a k/v-pair + * @param sink the sink + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero sinking the k/v-pair failed + */ +cx_attr_nonnull +typedef int(*cx_properties_sink_func)( + CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +); + +/** + * Defines a sink for k/v-pairs. + */ +struct cx_properties_sink_s { + /** + * The sink object. + */ + void *sink; + /** + * Optional custom data. + */ + void *data; + /** + * A function for consuming k/v-pairs into the sink. + */ + cx_properties_sink_func sink_func; +}; + + +/** + * Typedef for a properties source. + */ +typedef struct cx_properties_source_s CxPropertiesSource; + +/** + * A function that reads data from a source. + * + * When the source is depleted, implementations SHALL provide an empty + * string in the @p target and return zero. + * A non-zero return value is only permitted in case of an error. + * + * The meaning of the optional parameters is implementation-dependent. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @param target a string buffer where the read data shall be stored + * @retval zero success + * @retval non-zero reading the data failed + */ +cx_attr_nonnull +typedef int(*cx_properties_read_func)( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +); + +/** + * A function that may initialize additional memory for the source. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + * @retval zero initialization was successful + * @retval non-zero otherwise + */ +cx_attr_nonnull +typedef int(*cx_properties_read_init_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * A function that cleans memory initialized by the read_init_func. + * + * @param prop the properties interface that wants to read from the source + * @param src the source + */ +cx_attr_nonnull +typedef void(*cx_properties_read_clean_func)( + CxProperties *prop, + CxPropertiesSource *src +); + +/** + * Defines a properties source. + */ +struct cx_properties_source_s { + /** + * The source object. + * + * For example a file stream or a string. + */ + void *src; + /** + * Optional additional data pointer. + */ + void *data_ptr; + /** + * Optional size information. + */ + size_t data_size; + /** + * A function that reads data from the source. + */ + cx_properties_read_func read_func; + /** + * Optional function that may prepare the source for reading data. + */ + cx_properties_read_init_func read_init_func; + /** + * Optional function that cleans additional memory allocated by the + * read_init_func. + */ + cx_properties_read_clean_func read_clean_func; +}; + +/** + * Initialize a properties interface. + * + * @param prop the properties interface + * @param config the properties configuration + * @see cxPropertiesInitDefault() + */ +cx_attr_nonnull +void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); + +/** + * Destroys the properties interface. + * + * @note Even when you are certain that you did not use the interface in a + * way that caused a memory allocation, you should call this function anyway. + * Future versions of the library might add features that need additional memory + * and you really don't want to search the entire code where you might need + * add call to this function. + * + * @param prop the properties interface + */ +cx_attr_nonnull +void cxPropertiesDestroy(CxProperties *prop); + +/** + * Destroys and re-initializes the properties interface. + * + * You might want to use this, to reset the parser after + * encountering a syntax error. + * + * @param prop the properties interface + */ +cx_attr_nonnull +static inline void cxPropertiesReset(CxProperties *prop) { + CxPropertiesConfig config = prop->config; + cxPropertiesDestroy(prop); + cxPropertiesInit(prop, config); +} + +/** + * Initialize a properties parser with the default configuration. + * + * @param prop (@c CxProperties*) the properties interface + * @see cxPropertiesInit() + */ +#define cxPropertiesInitDefault(prop) \ + cxPropertiesInit(prop, cx_properties_config_default) + +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @remark The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param buf a pointer to the data + * @param len the length of the data + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +); + +#ifdef __cplusplus +} // extern "C" +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +static inline int cxPropertiesFill( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cxPropertiesFill( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} + +extern "C" { +#else // __cplusplus +/** + * Fills the input buffer with data. + * + * After calling this function, you can parse the data by calling + * cxPropertiesNext(). + * + * @attention The properties interface tries to avoid allocations. + * When you use this function and cxPropertiesNext() interleaving, + * no allocations are performed. However, you must not free the + * pointer to the data in that case. When you invoke the fill + * function more than once before calling cxPropertiesNext(), + * the additional data is appended - inevitably leading to + * an allocation of a new buffer and copying the previous contents. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + * @see cxPropertiesFilln() + */ +#define cxPropertiesFill(prop, str) _Generic((str), \ + cxstring: cx_properties_fill_cxstr, \ + cxmutstr: cx_properties_fill_mutstr, \ + char*: cx_properties_fill_str, \ + const char*: cx_properties_fill_str) \ + (prop, str) + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_cxstr( + CxProperties *prop, + cxstring str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +static inline int cx_properties_fill_mutstr( + CxProperties *prop, + cxmutstr str +) { + return cxPropertiesFilln(prop, str.ptr, str.length); +} + +/** + * @copydoc cxPropertiesFill() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_properties_fill_str( + CxProperties *prop, + const char *str +) { + return cxPropertiesFilln(prop, str, strlen(str)); +} +#endif + +/** + * Specifies stack memory that shall be used as internal buffer. + * + * @param prop the properties interface + * @param buf a pointer to stack memory + * @param capacity the capacity of the stack memory + */ +cx_attr_nonnull +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +); + +/** + * Retrieves the next key/value-pair. + * + * This function returns zero as long as there are key/value-pairs found. + * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned. + * + * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is + * returned, and you can add more data with #cxPropertiesFill(). + * + * @remark The incomplete line will be stored in an internal buffer, which is + * allocated on the heap, by default. If you want to avoid allocations, + * you can specify sufficient space with cxPropertiesUseStack() after + * initialization with cxPropertiesInit(). + * + * @attention The returned strings will point into a buffer that might not be + * available later. It is strongly recommended to copy the strings for further + * use. + * + * @param prop the properties interface + * @param key a pointer to the cxstring that shall contain the property name + * @param value a pointer to the cxstring that shall contain the property value + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer + * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete + * (fill more data and try again) + * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +cx_attr_nodiscard +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +); + +/** + * Creates a properties sink for an UCX map. + * + * The values stored in the map will be pointers to strings allocated + * by #cx_strdup_a(). + * The default stdlib allocator will be used, unless you specify a custom + * allocator in the optional @c data of the sink. + * + * @param map the map that shall consume the k/v-pairs. + * @return the sink + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +CxPropertiesSink cxPropertiesMapSink(CxMap *map); + +/** + * Creates a properties source based on an UCX string. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nodiscard +CxPropertiesSource cxPropertiesStringSource(cxstring str); + +/** + * Creates a properties source based on C string with the specified length. + * + * @param str the string + * @param len the length + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1, 2) +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); + +/** + * Creates a properties source based on a C string. + * + * The length will be determined with strlen(), so the string MUST be + * zero-terminated. + * + * @param str the string + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +CxPropertiesSource cxPropertiesCstrSource(const char *str); + +/** + * Creates a properties source based on an FILE. + * + * @param file the file + * @param chunk_size how many bytes may be read in one operation + * + * @return the properties source + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1) +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); + + +/** + * Loads properties data from a source and transfers it to a sink. + * + * This function tries to read as much data from the source as possible. + * When the source was completely consumed and at least on k/v-pair was found, + * the return value will be #CX_PROPERTIES_NO_ERROR. + * When the source was consumed but no k/v-pairs were found, the return value + * will be #CX_PROPERTIES_NO_DATA. + * The other result codes apply, according to their description. + * + * @param prop the properties interface + * @param sink the sink + * @param source the source + * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found + * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed + * @retval CX_PROPERTIES_READ_FAILED reading from the source failed + * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed + * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + */ +cx_attr_nonnull +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_PROPERTIES_H diff --git a/ucx/cx/streams.h b/ucx/cx/streams.h new file mode 100644 index 0000000..abb1e78 --- /dev/null +++ b/ucx/cx/streams.h @@ -0,0 +1,135 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file streams.h + * + * @brief Utility functions for data streams. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_STREAMS_H +#define UCX_STREAMS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can + * set this to zero to let the implementation decide + * @param n the maximum number of bytes that shall be copied. + * If this is larger than @p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_access_w(5) +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is + * @c NULL you can set this to zero to let the implementation decide + * @return total number of bytes copied + */ +#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +cx_attr_nonnull +cx_attr_access_r(1) +cx_attr_access_w(2) +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src (@c void*) the source stream + * @param dest (@c void*) the destination stream + * @param rfnc (@c cx_read_func) the read function + * @param wfnc (@c cx_write_func) the write function + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_STREAMS_H diff --git a/ucx/cx/string.h b/ucx/cx/string.h new file mode 100644 index 0000000..5614425 --- /dev/null +++ b/ucx/cx/string.h @@ -0,0 +1,1554 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file string.h + * @brief Strings that know their length. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_STRING_H +#define UCX_STRING_H + +#include "common.h" +#include "allocator.h" + +/** + * The maximum length of the "needle" in cx_strstr() that can use SBO. + */ +extern const unsigned cx_strstr_sbo_size; + +/** + * The UCX string structure. + */ +struct cx_mutstr_s { + /** + * A pointer to the string. + * @note The string is not necessarily @c NULL terminated. + * Always use the length. + */ + char *ptr; + /** The length of the string */ + size_t length; +}; + +/** + * A mutable string. + */ +typedef struct cx_mutstr_s cxmutstr; + +/** + * The UCX string structure for immutable (constant) strings. + */ +struct cx_string_s { + /** + * A pointer to the immutable string. + * @note The string is not necessarily @c NULL terminated. + * Always use the length. + */ + const char *ptr; + /** The length of the string */ + size_t length; +}; + +/** + * An immutable string. + */ +typedef struct cx_string_s cxstring; + +/** + * Context for string tokenizing. + */ +struct cx_strtok_ctx_s { + /** + * The string to tokenize. + */ + cxstring str; + /** + * The primary delimiter. + */ + cxstring delim; + /** + * Optional array of more delimiters. + */ + const cxstring *delim_more; + /** + * Length of the array containing more delimiters. + */ + size_t delim_more_count; + /** + * Position of the currently active token in the source string. + */ + size_t pos; + /** + * Position of next delimiter in the source string. + * + * If the tokenizer has not yet returned a token, the content of this field + * is undefined. If the tokenizer reached the end of the string, this field + * contains the length of the source string. + */ + size_t delim_pos; + /** + * The position of the next token in the source string. + */ + size_t next_pos; + /** + * The number of already found tokens. + */ + size_t found; + /** + * The maximum number of tokens that shall be returned. + */ + size_t limit; +}; + +/** + * A string tokenizing context. + */ +typedef struct cx_strtok_ctx_s CxStrtokCtx; + +#ifdef __cplusplus +extern "C" { + +/** + * A literal initializer for an UCX string structure. + * + * @param literal the string literal + */ +#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1} + +#else // __cplusplus + +/** + * A literal initializer for an UCX string structure. + * + * The argument MUST be a string (const char*) @em literal. + * + * @param literal the string literal + */ +#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1} + +#endif + + +/** + * Wraps a mutable string that must be zero-terminated. + * + * The length is implicitly inferred by using a call to @c strlen(). + * + * @note the wrapped string will share the specified pointer to the string. + * If you do want a copy, use cx_strdup() on the return value of this function. + * + * If you need to wrap a constant string, use cx_str(). + * + * @param cstring the string to wrap, must be zero-terminated + * @return the wrapped string + * + * @see cx_mutstrn() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cxmutstr cx_mutstr(char *cstring); + +/** + * Wraps a string that does not need to be zero-terminated. + * + * The argument may be @c NULL if the length is zero. + * + * @note the wrapped string will share the specified pointer to the string. + * If you do want a copy, use cx_strdup() on the return value of this function. + * + * If you need to wrap a constant string, use cx_strn(). + * + * @param cstring the string to wrap (or @c NULL, only if the length is zero) + * @param length the length of the string + * @return the wrapped string + * + * @see cx_mutstr() + */ +cx_attr_nodiscard +cx_attr_access_rw(1, 2) +cxmutstr cx_mutstrn( + char *cstring, + size_t length +); + +/** + * Wraps a string that must be zero-terminated. + * + * The length is implicitly inferred by using a call to @c strlen(). + * + * @note the wrapped string will share the specified pointer to the string. + * If you do want a copy, use cx_strdup() on the return value of this function. + * + * If you need to wrap a non-constant string, use cx_mutstr(). + * + * @param cstring the string to wrap, must be zero-terminated + * @return the wrapped string + * + * @see cx_strn() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cxstring cx_str(const char *cstring); + + +/** + * Wraps a string that does not need to be zero-terminated. + * + * The argument may be @c NULL if the length is zero. + * + * @note the wrapped string will share the specified pointer to the string. + * If you do want a copy, use cx_strdup() on the return value of this function. + * + * If you need to wrap a non-constant string, use cx_mutstrn(). + * + * @param cstring the string to wrap (or @c NULL, only if the length is zero) + * @param length the length of the string + * @return the wrapped string + * + * @see cx_str() + */ +cx_attr_nodiscard +cx_attr_access_r(1, 2) +cxstring cx_strn( + const char *cstring, + size_t length +); + +#ifdef __cplusplus +} // extern "C" +cx_attr_nodiscard +static inline cxstring cx_strcast(cxmutstr str) { + return cx_strn(str.ptr, str.length); +} +cx_attr_nodiscard +static inline cxstring cx_strcast(cxstring str) { + return str; +} +extern "C" { +#else +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_m(cxmutstr str) { + return (cxstring) {str.ptr, str.length}; +} +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_c(cxstring str) { + return str; +} + +/** +* Casts a mutable string to an immutable string. +* +* Does nothing for already immutable strings. +* +* @note This is not seriously a cast. Instead, you get a copy +* of the struct with the desired pointer type. Both structs still +* point to the same location, though! +* +* @param str (@c cxstring or @c cxmutstr) the string to cast +* @return (@c cxstring) an immutable copy of the string pointer +*/ +#define cx_strcast(str) _Generic((str), \ + cxmutstr: cx_strcast_m, \ + cxstring: cx_strcast_c) \ + (str) +#endif + +/** + * Passes the pointer in this string to @c free(). + * + * The pointer in the struct is set to @c NULL and the length is set to zero. + * + * @note There is no implementation for cxstring, because it is unlikely that + * you ever have a const char* you are really supposed to free. + * If you encounter such situation, you should double-check your code. + * + * @param str the string to free + */ +void cx_strfree(cxmutstr *str); + +/** + * Passes the pointer in this string to the allocators free function. + * + * The pointer in the struct is set to @c NULL and the length is set to zero. + * + * @note There is no implementation for cxstring, because it is unlikely that + * you ever have a const char* you are really supposed to free. + * If you encounter such situation, you should double-check your code. + * + * @param alloc the allocator + * @param str the string to free + */ +cx_attr_nonnull_arg(1) +void cx_strfree_a( + const CxAllocator *alloc, + cxmutstr *str +); + +/** + * Returns the accumulated length of all specified strings. + * + * If this sum overflows, errno is set to EOVERFLOW. + * + * @attention if the count argument is larger than the number of the + * specified strings, the behavior is undefined. + * + * @param count the total number of specified strings + * @param ... all strings + * @return the accumulated length of all strings + */ +cx_attr_nodiscard +size_t cx_strlen( + size_t count, + ... +); + +/** + * Concatenates strings. + * + * The resulting string will be allocated by the specified allocator. + * So developers @em must pass the return value to cx_strfree_a() eventually. + * + * If @p str already contains a string, the memory will be reallocated and + * the other strings are appended. Otherwise, new memory is allocated. + * + * If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. + * It is also guaranteed that the returned string is zero-terminated. + * + * @param alloc the allocator to use + * @param str the string the other strings shall be concatenated to + * @param count the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return the concatenated string + */ +cx_attr_nodiscard +cx_attr_nonnull +cxmutstr cx_strcat_ma( + const CxAllocator *alloc, + cxmutstr str, + size_t count, + ... +); + +/** + * Concatenates strings and returns a new string. + * + * The resulting string will be allocated by the specified allocator. + * So developers @em must pass the return value to cx_strfree_a() eventually. + * +* If memory allocation fails, the pointer in the returned string will + * be @c NULL. Depending on the allocator, @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. + * It is also guaranteed that the returned string is zero-terminated. + * + * @param alloc (@c CxAllocator*) the allocator to use + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string + */ +#define cx_strcat_a(alloc, count, ...) \ +cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + +/** + * Concatenates strings and returns a new string. + * + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. + * +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. + * It is also guaranteed that the returned string is zero-terminated. + * + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other UCX strings + * @return (@c cxmutstr) the concatenated string + */ +#define cx_strcat(count, ...) \ +cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + +/** + * Concatenates strings. + * + * The resulting string will be allocated by standard @c malloc(). + * So developers @em must pass the return value to cx_strfree() eventually. + * + * If @p str already contains a string, the memory will be reallocated and + * the other strings are appended. Otherwise, new memory is allocated. + * +* If memory allocation fails, the pointer in the returned string will + * be @c NULL and @c errno might be set. + * + * @note It is guaranteed that there is only one allocation for the + * resulting string. + * It is also guaranteed that the returned string is zero-terminated. + * + * @param str (@c cxmutstr) the string the other strings shall be concatenated to + * @param count (@c size_t) the number of the other following strings to concatenate + * @param ... all other strings + * @return (@c cxmutstr) the concatenated string + */ +#define cx_strcat_m(str, count, ...) \ +cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) + +/** + * Returns a substring starting at the specified location. + * + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. + * Use cx_strdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of @p string starting at @p start + * + * @see cx_strsubsl() + * @see cx_strsubs_m() + * @see cx_strsubsl_m() + */ +cx_attr_nodiscard +cxstring cx_strsubs( + cxstring string, + size_t start +); + +/** + * Returns a substring starting at the specified location. + * + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. + * + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. + * Use cx_strdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the returned string + * @return a substring of @p string starting at @p start + * + * @see cx_strsubs() + * @see cx_strsubs_m() + * @see cx_strsubsl_m() + */ +cx_attr_nodiscard +cxstring cx_strsubsl( + cxstring string, + size_t start, + size_t length +); + +/** + * Returns a substring starting at the specified location. + * + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. + * Use cx_strdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of @p string starting at @p start + * + * @see cx_strsubsl_m() + * @see cx_strsubs() + * @see cx_strsubsl() + */ +cx_attr_nodiscard +cxmutstr cx_strsubs_m( + cxmutstr string, + size_t start +); + +/** + * Returns a substring starting at the specified location. + * + * The returned string will be limited to @p length bytes or the number + * of bytes available in @p string, whichever is smaller. + * + * @attention the new string references the same memory area as the + * input string and is usually @em not zero-terminated. + * Use cx_strdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the returned string + * @return a substring of @p string starting at @p start + * + * @see cx_strsubs_m() + * @see cx_strsubs() + * @see cx_strsubsl() + */ +cx_attr_nodiscard +cxmutstr cx_strsubsl_m( + cxmutstr string, + size_t start, + size_t length +); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of @p chr + * + * @see cx_strchr_m() + */ +cx_attr_nodiscard +cxstring cx_strchr( + cxstring string, + int chr +); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of @p chr + * + * @see cx_strchr() + */ +cx_attr_nodiscard +cxmutstr cx_strchr_m( + cxmutstr string, + int chr +); + +/** + * Returns a substring starting at the location of the last occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of @p chr + * + * @see cx_strrchr_m() + */ +cx_attr_nodiscard +cxstring cx_strrchr( + cxstring string, + int chr +); + +/** + * Returns a substring starting at the location of the last occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of @p chr + * + * @see cx_strrchr() + */ +cx_attr_nodiscard +cxmutstr cx_strrchr_m( + cxmutstr string, + int chr +); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If @p haystack does not contain @p needle, an empty string is returned. + * + * If @p needle is an empty string, the complete @p haystack is + * returned. + * + * @param haystack the string to be scanned + * @param needle string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * @p needle, or an empty string, if the sequence is not + * contained + * @see cx_strstr_m() + */ +cx_attr_nodiscard +cxstring cx_strstr( + cxstring haystack, + cxstring needle +); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If @p haystack does not contain @p needle, an empty string is returned. + * + * If @p needle is an empty string, the complete @p haystack is + * returned. + * + * @param haystack the string to be scanned + * @param needle string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * @p needle, or an empty string, if the sequence is not + * contained + * @see cx_strstr() + */ +cx_attr_nodiscard +cxmutstr cx_strstr_m( + cxmutstr haystack, + cxstring needle +); + +/** + * Splits a given string using a delimiter string. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @param string the string to split + * @param delim the delimiter + * @param limit the maximum number of split items + * @param output a pre-allocated array of at least @p limit length + * @return the actual number of split items + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) +size_t cx_strsplit( + cxstring string, + cxstring delim, + size_t limit, + cxstring *output +); + +/** + * Splits a given string using a delimiter string. + * + * The array pointed to by @p output will be allocated by @p allocator. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. + * + * @param allocator the allocator to use for allocating the resulting array + * @param string the string to split + * @param delim the delimiter + * @param limit the maximum number of split items + * @param output a pointer where the address of the allocated array shall be + * written to + * @return the actual number of split items + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) +size_t cx_strsplit_a( + const CxAllocator *allocator, + cxstring string, + cxstring delim, + size_t limit, + cxstring **output +); + + +/** + * Splits a given string using a delimiter string. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @param string the string to split + * @param delim the delimiter + * @param limit the maximum number of split items + * @param output a pre-allocated array of at least @p limit length + * @return the actual number of split items + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) +size_t cx_strsplit_m( + cxmutstr string, + cxstring delim, + size_t limit, + cxmutstr *output +); + +/** + * Splits a given string using a delimiter string. + * + * The array pointed to by @p output will be allocated by @p allocator. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. + * + * @param allocator the allocator to use for allocating the resulting array + * @param string the string to split + * @param delim the delimiter + * @param limit the maximum number of split items + * @param output a pointer where the address of the allocated array shall be + * written to + * @return the actual number of split items + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) +size_t cx_strsplit_ma( + const CxAllocator *allocator, + cxmutstr string, + cxstring delim, + size_t limit, + cxmutstr **output +); + +/** + * Compares two strings. + * + * @param s1 the first string + * @param s2 the second string + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal + */ +cx_attr_nodiscard +int cx_strcmp( + cxstring s1, + cxstring s2 +); + +/** + * Compares two strings ignoring case. + * + * @param s1 the first string + * @param s2 the second string + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case + */ +cx_attr_nodiscard +int cx_strcasecmp( + cxstring s1, + cxstring s2 +); + +/** + * Compares two strings. + * + * This function has a compatible signature for the use as a cx_compare_func. + * + * @param s1 the first string + * @param s2 the second string + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal + */ +cx_attr_nodiscard +cx_attr_nonnull +int cx_strcmp_p( + const void *s1, + const void *s2 +); + +/** + * Compares two strings ignoring case. + * + * This function has a compatible signature for the use as a cx_compare_func. + * + * @param s1 the first string + * @param s2 the second string + * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger + * than @p s2, zero if both strings equal ignoring case + */ +cx_attr_nodiscard +cx_attr_nonnull +int cx_strcasecmp_p( + const void *s1, + const void *s2 +); + + +/** + * Creates a duplicate of the specified string. + * + * The new string will contain a copy allocated by @p allocator. + * + * @note The returned string is guaranteed to be zero-terminated. + * + * @param allocator the allocator to use + * @param string the string to duplicate + * @return a duplicate of the string + * @see cx_strdup() + */ +cx_attr_nodiscard +cx_attr_nonnull +cxmutstr cx_strdup_a( + const CxAllocator *allocator, + cxstring string +); + +/** + * Creates a duplicate of the specified string. + * + * The new string will contain a copy allocated by standard + * @c malloc(). So developers @em must pass the return value to cx_strfree(). + * + * @note The returned string is guaranteed to be zero-terminated. + * + * @param string (@c cxstring) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup_a() + */ +#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) + + +/** + * Creates a duplicate of the specified string. + * + * The new string will contain a copy allocated by @p allocator. + * + * @note The returned string is guaranteed to be zero-terminated. + * + * @param allocator (@c CxAllocator*) the allocator to use + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup_m() + */ +#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string)) + +/** + * Creates a duplicate of the specified string. + * + * The new string will contain a copy allocated by standard + * @c malloc(). So developers @em must pass the return value to cx_strfree(). + * + * @note The returned string is guaranteed to be zero-terminated. + * + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup_ma() + */ +#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string)) + +/** + * Omits leading and trailing spaces. + * + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. + * + * @param string the string that shall be trimmed + * @return the trimmed string + */ +cx_attr_nodiscard +cxstring cx_strtrim(cxstring string); + +/** + * Omits leading and trailing spaces. + * + * @note the returned string references the same memory, thus you + * must @em not free the returned memory. + * + * @param string the string that shall be trimmed + * @return the trimmed string + */ +cx_attr_nodiscard +cxmutstr cx_strtrim_m(cxmutstr string); + +/** + * Checks, if a string has a specific prefix. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise + */ +cx_attr_nodiscard +bool cx_strprefix( + cxstring string, + cxstring prefix +); + +/** + * Checks, if a string has a specific suffix. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise + */ +cx_attr_nodiscard +bool cx_strsuffix( + cxstring string, + cxstring suffix +); + +/** + * Checks, if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise + */ +cx_attr_nodiscard +bool cx_strcaseprefix( + cxstring string, + cxstring prefix +); + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise + */ +cx_attr_nodiscard +bool cx_strcasesuffix( + cxstring string, + cxstring suffix +); + +/** + * Converts the string to lower case. + * + * The change is made in-place. If you want a copy, use cx_strdup(), first. + * + * @param string the string to modify + * @see cx_strdup() + */ +void cx_strlower(cxmutstr string); + +/** + * Converts the string to upper case. + * + * The change is made in-place. If you want a copy, use cx_strdup(), first. + * + * @param string the string to modify + * @see cx_strdup() + */ +void cx_strupper(cxmutstr string); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most @p replmax occurrences. + * + * The returned string will be allocated by @p allocator and is guaranteed + * to be zero-terminated. + * + * If allocation fails, or the input string is empty, + * the returned string will be empty. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +cx_attr_nodiscard +cx_attr_nonnull +cxmutstr cx_strreplacen_a( + const CxAllocator *allocator, + cxstring str, + cxstring pattern, + cxstring replacement, + size_t replmax +); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most @p replmax occurrences. + * + * The returned string will be allocated by @c malloc() and is guaranteed + * to be zero-terminated. + * + * If allocation fails, or the input string is empty, + * the returned string will be empty. + * + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @param replmax (@c size_t) maximum number of replacements + * @return (@c cxmutstr) the resulting string after applying the replacements + */ +#define cx_strreplacen(str, pattern, replacement, replmax) \ +cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * + * The returned string will be allocated by @p allocator and is guaranteed + * to be zero-terminated. + * + * If allocation fails, or the input string is empty, + * the returned string will be empty. + * + * @param allocator (@c CxAllocator*) the allocator to use + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements + */ +#define cx_strreplace_a(allocator, str, pattern, replacement) \ +cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most @p replmax occurrences. + * + * The returned string will be allocated by @c malloc() and is guaranteed + * to be zero-terminated. + * + * If allocation fails, or the input string is empty, + * the returned string will be empty. + * + * @param str (@c cxstring) the string where replacements should be applied + * @param pattern (@c cxstring) the pattern to search for + * @param replacement (@c cxstring) the replacement string + * @return (@c cxmutstr) the resulting string after applying the replacements + */ +#define cx_strreplace(str, pattern, replacement) \ +cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX) + +/** + * Creates a string tokenization context. + * + * @param str the string to tokenize + * @param delim the delimiter (must not be empty) + * @param limit the maximum number of tokens that shall be returned + * @return a new string tokenization context + */ +cx_attr_nodiscard +CxStrtokCtx cx_strtok( + cxstring str, + cxstring delim, + size_t limit +); + +/** +* Creates a string tokenization context for a mutable string. +* +* @param str the string to tokenize +* @param delim the delimiter (must not be empty) +* @param limit the maximum number of tokens that shall be returned +* @return a new string tokenization context +*/ +cx_attr_nodiscard +CxStrtokCtx cx_strtok_m( + cxmutstr str, + cxstring delim, + size_t limit +); + +/** + * Returns the next token. + * + * The token will point to the source string. + * + * @param ctx the tokenization context + * @param token a pointer to memory where the next token shall be stored + * @return true if successful, false if the limit or the end of the string + * has been reached + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) +bool cx_strtok_next( + CxStrtokCtx *ctx, + cxstring *token +); + +/** + * Returns the next token of a mutable string. + * + * The token will point to the source string. + * If the context was not initialized over a mutable string, modifying + * the data of the returned token is undefined behavior. + * + * @param ctx the tokenization context + * @param token a pointer to memory where the next token shall be stored + * @return true if successful, false if the limit or the end of the string + * has been reached + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) +bool cx_strtok_next_m( + CxStrtokCtx *ctx, + cxmutstr *token +); + +/** + * Defines an array of more delimiters for the specified tokenization context. + * + * @param ctx the tokenization context + * @param delim array of more delimiters + * @param count number of elements in the array + */ +cx_attr_nonnull +cx_attr_access_r(2, 3) +void cx_strtok_delim( + CxStrtokCtx *ctx, + const cxstring *delim, + size_t count +); + +/* ------------------------------------------------------------------------- * + * string to number conversion functions * + * ------------------------------------------------------------------------- */ + +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep); + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep); + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep); + +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep); + +#ifndef CX_STR_IMPLEMENTATION +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc(cx_strcast(str), output, base, groupsep) +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtouz_lc(str, output, base, groupsep) cx_strtouz_lc(cx_strcast(str), output, base, groupsep) + +/** + * @copydoc cx_strtouz() + */ +#define cx_strtos(str, output, base) cx_strtos_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi(str, output, base) cx_strtoi_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtol(str, output, base) cx_strtol_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoll(str, output, base) cx_strtoll_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi8(str, output, base) cx_strtoi8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi16(str, output, base) cx_strtoi16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi32(str, output, base) cx_strtoi32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi64(str, output, base) cx_strtoi64_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoz(str, output, base) cx_strtoz_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtous(str, output, base) cx_strtous_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou(str, output, base) cx_strtou_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoul(str, output, base) cx_strtoul_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoull(str, output, base) cx_strtoull_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou8(str, output, base) cx_strtou8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou16(str, output, base) cx_strtou16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou32(str, output, base) cx_strtou32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou64(str, output, base) cx_strtou64_lc(str, output, base, ",") +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtouz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtouz(str, output, base) cx_strtouz_lc(str, output, base, ",") + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc(cx_strcast(str), output, decsep, groupsep) +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @param decsep the decimal separator + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc(cx_strcast(str), output, decsep, groupsep) + +/** + * Converts a string to a single precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the float variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtof(str, output) cx_strtof_lc(str, output, '.', ",") +/** + * Converts a string to a double precision floating point number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character. + * + * The decimal separator is assumed to be a dot character. + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose a different format, use cx_strtof_lc(). + * + * @param str the string to convert + * @param output a pointer to the double variable where the result shall be stored + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtod(str, output) cx_strtod_lc(str, output, '.', ",") + +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //UCX_STRING_H diff --git a/ucx/cx/test.h b/ucx/cx/test.h new file mode 100644 index 0000000..fdd9269 --- /dev/null +++ b/ucx/cx/test.h @@ -0,0 +1,338 @@ +/* + * 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: **** + * + * + * CX_TEST(function_name); + * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional + * + * + * **** IN SOURCE FILE: **** + * + * CX_TEST_SUBROUTINE(subroutine_name, paramlist) { + * // tests with CX_TEST_ASSERT() + * } + * + * CX_TEST(function_name) { + * // memory allocation and other stuff here + * #CX_TEST_DO { + * // tests with CX_TEST_ASSERT() and/or + * // calls with CX_TEST_CALL_SUBROUTINE() here + * } + * // cleanup of memory here + * } + * + * + * @attention Do not call own functions within a test, that use + * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_TEST_H +#define UCX_TEST_H + +#include "common.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 + +#if !defined(__clang__) && __GNUC__ > 3 +#pragma GCC diagnostic ignored "-Wclobbered" +#endif + +/** Type for the CxTestSuite. */ +typedef struct CxTestSuite CxTestSuite; + +/** Pointer to a test function. */ +typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func); + +/** Type for the internal list of test cases. */ +typedef struct CxTestSet CxTestSet; + +/** Structure for the internal list of test cases. */ +struct CxTestSet { + + /** Test case. */ + CxTest test; + + /** Pointer to the next list element. */ + CxTestSet *next; +}; + +/** + * A test suite containing multiple test cases. + */ +struct CxTestSuite { + + /** The number of successful tests after the suite has been run. */ + unsigned int success; + + /** The number of failed tests after the suite has been run. */ + unsigned int failure; + + /** The optional name of this test suite. */ + const char *name; + + /** + * Internal list of test cases. + * Use cx_test_register() to add tests to this list. + */ + CxTestSet *tests; +}; + +/** + * Creates a new test suite. + * @param name optional name of the suite + * @return a new test suite + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_malloc +static inline CxTestSuite* cx_test_suite_new(const char *name) { + CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); + if (suite != NULL) { + suite->name = name; + suite->success = 0; + suite->failure = 0; + suite->tests = NULL; + } + + return suite; +} + +/** + * Deallocates a test suite. + * + * @param suite the test suite to free + */ +static inline void cx_test_suite_free(CxTestSuite* suite) { + if (suite == NULL) return; + CxTestSet *l = suite->tests; + while (l != NULL) { + CxTestSet *e = l; + l = l->next; + free(e); + } + free(suite); +} + +/** + * Registers a test function with the specified test suite. + * + * @param suite the suite, the test function shall be added to + * @param test the test function to register + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +static inline int cx_test_register(CxTestSuite* suite, CxTest test) { + CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); + if (t) { + t->test = test; + t->next = NULL; + if (suite->tests == NULL) { + suite->tests = t; + } else { + CxTestSet *last = suite->tests; + while (last->next) { + last = last->next; + } + last->next = t; + } + return 0; + } else { + return 1; + } +} + +/** + * Runs a test suite and writes the test log to the specified stream. + * @param suite the test suite to run + * @param out_target the target buffer or file to write the output to + * @param out_writer the write function writing to @p out_target + */ +cx_attr_nonnull +static inline void cx_test_run(CxTestSuite *suite, + void *out_target, cx_write_func out_writer) { + if (suite->name == NULL) { + out_writer("*** Test Suite ***\n", 1, 19, out_target); + } else { + out_writer("*** Test Suite : ", 1, 17, out_target); + out_writer(suite->name, 1, strlen(suite->name), out_target); + out_writer(" ***\n", 1, 5, out_target); + } + suite->success = 0; + suite->failure = 0; + for (CxTestSet *elem = suite->tests; elem; elem = elem->next) { + elem->test(suite, out_target, out_writer); + } + out_writer("\nAll test completed.\n", 1, 21, out_target); + char total[80]; + int len = snprintf( + total, 80, + " Total: %u\n Success: %u\n Failure: %u\n\n", + suite->success + suite->failure, suite->success, suite->failure + ); + out_writer(total, 1, len, out_target); +} + +/** + * Runs a test suite and writes the test log to the specified FILE stream. + * @param suite (@c CxTestSuite*) the test suite to run + * @param file (@c FILE*) the target file to write the output to + */ +#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite) + +/** + * Runs a test suite and writes the test log to stdout. + * @param suite (@c CxTestSuite*) the test suite to run + */ +#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout) + +/** + * Macro for a #CxTest function header. + * + * Use this macro to declare and/or define a #CxTest function. + * + * @param name the name of the test function + */ +#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_) + +/** + * Defines the scope of a test. + * + * @code + * CX_TEST(my_test_name) { + * // setup code + * CX_TEST_DO { + * // your tests go here + * } + * // tear down code + * } + * @endcode + * + * @attention Any CX_TEST_ASSERT() calls must be performed in scope of + * #CX_TEST_DO. + */ +#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\ + _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ + _writefnc_("... ", 1, 4, _output_);\ + jmp_buf _env_;\ + for (unsigned int _cx_test_loop_ = 0 ;\ + _cx_test_loop_ == 0 && !setjmp(_env_);\ + _writefnc_("success.\n", 1, 9, _output_),\ + _suite_->success++, _cx_test_loop_++) + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition (@c bool) the condition to check + * @param message (@c char*) the message that shall be printed out on failure + */ +#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \ + const char *_assert_msg_ = message; \ + _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \ + _writefnc_(".\n", 1, 2, _output_); \ + _suite_->failure++; \ + longjmp(_env_, 1);\ + } (void) 0 + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition (@c bool) the condition to check + */ +#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed") + +/** + * Macro for a test subroutine function header. + * + * Use this to declare and/or define a subroutine that can be called by using + * CX_TEST_CALL_SUBROUTINE(). + * + * @param name the name of the subroutine + * @param ... the parameter list + * + * @see CX_TEST_CALL_SUBROUTINE() + */ +#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\ + void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__) + +/** + * Macro for calling a test subroutine. + * + * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this + * macro. + * + * @remark You may only call subroutines within a #CX_TEST_DO block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see CX_TEST_SUBROUTINE() + */ +#define CX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ + diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h new file mode 100644 index 0000000..515e7db --- /dev/null +++ b/ucx/cx/tree.h @@ -0,0 +1,1405 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file tree.h + * @brief Interface for tree implementations. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_TREE_H +#define UCX_TREE_H + +#include "common.h" + +#include "collection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A depth-first tree iterator. + * + * This iterator is not position-aware in a strict sense, as it does not assume + * a particular order of elements in the tree. However, the iterator keeps track + * of the number of nodes it has passed in a counter variable. + * Each node, regardless of the number of passes, is counted only once. + * + * @note Objects that are pointed to by an iterator are mutable through that + * iterator. However, if the + * underlying data structure is mutated by other means than this iterator (e.g. + * elements added or removed), the iterator becomes invalid (regardless of what + * cxIteratorValid() returns). + * + * @see CxIterator + */ +typedef struct cx_tree_iterator_s { + /** + * Base members. + */ + CX_ITERATOR_BASE; + /** + * Indicates whether the subtree below the current node shall be skipped. + */ + bool skip; + /** + * Set to true, when the iterator shall visit a node again + * when all it's children have been processed. + */ + bool visit_on_exit; + /** + * True, if this iterator is currently leaving the node. + */ + bool exiting; + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + /** + * Offset in the node struct for the next pointer. + */ + ptrdiff_t loc_next; + /** + * The total number of distinct nodes that have been passed so far. + */ + size_t counter; + /** + * The currently observed node. + * + * This is the same what cxIteratorCurrent() would return. + */ + void *node; + /** + * Stores a copy of the next pointer of the visited node. + * Allows freeing a node on exit without corrupting the iteration. + */ + void *node_next; + /** + * Internal stack. + * Will be automatically freed once the iterator becomes invalid. + * + * If you want to discard the iterator before, you need to manually + * call cxTreeIteratorDispose(). + */ + void **stack; + /** + * Internal capacity of the stack. + */ + size_t stack_capacity; + union { + /** + * Internal stack size. + */ + size_t stack_size; + /** + * The current depth in the tree. + */ + size_t depth; + }; +} CxTreeIterator; + +/** + * An element in a visitor queue. + */ +struct cx_tree_visitor_queue_s { + /** + * The tree node to visit. + */ + void *node; + /** + * The depth of the node. + */ + size_t depth; + /** + * The next element in the queue or @c NULL. + */ + struct cx_tree_visitor_queue_s *next; +}; + +/** + * A breadth-first tree iterator. + * + * This iterator needs to maintain a visitor queue that will be automatically + * freed once the iterator becomes invalid. + * If you want to discard the iterator before, you MUST manually call + * cxTreeVisitorDispose(). + * + * This iterator is not position-aware in a strict sense, as it does not assume + * a particular order of elements in the tree. However, the iterator keeps track + * of the number of nodes it has passed in a counter variable. + * Each node, regardless of the number of passes, is counted only once. + * + * @note Objects that are pointed to by an iterator are mutable through that + * iterator. However, if the + * underlying data structure is mutated by other means than this iterator (e.g. + * elements added or removed), the iterator becomes invalid (regardless of what + * cxIteratorValid() returns). + * + * @see CxIterator + */ +typedef struct cx_tree_visitor_s { + /** + * Base members. + */ + CX_ITERATOR_BASE; + /** + * Indicates whether the subtree below the current node shall be skipped. + */ + bool skip; + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + /** + * Offset in the node struct for the next pointer. + */ + ptrdiff_t loc_next; + /** + * The total number of distinct nodes that have been passed so far. + */ + size_t counter; + /** + * The currently observed node. + * + * This is the same what cxIteratorCurrent() would return. + */ + void *node; + /** + * The current depth in the tree. + */ + size_t depth; + /** + * The next element in the visitor queue. + */ + struct cx_tree_visitor_queue_s *queue_next; + /** + * The last element in the visitor queue. + */ + struct cx_tree_visitor_queue_s *queue_last; +} CxTreeVisitor; + +/** + * Releases internal memory of the given tree iterator. + * @param iter the iterator + */ +cx_attr_nonnull +static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { + free(iter->stack); + iter->stack = NULL; +} + +/** + * Releases internal memory of the given tree visitor. + * @param visitor the visitor + */ +cx_attr_nonnull +static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { + struct cx_tree_visitor_queue_s *q = visitor->queue_next; + while (q != NULL) { + struct cx_tree_visitor_queue_s *next = q->next; + free(q); + q = next; + } +} + +/** + * Advises the iterator to skip the subtree below the current node and + * also continues the current loop. + * + * @param iterator (@c CxTreeIterator) the iterator + */ +#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue + +/** + * Advises the visitor to skip the subtree below the current node and + * also continues the current loop. + * + * @param visitor (@c CxTreeVisitor) the visitor + */ +#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor) + +/** + * Links a node to a (new) parent. + * + * If the node has already a parent, it is unlinked, first. + * If the parent has children already, the node is @em appended to the list + * of all currently existing children. + * + * @param parent the parent node + * @param node the node that shall be linked + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @see cx_tree_unlink() + */ +cx_attr_nonnull +void cx_tree_link( + void *parent, + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Unlinks a node from its parent. + * + * If the node has no parent, this function does nothing. + * + * @param node the node that shall be unlinked from its parent + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @see cx_tree_link() + */ +cx_attr_nonnull +void cx_tree_unlink( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Macro that can be used instead of the magic value for infinite search depth. + */ +#define CX_TREE_SEARCH_INFINITE_DEPTH 0 + +/** + * Function pointer for a search function. + * + * A function of this kind shall check if the specified @p node + * contains the given @p data or if one of the children might contain + * the data. + * + * The function should use the returned integer to indicate how close the + * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. + * + * For example if a tree stores file path information, a node that is + * describing a parent directory of a filename that is searched, shall + * return a positive number to indicate that a child node might contain the + * searched item. On the other hand, if the node denotes a path that is not a + * prefix of the searched filename, the function would return -1 to indicate + * that the search does not need to be continued in that branch. + * + * @param node the node that is currently investigated + * @param data the data that is searched for + * + * @return 0 if the node contains the data, + * positive if one of the children might contain the data, + * negative if neither the node, nor the children contains the data + */ +cx_attr_nonnull +typedef int (*cx_tree_search_data_func)(const void *node, const void *data); + + +/** + * Function pointer for a search function. + * + * A function of this kind shall check if the specified @p node + * contains the same @p data as @p new_node or if one of the children might + * contain the data. + * + * The function should use the returned integer to indicate how close the + * match is, where a negative number means that it does not match at all. + * Zero means exact match and a positive number is an implementation defined + * measure for the distance to an exact match. + * + * For example if a tree stores file path information, a node that is + * describing a parent directory of a filename that is searched, shall + * return a positive number to indicate that a child node might contain the + * searched item. On the other hand, if the node denotes a path that is not a + * prefix of the searched filename, the function would return -1 to indicate + * that the search does not need to be continued in that branch. + * + * @param node the node that is currently investigated + * @param new_node a new node with the information which is searched + * + * @return 0 if @p node contains the same data as @p new_node, + * positive if one of the children might contain the data, + * negative if neither the node, nor the children contains the data + */ +cx_attr_nonnull +typedef int (*cx_tree_search_func)(const void *node, const void *new_node); + +/** + * Searches for data in a tree. + * + * When the data cannot be found exactly, the search function might return a + * closest result which might be a good starting point for adding a new node + * to the tree (see also #cx_tree_add()). + * + * Depending on the tree structure it is not necessarily guaranteed that the + * "closest" match is uniquely defined. This function will search for a node + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * node matching the criteria is returned. + * + * @param root the root node + * @param depth the maximum depth (zero=indefinite, one=just root) + * @param data the data to search for + * @param sfunc the search function + * @param result where the result shall be stored + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return zero if the node was found exactly, positive if a node was found that + * could contain the node (but doesn't right now), negative if the tree does not + * contain any node that might be related to the searched data + */ +cx_attr_nonnull +cx_attr_access_w(5) +int cx_tree_search_data( + const void *root, + size_t depth, + const void *data, + cx_tree_search_data_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Searches for a node in a tree. + * + * When no node with the same data can be found, the search function might + * return a closest result which might be a good starting point for adding the + * new node to the tree (see also #cx_tree_add()). + * + * Depending on the tree structure it is not necessarily guaranteed that the + * "closest" match is uniquely defined. This function will search for a node + * with the best match according to the @p sfunc (meaning: the return value of + * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary + * node matching the criteria is returned. + * + * @param root the root node +* @param depth the maximum depth (zero=indefinite, one=just root) + * @param node the node to search for + * @param sfunc the search function + * @param result where the result shall be stored + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return zero if the node was found exactly, positive if a node was found that + * could contain the node (but doesn't right now), negative if the tree does not + * contain any node that might be related to the searched data + */ +cx_attr_nonnull +cx_attr_access_w(5) +int cx_tree_search( + const void *root, + size_t depth, + const void *node, + cx_tree_search_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Creates a depth-first iterator for a tree with the specified root node. + * + * @note A tree iterator needs to maintain a stack of visited nodes, which is + * allocated using stdlib malloc(). + * When the iterator becomes invalid, this memory is automatically released. + * However, if you wish to cancel the iteration before the iterator becomes + * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release + * the memory. + * + * @remark The returned iterator does not support cxIteratorFlagRemoval(). + * + * @param root the root node + * @param visit_on_exit set to true, when the iterator shall visit a node again + * after processing all children + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return the new tree iterator + * @see cxTreeIteratorDispose() + */ +cx_attr_nodiscard +CxTreeIterator cx_tree_iterator( + void *root, + bool visit_on_exit, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Creates a breadth-first iterator for a tree with the specified root node. + * + * @note A tree visitor needs to maintain a queue of to be visited nodes, which + * is allocated using stdlib malloc(). + * When the visitor becomes invalid, this memory is automatically released. + * However, if you wish to cancel the iteration before the visitor becomes + * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release + * the memory. + * + * @remark The returned iterator does not support cxIteratorFlagRemoval(). + * + * @param root the root node + * @param loc_children offset in the node struct for the children linked list + * @param loc_next offset in the node struct for the next pointer + * @return the new tree visitor + * @see cxTreeVisitorDispose() + */ +cx_attr_nodiscard +CxTreeVisitor cx_tree_visitor( + void *root, + ptrdiff_t loc_children, + ptrdiff_t loc_next +); + +/** + * Describes a function that creates a tree node from the specified data. + * The first argument points to the data the node shall contain and + * the second argument may be used for additional data (e.g. an allocator). + * Functions of this type shall either return a new pointer to a newly + * created node or @c NULL when allocation fails. + * + * @note the function may leave the node pointers in the struct uninitialized. + * The caller is responsible to set them according to the intended use case. + */ +cx_attr_nonnull_arg(1) +typedef void *(*cx_tree_node_create_func)(const void *, void *); + +/** + * The local search depth for a new subtree when adding multiple elements. + * The default value is 3. + * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to + * implement optimized insertion of multiple elements into a tree. + */ +extern unsigned int cx_tree_add_look_around_depth; + +/** + * Adds multiple elements efficiently to a tree. + * + * Once an element cannot be added to the tree, this function returns, leaving + * the iterator in a valid state pointing to the element that could not be + * added. + * Also, the pointer of the created node will be stored to @p failed. + * The integer returned by this function denotes the number of elements obtained + * from the @p iter that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. + * + * The advantage of this function compared to multiple invocations of + * #cx_tree_add() is that the search for the insert locations is not always + * started from the root node. + * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes + * of the current insert location before starting from the root node again. + * When the variable is set to zero, only the last found location is checked + * again. + * + * Refer to the documentation of #cx_tree_add() for more details. + * + * @param iter a pointer to an arbitrary iterator + * @param num the maximum number of elements to obtain from the iterator + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param root the root node of the tree + * @param failed location where the pointer to a failed node shall be stored + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the number of nodes created and added + * @see cx_tree_add() + */ +cx_attr_nonnull_arg(1, 3, 4, 6, 7) +cx_attr_access_w(6) +size_t cx_tree_add_iter( + struct cx_iterator_base_s *iter, + size_t num, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Adds multiple elements efficiently to a tree. + * + * Once an element cannot be added to the tree, this function returns, storing + * the pointer of the created node to @p failed. + * The integer returned by this function denotes the number of elements from + * the @p src array that have been successfully processed. + * When all elements could be processed, a @c NULL pointer will be written to + * @p failed. + * + * The advantage of this function compared to multiple invocations of + * #cx_tree_add() is that the search for the insert locations is not always + * started from the root node. + * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes + * of the current insert location before starting from the root node again. + * When the variable is set to zero, only the last found location is checked + * again. + * + * Refer to the documentation of #cx_tree_add() for more details. + * + * @param src a pointer to the source data array + * @param num the number of elements in the @p src array + * @param elem_size the size of each element in the @p src array + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param failed location where the pointer to a failed node shall be stored + * @param root the root node of the tree + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the number of array elements successfully processed + * @see cx_tree_add() + */ +cx_attr_nonnull_arg(1, 4, 5, 7, 8) +cx_attr_access_w(7) +size_t cx_tree_add_array( + const void *src, + size_t num, + size_t elem_size, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Adds data to a tree. + * + * An adequate location where to add the new tree node is searched with the + * specified @p sfunc. + * + * When a location is found, the @p cfunc will be invoked with @p cdata. + * + * The node returned by @p cfunc will be linked into the tree. + * When @p sfunc returned a positive integer, the new node will be linked as a + * child. The other children (now siblings of the new node) are then checked + * with @p sfunc, whether they could be children of the new node and re-linked + * accordingly. + * + * When @p sfunc returned zero and the found node has a parent, the new + * node will be added as sibling - otherwise, the new node will be added + * as a child. + * + * When @p sfunc returned a negative value, the new node will not be added to + * the tree and this function returns a non-zero value. + * The caller should check if @p cnode contains a node pointer and deal with the + * node that could not be added. + * + * This function also returns a non-zero value when @p cfunc tries to allocate + * a new node but fails to do so. In that case, the pointer stored to @p cnode + * will be @c NULL. + * + * Multiple elements can be added more efficiently with + * #cx_tree_add_array() or #cx_tree_add_iter(). + * + * @param src a pointer to the data + * @param sfunc a search function + * @param cfunc a node creation function + * @param cdata optional additional data + * @param cnode the location where a pointer to the new node is stored + * @param root the root node of the tree + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return zero when a new node was created and added to the tree, + * non-zero otherwise + */ +cx_attr_nonnull_arg(1, 2, 3, 5, 6) +cx_attr_access_w(5) +int cx_tree_add( + const void *src, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **cnode, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + + +/** + * Tree class type. + */ +typedef struct cx_tree_class_s cx_tree_class; + +/** + * Base structure that can be used for tree nodes in a #CxTree. + */ +struct cx_tree_node_base_s { + /** + * Pointer to the parent. + */ + struct cx_tree_node_base_s *parent; + /** + * Pointer to the first child. + */ + struct cx_tree_node_base_s *children; + /** + * Pointer to the last child. + */ + struct cx_tree_node_base_s *last_child; + /** + * Pointer to the previous sibling. + */ + struct cx_tree_node_base_s *prev; + /** + * Pointer to the next sibling. + */ + struct cx_tree_node_base_s *next; +}; + +/** + * Structure for holding the base data of a tree. + */ +struct cx_tree_s { + /** + * The tree class definition. + */ + const cx_tree_class *cl; + + /** + * Allocator to allocate new nodes. + */ + const CxAllocator *allocator; + + /** + * A pointer to the root node. + * + * Will be @c NULL when @c size is 0. + */ + void *root; + + /** + * A function to create new nodes. + * + * Invocations to this function will receive a pointer to this tree + * structure as second argument. + * + * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to. + */ + cx_tree_node_create_func node_create; + + /** + * An optional simple destructor for the tree nodes. + */ + cx_destructor_func simple_destructor; + + /** + * An optional advanced destructor for the tree nodes. + */ + cx_destructor_func2 advanced_destructor; + + /** + * The pointer to additional data that is passed to the advanced destructor. + */ + void *destructor_data; + + /** + * A function to compare two nodes. + */ + cx_tree_search_func search; + + /** + * A function to compare a node with data. + */ + cx_tree_search_data_func search_data; + + /** + * The number of currently stored elements. + */ + size_t size; + + /** + * Offset in the node struct for the parent pointer. + */ + ptrdiff_t loc_parent; + + /** + * Offset in the node struct for the children linked list. + */ + ptrdiff_t loc_children; + + /** + * Optional offset in the node struct for the pointer to the last child + * in the linked list (negative if there is no such pointer). + */ + ptrdiff_t loc_last_child; + + /** + * Offset in the node struct for the previous sibling pointer. + */ + ptrdiff_t loc_prev; + + /** + * Offset in the node struct for the next sibling pointer. + */ + ptrdiff_t loc_next; +}; + +/** + * Macro to roll out the #cx_tree_node_base_s structure with a custom + * node type. + * + * Must be used as first member in your custom tree struct. + * + * @param type the data type for the nodes + */ +#define CX_TREE_NODE_BASE(type) \ + type *parent; \ + type *children;\ + type *last_child;\ + type *prev;\ + type *next + +/** + * Macro for specifying the layout of a base node tree. + * + * When your tree uses #CX_TREE_NODE_BASE, you can use this + * macro in all tree functions that expect the layout parameters + * @c loc_parent, @c loc_children, @c loc_last_child, @c loc_prev, + * and @c loc_next. + */ +#define cx_tree_node_base_layout \ + offsetof(struct cx_tree_node_base_s, parent),\ + offsetof(struct cx_tree_node_base_s, children),\ + offsetof(struct cx_tree_node_base_s, last_child),\ + offsetof(struct cx_tree_node_base_s, prev), \ + offsetof(struct cx_tree_node_base_s, next) + +/** + * The class definition for arbitrary trees. + */ +struct cx_tree_class_s { + /** + * Member function for inserting a single element. + * + * Implementations SHALL NOT simply invoke @p insert_many as this comes + * with too much overhead. + */ + int (*insert_element)( + struct cx_tree_s *tree, + const void *data + ); + + /** + * Member function for inserting multiple elements. + * + * Implementations SHALL avoid to perform a full search in the tree for + * every element even though the source data MAY be unsorted. + */ + size_t (*insert_many)( + struct cx_tree_s *tree, + struct cx_iterator_base_s *iter, + size_t n + ); + + /** + * Member function for finding a node. + */ + void *(*find)( + struct cx_tree_s *tree, + const void *subtree, + const void *data, + size_t depth + ); +}; + +/** + * Common type for all tree implementations. + */ +typedef struct cx_tree_s CxTree; + + +/** + * Destroys a node and it's subtree. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * When this function is invoked on the root node of the tree, it destroys the + * tree contents, but - in contrast to #cxTreeFree() - not the tree + * structure, leaving an empty tree behind. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @attention This function will not free the memory of the nodes with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to remove + * @see cxTreeFree() + */ +cx_attr_nonnull +void cxTreeDestroySubtree(CxTree *tree, void *node); + + +/** + * Destroys the tree contents. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor, starting with the leaf nodes of the subtree. + * + * This is a convenience macro for invoking #cxTreeDestroySubtree() on the + * root node of the tree. + * + * @attention Be careful when calling this function when no destructor function + * is registered that actually frees the memory of nodes. In that case you will + * need a reference to the (former) root node of the tree somewhere or + * otherwise you will be leaking memory. + * + * @param tree the tree + * @see cxTreeDestroySubtree() + */ +#define cxTreeClear(tree) cxTreeDestroySubtree(tree, tree->root) + +/** + * Deallocates the tree structure. + * + * The destructor functions are invoked for each node, starting with the leaf + * nodes. + * It is guaranteed that for each node the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will only invoke the destructor functions + * on the nodes. + * It will NOT additionally free the nodes with the tree's allocator, because + * that would cause a double-free in most scenarios where the advanced + * destructor is already freeing the memory. + * + * @param tree the tree to free + */ +void cxTreeFree(CxTree *tree); + +/** + * Creates a new tree structure based on the specified layout. + * + * The specified @p allocator will be used for creating the tree struct + * and SHALL be used by @p create_func to allocate memory for the nodes. + * + * @note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator the allocator that shall be used + * (if @c NULL, a default stdlib allocator will be used) + * @param create_func a function that creates new nodes + * @param search_func a function that compares two nodes + * @param search_data_func a function that compares a node with data + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreateSimple() + * @see cxTreeCreateWrapped() + */ +cx_attr_nonnull_arg(2, 3, 4) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) +CxTree *cxTreeCreate( + const CxAllocator *allocator, + cx_tree_node_create_func create_func, + cx_tree_search_func search_func, + cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Creates a new tree structure based on a default layout. + * + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first + * member (or at least respect the default offsets specified in the tree + * struct) and they MUST be allocated with the specified allocator. + * + * @note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator (@c CxAllocator*) the allocator that shall be used + * @param create_func (@c cx_tree_node_create_func) a function that creates new nodes + * @param search_func (@c cx_tree_search_func) a function that compares two nodes + * @param search_data_func (@c cx_tree_search_data_func) a function that compares a node with data + * @return (@c CxTree*) the new tree + * @see cxTreeCreate() + */ +#define cxTreeCreateSimple(\ + allocator, create_func, search_func, search_data_func \ +) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ +cx_tree_node_base_layout) + +/** + * Creates a new tree structure based on an existing tree. + * + * The specified @p allocator will be used for creating the tree struct. + * + * @attention This function will create an incompletely defined tree structure + * where neither the create function, the search function, nor a destructor + * will be set. If you wish to use any of this functionality for the wrapped + * tree, you need to specify those functions afterwards. + * + * @param allocator the allocator that was used for nodes of the wrapped tree + * (if @c NULL, a default stdlib allocator is assumed) + * @param root the root node of the tree that shall be wrapped + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreate() + */ +cx_attr_nonnull_arg(2) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) +CxTree *cxTreeCreateWrapped( + const CxAllocator *allocator, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Inserts data into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to insert + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +static inline int cxTreeInsert( + CxTree *tree, + const void *data +) { + return tree->cl->insert_element(tree, data); +} + +/** + * Inserts elements provided by an iterator efficiently into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param iter the iterator providing the elements + * @param n the maximum number of elements to insert + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertIter( + CxTree *tree, + struct cx_iterator_base_s *iter, + size_t n +) { + return tree->cl->insert_many(tree, iter, n); +} + +/** + * Inserts an array of data efficiently into the tree. + * + * @remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the array of data to insert + * @param elem_size the size of each element in the array + * @param n the number of elements in the array + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertArray( + CxTree *tree, + const void *data, + size_t elem_size, + size_t n +) { + if (n == 0) return 0; + if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; + CxIterator iter = cxIterator(data, elem_size, n); + return cxTreeInsertIter(tree, cxIteratorRef(iter), n); +} + +/** + * Searches the data in the specified tree. + * + * @remark For this function to work, the tree needs a specified @c search_data + * function, which might not be available wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @return the first matching node, or @c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFind( + CxTree *tree, + const void *data +) { + return tree->cl->find(tree, tree->root, data, 0); +} + +/** + * Searches the data in the specified subtree. + * + * When @p max_depth is zero, the depth is not limited. + * The @p subtree_root itself is on depth 1 and its children have depth 2. + * + * @note When @p subtree_root is not part of the @p tree, the behavior is + * undefined. + * + * @remark For this function to work, the tree needs a specified @c search_data + * function, which might not be the case for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @param subtree_root the node where to start + * @param max_depth the maximum search depth + * @return the first matching node, or @c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFindInSubtree( + CxTree *tree, + const void *data, + void *subtree_root, + size_t max_depth +) { + return tree->cl->find(tree, subtree_root, data, max_depth); +} + +/** + * Determines the size of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the number of nodes in the specified subtree + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the tree depth including the @p subtree_root + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the entire tree. + * + * @param tree the tree + * @return the tree depth, counting the root as one + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeDepth(CxTree *tree); + +/** + * Creates a depth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterateSubtree( + CxTree *tree, + void *node, + bool visit_on_exit +) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a breadth-first iterator for the specified tree starting in @p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a depth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterate( + CxTree *tree, + bool visit_on_exit +) { + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); +} + +/** + * Creates a breadth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); +} + +/** + * Sets the (new) parent of the specified child. + * + * If the @p child is not already member of the tree, this function behaves + * as #cxTreeAddChildNode(). + * + * @param tree the tree + * @param parent the (new) parent of the child + * @param child the node to add + * @see cxTreeAddChildNode() + */ +cx_attr_nonnull +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +); + +/** + * Adds a new node to the tree. + * + * If the @p child is already member of the tree, the behavior is undefined. + * Use #cxTreeSetParent() if you want to move a subtree to another location. + * + * @attention The node may be externally created, but MUST obey the same rules + * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * the same allocator). + * + * @param tree the tree + * @param parent the parent of the node to add + * @param child the node to add + * @see cxTreeSetParent() + */ +cx_attr_nonnull +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +); + +/** + * Creates a new node and adds it to the tree. + * + * With this function you can decide where exactly the new node shall be added. + * If you specified an appropriate search function, you may want to consider + * leaving this task to the tree by using #cxTreeInsert(). + * + * Be aware that adding nodes at arbitrary locations in the tree might cause + * wrong or undesired results when subsequently invoking #cxTreeInsert() and + * the invariant imposed by the search function does not hold any longer. + * + * @param tree the tree + * @param parent the parent node of the new node + * @param data the data that will be submitted to the create function + * @return zero when the new node was created, non-zero on allocation failure + * @see cxTreeInsert() + */ +cx_attr_nonnull +int cxTreeAddChild( + CxTree *tree, + void *parent, + const void *data +); + +/** + * A function that is invoked when a node needs to be re-linked to a new parent. + * + * When a node is re-linked, sometimes the contents need to be updated. + * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode() + * so that those updates can be applied when re-linking the children of the + * removed node. + * + * @param node the affected node + * @param old_parent the old parent of the node + * @param new_parent the new parent of the node + */ +cx_attr_nonnull +typedef void (*cx_tree_relink_func)( + void *node, + const void *old_parent, + const void *new_parent +); + +/** + * Removes a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed node by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if @p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +int cxTreeRemoveNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + +/** + * Removes a node and it's subtree from the tree. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @note The destructor function, if any, will @em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove + */ +cx_attr_nonnull +void cxTreeRemoveSubtree(CxTree *tree, void *node); + +/** + * Destroys a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor. + * + * @attention This function will not free the memory of the node with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to destroy (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if @p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //UCX_TREE_H diff --git a/ucx/cx/utils.h b/ucx/cx/utils.h new file mode 100644 index 0000000..be359ea --- /dev/null +++ b/ucx/cx/utils.h @@ -0,0 +1,194 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file utils.h + * + * \brief General purpose utility functions. + * + * \author Mike Becker + * \author Olaf Wintermann + * \copyright 2-Clause BSD License + */ + +#ifndef UCX_UTILS_H +#define UCX_UTILS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Convenience macro for a for loop that counts from zero to n-1. + */ +#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++) + +/** + * Convenience macro for swapping two pointers. + */ +#ifdef __cplusplus +#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) +#else +#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) +#endif + +// cx_szmul() definition + +#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) +#define CX_SZMUL_BUILTIN + +/** + * Alias for \c __builtin_mul_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) + +#else // no GNUC or clang bultin + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * This is a custom implementation in case there is no compiler builtin + * available. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t where the result should be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +int cx_szmul_impl(size_t a, size_t b, size_t *result); + +#endif // cx_szmul + + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or \c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can + * set this to zero to let the implementation decide + * @param n the maximum number of bytes that shall be copied. + * If this is larger than \p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +__attribute__((__nonnull__(1, 2, 3, 4))) +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or \c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can + * set this to zero to let the implementation decide + * @return total number of bytes copied + */ +#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +__attribute__((__nonnull__)) +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_UTILS_H diff --git a/ucx/hash_key.c b/ucx/hash_key.c new file mode 100644 index 0000000..fac29bb --- /dev/null +++ b/ucx/hash_key.c @@ -0,0 +1,112 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/hash_key.h" +#include + +void cx_hash_murmur(CxHashKey *key) { + const unsigned char *data = key->data; + if (data == NULL) { + // extension: special value for NULL + key->hash = 1574210520u; + return; + } + size_t len = key->len; + + unsigned m = 0x5bd1e995; + unsigned r = 24; + unsigned h = 25 ^ (unsigned) len; + unsigned i = 0; + while (len >= 4) { + unsigned k = data[i + 0] & 0xFF; + k |= (data[i + 1] & 0xFF) << 8; + k |= (data[i + 2] & 0xFF) << 16; + k |= (data[i + 3] & 0xFF) << 24; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + i += 4; + len -= 4; + } + + switch (len) { + case 3: + h ^= (data[i + 2] & 0xFF) << 16; + __attribute__((__fallthrough__)); + case 2: + h ^= (data[i + 1] & 0xFF) << 8; + __attribute__((__fallthrough__)); + case 1: + h ^= (data[i + 0] & 0xFF); + h *= m; + __attribute__((__fallthrough__)); + default: // do nothing + ; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + key->hash = h; +} + +CxHashKey cx_hash_key_str(const char *str) { + CxHashKey key; + key.data = str; + key.len = str == NULL ? 0 : strlen(str); + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key_bytes( + const unsigned char *bytes, + size_t len +) { + CxHashKey key; + key.data = bytes; + key.len = len; + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key( + const void *obj, + size_t len +) { + CxHashKey key; + key.data = obj; + key.len = len; + cx_hash_murmur(&key); + return key; +} diff --git a/ucx/hash_map.c b/ucx/hash_map.c new file mode 100644 index 0000000..fa9eba4 --- /dev/null +++ b/ucx/hash_map.c @@ -0,0 +1,498 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/hash_map.h" + +#include +#include +#include + +struct cx_hash_map_element_s { + /** A pointer to the next element in the current bucket. */ + struct cx_hash_map_element_s *next; + + /** The corresponding key. */ + CxHashKey key; + + /** The value data. */ + char data[]; +}; + +static void cx_hash_map_clear(struct cx_map_s *map) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + for (size_t i = 0; i < hash_map->bucket_count; i++) { + struct cx_hash_map_element_s *elem = hash_map->buckets[i]; + if (elem != NULL) { + do { + struct cx_hash_map_element_s *next = elem->next; + // invoke the destructor + cx_invoke_destructor(map, elem->data); + // free the key data + cxFree(map->collection.allocator, (void *) elem->key.data); + // free the node + cxFree(map->collection.allocator, elem); + // proceed + elem = next; + } while (elem != NULL); + + // do not leave a dangling pointer + hash_map->buckets[i] = NULL; + } + } + map->collection.size = 0; +} + +static void cx_hash_map_destructor(struct cx_map_s *map) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + + // free the buckets + cx_hash_map_clear(map); + cxFree(map->collection.allocator, hash_map->buckets); + + // free the map structure + cxFree(map->collection.allocator, map); +} + +static int cx_hash_map_put( + CxMap *map, + CxHashKey key, + void *value +) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + const CxAllocator *allocator = map->collection.allocator; + + unsigned hash = key.hash; + if (hash == 0) { + cx_hash_murmur(&key); + hash = key.hash; + } + + size_t slot = hash % hash_map->bucket_count; + struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; + struct cx_hash_map_element_s *prev = NULL; + + while (elm != NULL && elm->key.hash < hash) { + prev = elm; + elm = elm->next; + } + + if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && + memcmp(elm->key.data, key.data, key.len) == 0) { + // overwrite existing element + if (map->collection.store_pointer) { + memcpy(elm->data, &value, sizeof(void *)); + } else { + memcpy(elm->data, value, map->collection.elem_size); + } + } else { + // allocate new element + struct cx_hash_map_element_s *e = cxMalloc( + allocator, + sizeof(struct cx_hash_map_element_s) + map->collection.elem_size + ); + if (e == NULL) return -1; + + // write the value + if (map->collection.store_pointer) { + memcpy(e->data, &value, sizeof(void *)); + } else { + memcpy(e->data, value, map->collection.elem_size); + } + + // copy the key + void *kd = cxMalloc(allocator, key.len); + if (kd == NULL) return -1; + memcpy(kd, key.data, key.len); + e->key.data = kd; + e->key.len = key.len; + e->key.hash = hash; + + // insert the element into the linked list + if (prev == NULL) { + hash_map->buckets[slot] = e; + } else { + prev->next = e; + } + e->next = elm; + + // increase the size + map->collection.size++; + } + + return 0; +} + +static void cx_hash_map_unlink( + struct cx_hash_map_s *hash_map, + size_t slot, + struct cx_hash_map_element_s *prev, + struct cx_hash_map_element_s *elm +) { + // unlink + if (prev == NULL) { + hash_map->buckets[slot] = elm->next; + } else { + prev->next = elm->next; + } + // free element + cxFree(hash_map->base.collection.allocator, (void *) elm->key.data); + cxFree(hash_map->base.collection.allocator, elm); + // decrease size + hash_map->base.collection.size--; +} + +/** + * Helper function to avoid code duplication. + * + * If @p remove is true, and @p targetbuf is @c NULL, the element + * will be destroyed when found. + * + * If @p remove is true, and @p targetbuf is set, the element will + * be copied to that buffer and no destructor function is called. + * + * If @p remove is false, @p targetbuf must not be non-null and + * either the pointer, when the map is storing pointers, is copied + * to the target buffer, or a pointer to the stored object will + * be copied to the target buffer. + * + * @param map the map + * @param key the key to look up + * @param targetbuf see description + * @param remove flag indicating whether the looked up entry shall be removed + * @return zero, if the key was found, non-zero otherwise + */ +static int cx_hash_map_get_remove( + CxMap *map, + CxHashKey key, + void *targetbuf, + bool remove +) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + + unsigned hash = key.hash; + if (hash == 0) { + cx_hash_murmur(&key); + hash = key.hash; + } + + size_t slot = hash % hash_map->bucket_count; + struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; + struct cx_hash_map_element_s *prev = NULL; + while (elm && elm->key.hash <= hash) { + if (elm->key.hash == hash && elm->key.len == key.len) { + if (memcmp(elm->key.data, key.data, key.len) == 0) { + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); + } else { + memcpy(targetbuf, elm->data, map->collection.elem_size); + } + cx_hash_map_unlink(hash_map, slot, prev, elm); + } else { + assert(targetbuf != NULL); + void *data = NULL; + if (map->collection.store_pointer) { + data = *(void **) elm->data; + } else { + data = elm->data; + } + memcpy(targetbuf, &data, sizeof(void *)); + } + return 0; + } + } + prev = elm; + elm = prev->next; + } + + return 1; +} + +static void *cx_hash_map_get( + const CxMap *map, + CxHashKey key +) { + // we can safely cast, because we know the map stays untouched + void *ptr = NULL; + int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false); + return found == 0 ? ptr : NULL; +} + +static int cx_hash_map_remove( + CxMap *map, + CxHashKey key, + void *targetbuf +) { + return cx_hash_map_get_remove(map, key, targetbuf, true); +} + +static void *cx_hash_map_iter_current_entry(const void *it) { + const struct cx_iterator_s *iter = it; + // struct has to have a compatible signature + return (struct cx_map_entry_s *) &(iter->kv_data); +} + +static void *cx_hash_map_iter_current_key(const void *it) { + const struct cx_iterator_s *iter = it; + struct cx_hash_map_element_s *elm = iter->elem_handle; + return &elm->key; +} + +static void *cx_hash_map_iter_current_value(const void *it) { + const struct cx_iterator_s *iter = it; + const struct cx_hash_map_s *map = iter->src_handle.c; + struct cx_hash_map_element_s *elm = iter->elem_handle; + if (map->base.collection.store_pointer) { + return *(void **) elm->data; + } else { + return elm->data; + } +} + +static bool cx_hash_map_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle != NULL; +} + +static void cx_hash_map_iter_next(void *it) { + struct cx_iterator_s *iter = it; + struct cx_hash_map_element_s *elm = iter->elem_handle; + struct cx_hash_map_s *map = iter->src_handle.m; + + // remove current element, if asked + if (iter->base.remove) { + + // clear the flag + iter->base.remove = false; + + // determine the next element + struct cx_hash_map_element_s *next = elm->next; + + // search the previous element + struct cx_hash_map_element_s *prev = NULL; + if (map->buckets[iter->slot] != elm) { + prev = map->buckets[iter->slot]; + while (prev->next != elm) { + prev = prev->next; + } + } + + // destroy + cx_invoke_destructor((struct cx_map_s *) map, elm->data); + + // unlink + cx_hash_map_unlink(map, iter->slot, prev, elm); + + // advance + elm = next; + } else { + // just advance + elm = elm->next; + iter->index++; + } + + // search the next bucket, if required + while (elm == NULL && ++iter->slot < map->bucket_count) { + elm = map->buckets[iter->slot]; + } + + // fill the struct with the next element + iter->elem_handle = elm; + if (elm == NULL) { + iter->kv_data.key = NULL; + iter->kv_data.value = NULL; + } else { + iter->kv_data.key = &elm->key; + if (map->base.collection.store_pointer) { + iter->kv_data.value = *(void **) elm->data; + } else { + iter->kv_data.value = elm->data; + } + } +} + +static CxIterator cx_hash_map_iterator( + const CxMap *map, + enum cx_map_iterator_type type +) { + CxIterator iter; + + iter.src_handle.c = map; + iter.elem_count = map->collection.size; + + switch (type) { + case CX_MAP_ITERATOR_PAIRS: + iter.elem_size = sizeof(CxMapEntry); + iter.base.current = cx_hash_map_iter_current_entry; + break; + case CX_MAP_ITERATOR_KEYS: + iter.elem_size = sizeof(CxHashKey); + iter.base.current = cx_hash_map_iter_current_key; + break; + case CX_MAP_ITERATOR_VALUES: + iter.elem_size = map->collection.elem_size; + iter.base.current = cx_hash_map_iter_current_value; + break; + default: + assert(false); // LCOV_EXCL_LINE + } + + iter.base.valid = cx_hash_map_iter_valid; + iter.base.next = cx_hash_map_iter_next; + iter.base.remove = false; + iter.base.mutating = false; + + iter.slot = 0; + iter.index = 0; + + if (map->collection.size > 0) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + struct cx_hash_map_element_s *elm = hash_map->buckets[0]; + while (elm == NULL) { + elm = hash_map->buckets[++iter.slot]; + } + iter.elem_handle = elm; + iter.kv_data.key = &elm->key; + if (map->collection.store_pointer) { + iter.kv_data.value = *(void **) elm->data; + } else { + iter.kv_data.value = elm->data; + } + } else { + iter.elem_handle = NULL; + iter.kv_data.key = NULL; + iter.kv_data.value = NULL; + } + + return iter; +} + +static cx_map_class cx_hash_map_class = { + cx_hash_map_destructor, + cx_hash_map_clear, + cx_hash_map_put, + cx_hash_map_get, + cx_hash_map_remove, + cx_hash_map_iterator, +}; + +CxMap *cxHashMapCreate( + const CxAllocator *allocator, + size_t itemsize, + size_t buckets +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + if (buckets == 0) { + // implementation defined default + buckets = 16; + } + + struct cx_hash_map_s *map = cxCalloc(allocator, 1, + sizeof(struct cx_hash_map_s)); + if (map == NULL) return NULL; + + // initialize hash map members + map->bucket_count = buckets; + map->buckets = cxCalloc(allocator, buckets, + sizeof(struct cx_hash_map_element_s *)); + if (map->buckets == NULL) { // LCOV_EXCL_START + cxFree(allocator, map); + return NULL; + } // LCOV_EXCL_STOP + + // initialize base members + map->base.cl = &cx_hash_map_class; + map->base.collection.allocator = allocator; + + if (itemsize > 0) { + map->base.collection.store_pointer = false; + map->base.collection.elem_size = itemsize; + } else { + map->base.collection.store_pointer = true; + map->base.collection.elem_size = sizeof(void *); + } + + return (CxMap *) map; +} + +int cxMapRehash(CxMap *map) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) { + + size_t new_bucket_count = (map->collection.size * 5) >> 1; + if (new_bucket_count < hash_map->bucket_count) { + errno = EOVERFLOW; + return 1; + } + struct cx_hash_map_element_s **new_buckets = cxCalloc( + map->collection.allocator, + new_bucket_count, sizeof(struct cx_hash_map_element_s *) + ); + + if (new_buckets == NULL) return 1; + + // iterate through the elements and assign them to their new slots + for (size_t slot = 0; slot < hash_map->bucket_count; slot++) { + struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; + while (elm != NULL) { + struct cx_hash_map_element_s *next = elm->next; + size_t new_slot = elm->key.hash % new_bucket_count; + + // find position where to insert + struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot]; + struct cx_hash_map_element_s *bucket_prev = NULL; + while (bucket_next != NULL && + bucket_next->key.hash < elm->key.hash) { + bucket_prev = bucket_next; + bucket_next = bucket_next->next; + } + + // insert + if (bucket_prev == NULL) { + elm->next = new_buckets[new_slot]; + new_buckets[new_slot] = elm; + } else { + bucket_prev->next = elm; + elm->next = bucket_next; + } + + // advance + elm = next; + } + } + + // assign result to the map + hash_map->bucket_count = new_bucket_count; + cxFree(map->collection.allocator, hash_map->buckets); + hash_map->buckets = new_buckets; + } + return 0; +} diff --git a/ucx/iterator.c b/ucx/iterator.c new file mode 100644 index 0000000..6a78a69 --- /dev/null +++ b/ucx/iterator.c @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/iterator.h" + +#include + +static bool cx_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->index < iter->elem_count; +} + +static void *cx_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle; +} + +static void *cx_iter_current_ptr(const void *it) { + const struct cx_iterator_s *iter = it; + return *(void**)iter->elem_handle; +} + +static void cx_iter_next_fast(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + iter->elem_count--; + // only move the last element when we are not currently aiming + // at the last element already + if (iter->index < iter->elem_count) { + void *last = ((char *) iter->src_handle.m) + + iter->elem_count * iter->elem_size; + memcpy(iter->elem_handle, last, iter->elem_size); + } + } else { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; + } +} + +static void cx_iter_next_slow(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + iter->elem_count--; + + // number of elements to move + size_t remaining = iter->elem_count - iter->index; + if (remaining > 0) { + memmove( + iter->elem_handle, + ((char *) iter->elem_handle) + iter->elem_size, + remaining * iter->elem_size + ); + } + } else { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; + } +} + +CxIterator cxMutIterator( + void *array, + size_t elem_size, + size_t elem_count, + bool remove_keeps_order +) { + CxIterator iter; + + iter.index = 0; + iter.src_handle.m = array; + iter.elem_handle = array; + iter.elem_size = elem_size; + iter.elem_count = array == NULL ? 0 : elem_count; + iter.base.valid = cx_iter_valid; + iter.base.current = cx_iter_current; + iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast; + iter.base.remove = false; + iter.base.mutating = true; + + return iter; +} + +CxIterator cxIterator( + const void *array, + size_t elem_size, + size_t elem_count +) { + CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false); + iter.base.mutating = false; + return iter; +} + +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +) { + CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order); + iter.base.current = cx_iter_current_ptr; + return iter; +} + +CxIterator cxIteratorPtr( + const void *array, + size_t elem_count +) { + CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false); + iter.base.mutating = false; + return iter; +} diff --git a/ucx/json.c b/ucx/json.c new file mode 100644 index 0000000..72c7811 --- /dev/null +++ b/ucx/json.c @@ -0,0 +1,1212 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/json.h" +#include "cx/compare.h" + +#include +#include +#include +#include +#include +#include + +/* + * RFC 8259 + * https://tools.ietf.org/html/rfc8259 + */ + +static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; + +static int json_cmp_objvalue(const void *l, const void *r) { + const CxJsonObjValue *left = l; + const CxJsonObjValue *right = r; + return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); +} + +static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { + assert(obj->type == CX_JSON_OBJECT); + CxJsonObjValue kv_dummy; + kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); + size_t index = cx_array_binary_search( + obj->value.object.values, + obj->value.object.values_size, + sizeof(CxJsonObjValue), + &kv_dummy, + json_cmp_objvalue + ); + if (index == obj->value.object.values_size) { + return NULL; + } else { + return &obj->value.object.values[index]; + } +} + +static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { + assert(objv->type == CX_JSON_OBJECT); + const CxAllocator * const al = objv->allocator; + CxJsonObject *obj = &(objv->value.object); + + // determine the index where we need to insert the new member + size_t index = cx_array_binary_search_sup( + obj->values, + obj->values_size, + sizeof(CxJsonObjValue), + &member, json_cmp_objvalue + ); + + // is the name already present? + if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { + // free the original value + cx_strfree_a(al, &obj->values[index].name); + cxJsonValueFree(obj->values[index].value); + // replace the item + obj->values[index] = member; + + // nothing more to do + return 0; + } + + // determine the old capacity and reserve for one more element + CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); + size_t oldcap = obj->values_capacity; + if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; + + // check the new capacity, if we need to realloc the index array + size_t newcap = obj->values_capacity; + if (newcap > oldcap) { + if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { + return 1; + } + } + + // check if append or insert + if (index < obj->values_size) { + // move the other elements + memmove( + &obj->values[index+1], + &obj->values[index], + (obj->values_size - index) * sizeof(CxJsonObjValue) + ); + // increase indices for the moved elements + for (size_t i = 0; i < obj->values_size ; i++) { + if (obj->indices[i] >= index) { + obj->indices[i]++; + } + } + } + + // insert the element and set the index + obj->values[index] = member; + obj->indices[obj->values_size] = index; + obj->values_size++; + + return 0; +} + +static void token_destroy(CxJsonToken *token) { + if (token->allocated) { + cx_strfree(&token->content); + } +} + +static int num_isexp(const char *content, size_t length, size_t pos) { + if (pos >= length) { + return 0; + } + + int ok = 0; + for (size_t i = pos; i < length; i++) { + char c = content[i]; + if (isdigit(c)) { + ok = 1; + } else if (i == pos) { + if (!(c == '+' || c == '-')) { + return 0; + } + } else { + return 0; + } + } + + return ok; +} + +static CxJsonTokenType token_numbertype(const char *content, size_t length) { + if (length == 0) return CX_JSON_TOKEN_ERROR; + + if (content[0] != '-' && !isdigit(content[0])) { + return CX_JSON_TOKEN_ERROR; + } + + CxJsonTokenType type = CX_JSON_TOKEN_INTEGER; + for (size_t i = 1; i < length; i++) { + if (content[i] == '.') { + if (type == CX_JSON_TOKEN_NUMBER) { + return CX_JSON_TOKEN_ERROR; // more than one decimal separator + } + type = CX_JSON_TOKEN_NUMBER; + } else if (content[i] == 'e' || content[i] == 'E') { + return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; + } else if (!isdigit(content[i])) { + return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep + } + } + + return type; +} + +static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { + cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); + bool allocated = false; + if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { + allocated = true; + str = cx_strcat_m(json->uncompleted.content, 1, str); + if (str.ptr == NULL) { // LCOV_EXCL_START + return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; + } // LCOV_EXCL_STOP + } + json->uncompleted = (CxJsonToken){0}; + CxJsonTokenType ttype; + if (isstring) { + ttype = CX_JSON_TOKEN_STRING; + } else { + cxstring s = cx_strcast(str); + if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) + || !cx_strcmp(s, CX_STR("null"))) { + ttype = CX_JSON_TOKEN_LITERAL; + } else { + ttype = token_numbertype(str.ptr, str.length); + } + } + if (ttype == CX_JSON_TOKEN_ERROR) { + if (allocated) { + cx_strfree(&str); + } + return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; + } + return (CxJsonToken){ttype, allocated, str}; +} + +static CxJsonTokenType char2ttype(char c) { + switch (c) { + case '[': { + return CX_JSON_TOKEN_BEGIN_ARRAY; + } + case '{': { + return CX_JSON_TOKEN_BEGIN_OBJECT; + } + case ']': { + return CX_JSON_TOKEN_END_ARRAY; + } + case '}': { + return CX_JSON_TOKEN_END_OBJECT; + } + case ':': { + return CX_JSON_TOKEN_NAME_SEPARATOR; + } + case ',': { + return CX_JSON_TOKEN_VALUE_SEPARATOR; + } + case '"': { + return CX_JSON_TOKEN_STRING; + } + default: { + if (isspace(c)) { + return CX_JSON_TOKEN_SPACE; + } + } + } + return CX_JSON_NO_TOKEN; +} + +static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { + // check if there is data in the buffer + if (cxBufferEof(&json->buffer)) { + return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? + CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; + } + + // current token type and start index + CxJsonTokenType ttype = json->uncompleted.tokentype; + size_t token_start = json->buffer.pos; + + for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { + char c = json->buffer.space[i]; + if (ttype != CX_JSON_TOKEN_STRING) { + // currently non-string token + CxJsonTokenType ctype = char2ttype(c); // start of new token? + if (ttype == CX_JSON_NO_TOKEN) { + if (ctype == CX_JSON_TOKEN_SPACE) { + json->buffer.pos++; + continue; + } else if (ctype == CX_JSON_TOKEN_STRING) { + // begin string + ttype = CX_JSON_TOKEN_STRING; + token_start = i; + } else if (ctype != CX_JSON_NO_TOKEN) { + // single-char token + json->buffer.pos = i + 1; + *result = (CxJsonToken){ctype, false, {NULL, 0}}; + return CX_JSON_NO_ERROR; + } else { + ttype = CX_JSON_TOKEN_LITERAL; // number or literal + token_start = i; + } + } else { + // finish token + if (ctype != CX_JSON_NO_TOKEN) { + *result = token_create(json, false, token_start, i); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + if (result->tokentype == CX_JSON_TOKEN_ERROR) { + return CX_JSON_FORMAT_ERROR_NUMBER; + } + json->buffer.pos = i; + return CX_JSON_NO_ERROR; + } + } + } else { + // currently inside a string + if (json->tokenizer_escape) { + json->tokenizer_escape = false; + } else { + if (c == '"') { + *result = token_create(json, true, token_start, i + 1); + if (result->tokentype == CX_JSON_NO_TOKEN) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->buffer.pos = i + 1; + return CX_JSON_NO_ERROR; + } else if (c == '\\') { + json->tokenizer_escape = true; + } + } + } + } + + if (ttype != CX_JSON_NO_TOKEN) { + // uncompleted token + size_t uncompleted_len = json->buffer.size - token_start; + if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { + // current token is uncompleted + // save current token content + CxJsonToken uncompleted = { + ttype, true, + cx_strdup(cx_strn(json->buffer.space + token_start, uncompleted_len)) + }; + if (uncompleted.content.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted = uncompleted; + } else { + // previously we also had an uncompleted token + // combine the uncompleted token with the current token + assert(json->uncompleted.allocated); + cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, + cx_strn(json->buffer.space + token_start, uncompleted_len)); + if (str.ptr == NULL) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + json->uncompleted.content = str; + } + // advance the buffer position - we saved the stuff in the uncompleted token + json->buffer.pos += uncompleted_len; + } + + return CX_JSON_INCOMPLETE_DATA; +} + +static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { + // TODO: support more escape sequences + // we know that the unescaped string will be shorter by at least 2 chars + cxmutstr result; + result.length = 0; + result.ptr = cxMalloc(a, str.length - 1); + if (result.ptr == NULL) return result; // LCOV_EXCL_LINE + + bool u = false; + for (size_t i = 1; i < str.length - 1; i++) { + char c = str.ptr[i]; + if (u) { + u = false; + if (c == 'n') { + c = '\n'; + } else if (c == 't') { + c = '\t'; + } + result.ptr[result.length++] = c; + } else { + if (c == '\\') { + u = true; + } else { + result.ptr[result.length++] = c; + } + } + } + result.ptr[result.length] = 0; + + return result; +} + +static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) { + CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); + if (v == NULL) return NULL; // LCOV_EXCL_LINE + + // initialize the value + v->type = type; + v->allocator = json->allocator; + if (type == CX_JSON_ARRAY) { + cx_array_initialize_a(json->allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE + } else if (type == CX_JSON_OBJECT) { + cx_array_initialize_a(json->allocator, v->value.object.values, 16); + v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); + if (v->value.object.values == NULL || + v->value.object.indices == NULL) + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + + // add the new value to a possible parent + if (json->vbuf_size > 0) { + CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; + assert(parent != NULL); + if (parent->type == CX_JSON_ARRAY) { + CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); + if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } else if (parent->type == CX_JSON_OBJECT) { + // the member was already created after parsing the name + assert(json->uncompleted_member.name.ptr != NULL); + json->uncompleted_member.value = v; + if (json_add_objvalue(parent, json->uncompleted_member)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + } else { + assert(false); // LCOV_EXCL_LINE + } + } + + // add the new value to the stack, if it is an array or object + if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { + CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); + if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } + + // if currently no value is parsed, this is now the value of interest + if (json->parsed == NULL) { + json->parsed = v; + } + + return v; + // LCOV_EXCL_START +create_json_value_exit_error: + cxJsonValueFree(v); + return NULL; + // LCOV_EXCL_STOP +} + +#define JP_STATE_VALUE_BEGIN 0 +#define JP_STATE_VALUE_END 10 +#define JP_STATE_VALUE_BEGIN_OBJ 1 +#define JP_STATE_OBJ_SEP_OR_CLOSE 11 +#define JP_STATE_VALUE_BEGIN_AR 2 +#define JP_STATE_ARRAY_SEP_OR_CLOSE 12 +#define JP_STATE_OBJ_NAME_OR_CLOSE 5 +#define JP_STATE_OBJ_NAME 6 +#define JP_STATE_OBJ_COLON 7 + +void cxJsonInit(CxJson *json, const CxAllocator *allocator) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + memset(json, 0, sizeof(CxJson)); + json->allocator = allocator; + + json->states = json->states_internal; + json->states_capacity = cx_nmemb(json->states_internal); + json->states[0] = JP_STATE_VALUE_BEGIN; + json->states_size = 1; + + json->vbuf = json->vbuf_internal; + json->vbuf_capacity = cx_nmemb(json->vbuf_internal); +} + +void cxJsonDestroy(CxJson *json) { + cxBufferDestroy(&json->buffer); + if (json->states != json->states_internal) { + free(json->states); + } + if (json->vbuf != json->vbuf_internal) { + free(json->vbuf); + } + cxJsonValueFree(json->parsed); + json->parsed = NULL; + if (json->uncompleted_member.name.ptr != NULL) { + cx_strfree_a(json->allocator, &json->uncompleted_member.name); + json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; + } +} + +int cxJsonFilln(CxJson *json, const char *buf, size_t size) { + if (cxBufferEof(&json->buffer)) { + // reinitialize the buffer + cxBufferDestroy(&json->buffer); + cxBufferInit(&json->buffer, (char*) buf, size, + NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); + json->buffer.size = size; + return 0; + } else { + return size != cxBufferAppend(buf, 1, size, &json->buffer); + } +} + +static void json_add_state(CxJson *json, int state) { + // we have guaranteed the necessary space with cx_array_simple_reserve() + // therefore, we can safely add the state in the simplest way possible + json->states[json->states_size++] = state; +} + +#define return_rec(code) \ + token_destroy(&token); \ + return code + +static enum cx_json_status json_parse(CxJson *json) { + // Reserve a pointer for a possibly read value + CxJsonValue *vbuf = NULL; + + // grab the next token + CxJsonToken token; + { + enum cx_json_status ret = token_parse_next(json, &token); + if (ret != CX_JSON_NO_ERROR) { + return ret; + } + } + + // pop the current state + assert(json->states_size > 0); + int state = json->states[--json->states_size]; + + // guarantee that at least two more states fit on the stack + CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); + if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } + + + // 0 JP_STATE_VALUE_BEGIN value begin + // 10 JP_STATE_VALUE_END expect value end + // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object) + // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose + // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array) + // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose + // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose + // 6 JP_STATE_OBJ_NAME object, expect name + // 7 JP_STATE_OBJ_COLON object, expect ':' + + if (state < 3) { + // push expected end state to the stack + json_add_state(json, 10 + state); + switch (token.tokentype) { + case CX_JSON_TOKEN_BEGIN_ARRAY: { + if (create_json_value(json, CX_JSON_ARRAY) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_BEGIN_OBJECT: { + if (create_json_value(json, CX_JSON_OBJECT) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_STRING: { + if ((vbuf = create_json_value(json, CX_JSON_STRING)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + cxmutstr str = unescape_string(json->allocator, token.content); + if (str.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + vbuf->value.string = str; + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_INTEGER: + case CX_JSON_TOKEN_NUMBER: { + int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; + if (NULL == (vbuf = create_json_value(json, type))) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (type == CX_JSON_INTEGER) { + if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } else { + if (cx_strtod(token.content, &vbuf->value.number)) { + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + } + } + return_rec(CX_JSON_NO_ERROR); + } + case CX_JSON_TOKEN_LITERAL: { + if ((vbuf = create_json_value(json, CX_JSON_LITERAL)) == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { + vbuf->value.literal = CX_JSON_TRUE; + } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { + vbuf->value.literal = CX_JSON_FALSE; + } else { + vbuf->value.literal = CX_JSON_NULL; + } + return_rec(CX_JSON_NO_ERROR); + } + default: { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } + } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) { + // expect ',' or ']' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_VALUE_BEGIN_AR); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { + // discard the array from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { + if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + // expect string + if (token.tokentype != CX_JSON_TOKEN_STRING) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + + // add new entry + cxmutstr name = unescape_string(json->allocator, token.content); + if (name.ptr == NULL) { + return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE + } + assert(json->uncompleted_member.name.ptr == NULL); + json->uncompleted_member.name = name; + assert(json->vbuf_size > 0); + + // next state + json_add_state(json, JP_STATE_OBJ_COLON); + return_rec(CX_JSON_NO_ERROR); + } + } else if (state == JP_STATE_OBJ_COLON) { + // expect ':' + if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + // next state + json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ); + return_rec(CX_JSON_NO_ERROR); + } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) { + // expect ',' or '}' + if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { + json_add_state(json, JP_STATE_OBJ_NAME); + return_rec(CX_JSON_NO_ERROR); + } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { + // discard the obj from the value buffer + json->vbuf_size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } else { + // should be unreachable + assert(false); + return_rec(-1); + } +} + +CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { + // check if buffer has been filled + if (json->buffer.space == NULL) { + return CX_JSON_NULL_DATA; + } + + // initialize output value + *value = &cx_json_value_nothing; + + // parse data + CxJsonStatus result; + do { + result = json_parse(json); + if (result == CX_JSON_NO_ERROR && json->states_size == 1) { + // final state reached + assert(json->states[0] == JP_STATE_VALUE_END); + assert(json->vbuf_size == 0); + + // write output value + *value = json->parsed; + json->parsed = NULL; + + // re-initialize state machine + json->states[0] = JP_STATE_VALUE_BEGIN; + + return CX_JSON_NO_ERROR; + } + } while (result == CX_JSON_NO_ERROR); + + // the parser might think there is no data + // but when we did not reach the final state, + // we know that there must be more to come + if (result == CX_JSON_NO_DATA && json->states_size > 1) { + return CX_JSON_INCOMPLETE_DATA; + } + + return result; +} + +void cxJsonValueFree(CxJsonValue *value) { + if (value == NULL || value->type == CX_JSON_NOTHING) return; + switch (value->type) { + case CX_JSON_OBJECT: { + CxJsonObject obj = value->value.object; + for (size_t i = 0; i < obj.values_size; i++) { + cxJsonValueFree(obj.values[i].value); + cx_strfree_a(value->allocator, &obj.values[i].name); + } + cxFree(value->allocator, obj.values); + cxFree(value->allocator, obj.indices); + break; + } + case CX_JSON_ARRAY: { + CxJsonArray array = value->value.array; + for (size_t i = 0; i < array.array_size; i++) { + cxJsonValueFree(array.array[i]); + } + cxFree(value->allocator, array.array); + break; + } + case CX_JSON_STRING: { + cxFree(value->allocator, value->value.string.ptr); + break; + } + default: { + break; + } + } + cxFree(value->allocator, value); +} + +CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_OBJECT; + cx_array_initialize_a(allocator, v->value.object.values, 16); + if (v->value.object.values == NULL) { // LCOV_EXCL_START + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); + if (v->value.object.indices == NULL) { // LCOV_EXCL_START + cxFree(allocator, v->value.object.values); + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + return v; +} + +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_ARRAY; + cx_array_initialize_a(allocator, v->value.array.array, 16); + if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + return v; +} + +CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_NUMBER; + v->value.number = num; + return v; +} + +CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_INTEGER; + v->value.integer = num; + return v; +} + +CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { + return cxJsonCreateCxString(allocator, cx_str(str)); +} + +CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_STRING; + cxmutstr s = cx_strdup_a(allocator, str); + if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } + v->value.string = s; + return v; +} + +CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { + CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); + if (v == NULL) return NULL; + v->allocator = allocator; + v->type = CX_JSON_LITERAL; + v->value.literal = lit; + return v; +} + +// LCOV_EXCL_START +// never called as long as malloc() does not return NULL +static void cx_json_arr_free_temp(CxJsonValue** values, size_t count) { + for (size_t i = 0; i < count; i++) { + if (values[i] == NULL) break; + cxJsonValueFree(values[i]); + } + free(values); +} +// LCOV_EXCL_STOP + +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateNumber(arr->allocator, num[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateInteger(arr->allocator, num[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateString(arr->allocator, str[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateCxString(arr->allocator, str[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { + CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + if (values == NULL) return -1; + for (size_t i = 0; i < count; i++) { + values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); + if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { + CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); + assert(arr->type == CX_JSON_ARRAY); + return cx_array_simple_copy_a(&value_realloc, + arr->value.array.array, + arr->value.array.array_size, + val, count + ); +} + +int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { + cxmutstr k = cx_strdup_a(obj->allocator, name); + if (k.ptr == NULL) return -1; + CxJsonObjValue kv = {k, child}; + if (json_add_objvalue(obj, kv)) { + cx_strfree_a(obj->allocator, &k); + return 1; + } else { + return 0; + } +} + +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateObj(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { + CxJsonValue* v = cxJsonCreateArr(obj->allocator); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { + CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { + CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { + CxJsonValue* v = cxJsonCreateString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { + CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } + return v; +} + +CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { + CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); + if (v == NULL) return NULL; + if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} + return v; +} + +CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return &cx_json_value_nothing; + } + return value->value.array.array[index]; +} + +CxIterator cxJsonArrIter(const CxJsonValue *value) { + return cxIteratorPtr( + value->value.array.array, + value->value.array.array_size + ); +} + +CxIterator cxJsonObjIter(const CxJsonValue *value) { + return cxIterator( + value->value.object.values, + sizeof(CxJsonObjValue), + value->value.object.values_size + ); +} + +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { + CxJsonObjValue *member = json_find_objvalue(value, name); + if (member == NULL) { + return &cx_json_value_nothing; + } else { + return member->value; + } +} + +static const CxJsonWriter cx_json_writer_default = { + false, + true, + 255, + false, + 4 +}; + +CxJsonWriter cxJsonWriterCompact(void) { + return cx_json_writer_default; +} + +CxJsonWriter cxJsonWriterPretty(bool use_spaces) { + return (CxJsonWriter) { + true, + true, + 255, + use_spaces, + 4 + }; +} + +static int cx_json_writer_indent( + void *target, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + if (depth == 0) return 0; + + // determine the width and characters to use + const char* indent; // for 32 prepared chars + size_t width = depth; + if (settings->indent_space) { + if (settings->indent == 0) return 0; + width *= settings->indent; + indent = " "; + } else { + indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + } + + // calculate the number of write calls and write + size_t full = width / 32; + size_t remaining = width % 32; + for (size_t i = 0; i < full; i++) { + if (32 != wfunc(indent, 1, 32, target)) return 1; + } + if (remaining != wfunc(indent, 1, remaining, target)) return 1; + + return 0; +} + + +int cx_json_write_rec( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings, + unsigned int depth +) { + // keep track of written items + // the idea is to reduce the number of jumps for error checking + size_t actual = 0, expected = 0; + + // small buffer for number to string conversions + char numbuf[32]; + + // recursively write the values + switch (value->type) { + case CX_JSON_OBJECT: { + const char *begin_obj = "{\n"; + if (settings->pretty) { + actual += wfunc(begin_obj, 1, 2, target); + expected += 2; + } else { + actual += wfunc(begin_obj, 1, 1, target); + expected++; + } + depth++; + size_t elem_count = value->value.object.values_size; + for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { + // get the member either via index array or directly + size_t elem_idx = settings->sort_members + ? look_idx + : value->value.object.indices[look_idx]; + CxJsonObjValue *member = &value->value.object.values[elem_idx]; + if (settings->sort_members) { + depth++;depth--; + } + + // possible indentation + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) { + return 1; // LCOV_EXCL_LINE + } + } + + // the name + actual += wfunc("\"", 1, 1, target); + // TODO: escape the string + actual += wfunc(member->name.ptr, 1, + member->name.length, target); + actual += wfunc("\"", 1, 1, target); + const char *obj_name_sep = ": "; + if (settings->pretty) { + actual += wfunc(obj_name_sep, 1, 2, target); + expected += 4 + member->name.length; + } else { + actual += wfunc(obj_name_sep, 1, 1, target); + expected += 3 + member->name.length; + } + + // the value + if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; + + // end of object-value + if (look_idx < elem_count - 1) { + const char *obj_value_sep = ",\n"; + if (settings->pretty) { + actual += wfunc(obj_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(obj_value_sep, 1, 1, target); + expected++; + } + } else { + if (settings->pretty) { + actual += wfunc("\n", 1, 1, target); + expected ++; + } + } + } + depth--; + if (settings->pretty) { + if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1; + } + actual += wfunc("}", 1, 1, target); + expected++; + break; + } + case CX_JSON_ARRAY: { + actual += wfunc("[", 1, 1, target); + expected++; + CxIterator iter = cxJsonArrIter(value); + cx_foreach(CxJsonValue*, element, iter) { + if (cx_json_write_rec( + target, element, + wfunc, settings, depth) + ) return 1; + + if (iter.index < iter.elem_count - 1) { + const char *arr_value_sep = ", "; + if (settings->pretty) { + actual += wfunc(arr_value_sep, 1, 2, target); + expected += 2; + } else { + actual += wfunc(arr_value_sep, 1, 1, target); + expected++; + } + } + } + actual += wfunc("]", 1, 1, target); + expected++; + break; + } + case CX_JSON_STRING: { + actual += wfunc("\"", 1, 1, target); + // TODO: escape the string + actual += wfunc(value->value.string.ptr, 1, + value->value.string.length, target); + actual += wfunc("\"", 1, 1, target); + expected += 2 + value->value.string.length; + break; + } + case CX_JSON_NUMBER: { + // TODO: locale bullshit + // TODO: formatting settings + snprintf(numbuf, 32, "%g", value->value.number); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 1, len, target); + expected += len; + break; + } + case CX_JSON_INTEGER: { + snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 1, len, target); + expected += len; + break; + } + case CX_JSON_LITERAL: { + if (value->value.literal == CX_JSON_TRUE) { + actual += wfunc("true", 1, 4, target); + expected += 4; + } else if (value->value.literal == CX_JSON_FALSE) { + actual += wfunc("false", 1, 5, target); + expected += 5; + } else { + actual += wfunc("null", 1, 4, target); + expected += 4; + } + break; + } + case CX_JSON_NOTHING: { + // deliberately supported as an empty string! + // users might want to just write the result + // of a get operation without testing the value + // and therefore this should not blow up + break; + } + default: assert(false); // LCOV_EXCL_LINE + } + + return expected != actual; +} + +int cxJsonWrite( + void *target, + const CxJsonValue *value, + cx_write_func wfunc, + const CxJsonWriter *settings +) { + if (settings == NULL) { + settings = &cx_json_writer_default; + } + assert(target != NULL); + assert(value != NULL); + assert(wfunc != NULL); + + return cx_json_write_rec(target, value, wfunc, settings, 0); +} diff --git a/ucx/linked_list.c b/ucx/linked_list.c new file mode 100644 index 0000000..19955f6 --- /dev/null +++ b/ucx/linked_list.c @@ -0,0 +1,1154 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/linked_list.h" +#include "cx/compare.h" +#include +#include + +// LOW LEVEL LINKED LIST FUNCTIONS + +#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) +#define ll_prev(node) CX_LL_PTR(node, loc_prev) +#define ll_next(node) CX_LL_PTR(node, loc_next) +#define ll_advance(node) CX_LL_PTR(node, loc_advance) +#define ll_data(node) (((char*)(node))+loc_data) + +void *cx_linked_list_at( + const void *start, + size_t start_index, + ptrdiff_t loc_advance, + size_t index +) { + assert(start != NULL); + assert(loc_advance >= 0); + size_t i = start_index; + const void *cur = start; + while (i != index && cur != NULL) { + cur = ll_advance(cur); + i < index ? i++ : i--; + } + return (void *) cur; +} + +ssize_t cx_linked_list_find( + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +) { + void *dummy; + return cx_linked_list_find_node( + &dummy, start, + loc_advance, loc_data, + cmp_func, elem + ); +} + +ssize_t cx_linked_list_find_node( + void **result, + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +) { + assert(result != NULL); + assert(start != NULL); + assert(loc_advance >= 0); + assert(loc_data >= 0); + assert(cmp_func); + + const void *node = start; + ssize_t index = 0; + do { + void *current = ll_data(node); + if (cmp_func(current, elem) == 0) { + *result = (void *) node; + return index; + } + node = ll_advance(node); + index++; + } while (node != NULL); + *result = NULL; + return -1; +} + +void *cx_linked_list_first( + const void *node, + ptrdiff_t loc_prev +) { + return cx_linked_list_last(node, loc_prev); +} + +void *cx_linked_list_last( + const void *node, + ptrdiff_t loc_next +) { + assert(node != NULL); + assert(loc_next >= 0); + + const void *cur = node; + const void *last; + do { + last = cur; + } while ((cur = ll_next(cur)) != NULL); + + return (void *) last; +} + +void *cx_linked_list_prev( + const void *begin, + ptrdiff_t loc_next, + const void *node +) { + assert(begin != NULL); + assert(node != NULL); + assert(loc_next >= 0); + if (begin == node) return NULL; + const void *cur = begin; + const void *next; + while (1) { + next = ll_next(cur); + if (next == node) return (void *) cur; + cur = next; + } +} + +void cx_linked_list_link( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert(loc_next >= 0); + ll_next(left) = right; + if (loc_prev >= 0) { + ll_prev(right) = left; + } +} + +void cx_linked_list_unlink( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert (loc_next >= 0); + assert(ll_next(left) == right); + ll_next(left) = NULL; + if (loc_prev >= 0) { + assert(ll_prev(right) == left); + ll_prev(right) = NULL; + } +} + +void cx_linked_list_add( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +) { + void *last; + if (end == NULL) { + assert(begin != NULL); + last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next); + } else { + last = *end; + } + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node); +} + +void cx_linked_list_prepend( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +) { + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node); +} + +void cx_linked_list_insert( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *new_node +) { + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node); +} + +void cx_linked_list_insert_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *insert_begin, + void *insert_end +) { + // find the end of the chain, if not specified + if (insert_end == NULL) { + insert_end = cx_linked_list_last(insert_begin, loc_next); + } + + // determine the successor + void *successor; + if (node == NULL) { + assert(begin != NULL || (end != NULL && loc_prev >= 0)); + if (begin != NULL) { + successor = *begin; + *begin = insert_begin; + } else { + successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev); + } + } else { + successor = ll_next(node); + cx_linked_list_link(node, insert_begin, loc_prev, loc_next); + } + + if (successor == NULL) { + // the list ends with the new chain + if (end != NULL) { + *end = insert_end; + } + } else { + cx_linked_list_link(insert_end, successor, loc_prev, loc_next); + } +} + +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + cx_linked_list_insert_sorted_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} + +void cx_linked_list_insert_sorted_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(insert_begin != NULL); + + // track currently observed nodes + void *dest_prev = NULL; + void *dest = *begin; + void *src = insert_begin; + + // special case: list is empty + if (dest == NULL) { + *begin = src; + if (end != NULL) { + *end = cx_linked_list_last(src, loc_next); + } + return; + } + + // search the list for insertion points + while (dest != NULL && src != NULL) { + // compare current list node with source node + // if less or equal, skip + if (cmp_func(dest, src) <= 0) { + dest_prev = dest; + dest = ll_next(dest); + continue; + } + + // determine chain of elements that can be inserted + void *end_of_chain = src; + void *next_in_chain = ll_next(src); + while (next_in_chain != NULL) { + // once we become larger than the list elem, break + if (cmp_func(dest, next_in_chain) <= 0) { + break; + } + // otherwise, we can insert one more + end_of_chain = next_in_chain; + next_in_chain = ll_next(next_in_chain); + } + + // insert the elements + if (dest_prev == NULL) { + // new begin + *begin = src; + } else { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next); + + // continue with next + src = next_in_chain; + dest_prev = dest; + dest = ll_next(dest); + } + + // insert remaining items + if (src != NULL) { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + + // determine new end of list, if requested + if (end != NULL) { + *end = cx_linked_list_last( + dest != NULL ? dest : dest_prev, loc_next); + } +} + +size_t cx_linked_list_remove_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + size_t num +) { + assert(node != NULL); + assert(loc_next >= 0); + assert(loc_prev >= 0 || begin != NULL); + + // easy exit + if (num == 0) return 0; + + // find adjacent nodes + void *prev; + if (loc_prev >= 0) { + prev = ll_prev(node); + } else { + prev = cx_linked_list_prev(*begin, loc_next, node); + } + + void *next = ll_next(node); + size_t removed = 1; + for (; removed < num && next != NULL ; removed++) { + next = ll_next(next); + } + + // update next pointer of prev node, or set begin + if (prev == NULL) { + if (begin != NULL) { + *begin = next; + } + } else { + ll_next(prev) = next; + } + + // update prev pointer of next node, or set end + if (next == NULL) { + if (end != NULL) { + *end = prev; + } + } else if (loc_prev >= 0) { + ll_prev(next) = prev; + } + + return removed; +} + +size_t cx_linked_list_size( + const void *node, + ptrdiff_t loc_next +) { + assert(loc_next >= 0); + size_t size = 0; + while (node != NULL) { + node = ll_next(node); + size++; + } + return size; +} + +#ifndef CX_LINKED_LIST_SORT_SBO_SIZE +#define CX_LINKED_LIST_SORT_SBO_SIZE 1024 +#endif + +static void cx_linked_list_sort_merge( + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + ptrdiff_t loc_data, + size_t length, + void *ls, + void *le, + void *re, + cx_compare_func cmp_func, + void **begin, + void **end +) { + void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE]; + void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ? + malloc(sizeof(void *) * length) : sbo; + if (sorted == NULL) abort(); + void *rc, *lc; + + lc = ls; + rc = le; + size_t n = 0; + while (lc && lc != le && rc != re) { + if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) { + sorted[n] = lc; + lc = ll_next(lc); + } else { + sorted[n] = rc; + rc = ll_next(rc); + } + n++; + } + while (lc && lc != le) { + sorted[n] = lc; + lc = ll_next(lc); + n++; + } + while (rc && rc != re) { + sorted[n] = rc; + rc = ll_next(rc); + n++; + } + + // Update pointer + if (loc_prev >= 0) ll_prev(sorted[0]) = NULL; + for (size_t i = 0 ; i < length - 1; i++) { + cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next); + } + ll_next(sorted[length - 1]) = NULL; + + *begin = sorted[0]; + *end = sorted[length - 1]; + if (sorted != sbo) { + free(sorted); + } +} + +void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + ptrdiff_t loc_data, + cx_compare_func cmp_func +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(loc_data >= 0); + assert(cmp_func); + + void *lc, *ls, *le, *re; + + // set start node + ls = *begin; + + // early exit when this list is empty + if (ls == NULL) return; + + // check how many elements are already sorted + lc = ls; + size_t ln = 1; + while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) { + lc = ll_next(lc); + ln++; + } + le = ll_next(lc); + + // if first unsorted node is NULL, the list is already completely sorted + if (le != NULL) { + void *rc; + size_t rn = 1; + rc = le; + // skip already sorted elements + while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) { + rc = ll_next(rc); + rn++; + } + re = ll_next(rc); + + // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them + void *sorted_begin, *sorted_end; + cx_linked_list_sort_merge(loc_prev, loc_next, loc_data, + ln + rn, ls, le, re, cmp_func, + &sorted_begin, &sorted_end); + + // Something left? Sort it! + size_t remainder_length = cx_linked_list_size(re, loc_next); + if (remainder_length > 0) { + void *remainder = re; + cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func); + + // merge sorted list with (also sorted) remainder + cx_linked_list_sort_merge(loc_prev, loc_next, loc_data, + ln + rn + remainder_length, + sorted_begin, remainder, NULL, cmp_func, + &sorted_begin, &sorted_end); + } + *begin = sorted_begin; + if (end) *end = sorted_end; + } +} + +int cx_linked_list_compare( + const void *begin_left, + const void *begin_right, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func +) { + const void *left = begin_left, *right = begin_right; + + while (left != NULL && right != NULL) { + const void *left_data = ll_data(left); + const void *right_data = ll_data(right); + int result = cmp_func(left_data, right_data); + if (result != 0) return result; + left = ll_advance(left); + right = ll_advance(right); + } + + if (left != NULL) { return 1; } + else if (right != NULL) { return -1; } + else { return 0; } +} + +void cx_linked_list_reverse( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert(begin != NULL); + assert(loc_next >= 0); + + // swap all links + void *prev = NULL; + void *cur = *begin; + while (cur != NULL) { + void *next = ll_next(cur); + + ll_next(cur) = prev; + if (loc_prev >= 0) { + ll_prev(cur) = next; + } + + prev = cur; + cur = next; + } + + // update begin and end + if (end != NULL) { + *end = *begin; + } + *begin = prev; +} + +// HIGH LEVEL LINKED LIST IMPLEMENTATION + +typedef struct cx_linked_list_node cx_linked_list_node; +struct cx_linked_list_node { + cx_linked_list_node *prev; + cx_linked_list_node *next; + char payload[]; +}; + +#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev) +#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next) +#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload) + +typedef struct { + struct cx_list_s base; + cx_linked_list_node *begin; + cx_linked_list_node *end; +} cx_linked_list; + +static cx_linked_list_node *cx_ll_node_at( + const cx_linked_list *list, + size_t index +) { + if (index >= list->base.collection.size) { + return NULL; + } else if (index > list->base.collection.size / 2) { + return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index); + } else { + return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index); + } +} + +static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) { + return cxMalloc(list->collection.allocator, + sizeof(cx_linked_list_node) + list->collection.elem_size); +} + +static int cx_ll_insert_at( + struct cx_list_s *list, + cx_linked_list_node *node, + const void *elem +) { + + // create the new new_node + cx_linked_list_node *new_node = cx_ll_malloc_node(list); + + // sortir if failed + if (new_node == NULL) return 1; + + // initialize new new_node + new_node->prev = new_node->next = NULL; + memcpy(new_node->payload, elem, list->collection.elem_size); + + // insert + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_insert_chain( + (void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, + node, new_node, new_node + ); + + // increase the size and return + list->collection.size++; + return 0; +} + +static size_t cx_ll_insert_array( + struct cx_list_s *list, + size_t index, + const void *array, + size_t n +) { + // out-of bounds and corner case check + if (index > list->collection.size || n == 0) return 0; + + // find position efficiently + cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + + // perform first insert + if (0 != cx_ll_insert_at(list, node, array)) return 1; + + // is there more? + if (n == 1) return 1; + + // we now know exactly where we are + node = node == NULL ? ((cx_linked_list *) list)->begin : node->next; + + // we can add the remaining nodes and immediately advance to the inserted node + const char *source = array; + for (size_t i = 1; i < n; i++) { + source += list->collection.elem_size; + if (0 != cx_ll_insert_at(list, node, source)) return i; + node = node->next; + } + return n; +} + +static int cx_ll_insert_element( + struct cx_list_s *list, + size_t index, + const void *element +) { + return 1 != cx_ll_insert_array(list, index, element, 1); +} + +static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; + +static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { + const cx_linked_list_node *left = l; + const cx_linked_list_node *right = r; + return cx_ll_insert_sorted_cmp_func(left->payload, right->payload); +} + +static size_t cx_ll_insert_sorted( + struct cx_list_s *list, + const void *array, + size_t n +) { + // special case + if (n == 0) return 0; + + // create a new chain of nodes + cx_linked_list_node *chain = cx_ll_malloc_node(list); + if (chain == NULL) return 0; + + memcpy(chain->payload, array, list->collection.elem_size); + chain->prev = NULL; + chain->next = NULL; + + // add all elements from the array to that chain + cx_linked_list_node *prev = chain; + const char *src = array; + size_t inserted = 1; + for (; inserted < n; inserted++) { + cx_linked_list_node *next = cx_ll_malloc_node(list); + if (next == NULL) break; + src += list->collection.elem_size; + memcpy(next->payload, src, list->collection.elem_size); + prev->next = next; + next->prev = prev; + prev = next; + } + prev->next = NULL; + + // invoke the low level function + cx_linked_list *ll = (cx_linked_list *) list; + cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; + cx_linked_list_insert_sorted_chain( + (void **) &ll->begin, + (void **) &ll->end, + CX_LL_LOC_PREV, + CX_LL_LOC_NEXT, + chain, + cx_ll_insert_sorted_cmp_helper + ); + + // adjust the list metadata + list->collection.size += inserted; + + return inserted; +} + +static size_t cx_ll_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_node *node = cx_ll_node_at(ll, index); + + // out-of-bounds check + if (node == NULL) return 0; + + // remove + size_t removed = cx_linked_list_remove_chain( + (void **) &ll->begin, + (void **) &ll->end, + CX_LL_LOC_PREV, + CX_LL_LOC_NEXT, + node, + num + ); + + // adjust size + list->collection.size -= removed; + + // copy or destroy the removed chain + if (targetbuf == NULL) { + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // element destruction + cx_invoke_destructor(list, n->payload); + + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } else { + char *dest = targetbuf; + cx_linked_list_node *n = node; + for (size_t i = 0; i < removed; i++) { + // copy payload + memcpy(dest, n->payload, list->collection.elem_size); + + // advance target buffer + dest += list->collection.elem_size; + + // free the node and advance + void *next = n->next; + cxFree(list->collection.allocator, n); + n = next; + } + } + + return removed; +} + +static void cx_ll_clear(struct cx_list_s *list) { + if (list->collection.size == 0) return; + + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_node *node = ll->begin; + while (node != NULL) { + cx_invoke_destructor(list, node->payload); + cx_linked_list_node *next = node->next; + cxFree(list->collection.allocator, node); + node = next; + } + ll->begin = ll->end = NULL; + list->collection.size = 0; +} + +#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE +#define CX_LINKED_LIST_SWAP_SBO_SIZE 128 +#endif +const unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; + +static int cx_ll_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + if (i >= list->collection.size || j >= list->collection.size) return 1; + if (i == j) return 0; + + // perform an optimized search that finds both elements in one run + cx_linked_list *ll = (cx_linked_list *) list; + size_t mid = list->collection.size / 2; + size_t left, right; + if (i < j) { + left = i; + right = j; + } else { + left = j; + right = i; + } + cx_linked_list_node *nleft = NULL, *nright = NULL; + if (left < mid && right < mid) { + // case 1: both items left from mid + nleft = cx_ll_node_at(ll, left); + assert(nleft != NULL); + nright = nleft; + for (size_t c = left; c < right; c++) { + nright = nright->next; + } + } else if (left >= mid && right >= mid) { + // case 2: both items right from mid + nright = cx_ll_node_at(ll, right); + assert(nright != NULL); + nleft = nright; + for (size_t c = right; c > left; c--) { + nleft = nleft->prev; + } + } else { + // case 3: one item left, one item right + + // chose the closest to begin / end + size_t closest; + size_t other; + size_t diff2boundary = list->collection.size - right - 1; + if (left <= diff2boundary) { + closest = left; + other = right; + nleft = cx_ll_node_at(ll, left); + } else { + closest = right; + other = left; + diff2boundary = left; + nright = cx_ll_node_at(ll, right); + } + + // is other element closer to us or closer to boundary? + if (right - left <= diff2boundary) { + // search other element starting from already found element + if (closest == left) { + nright = nleft; + for (size_t c = left; c < right; c++) { + nright = nright->next; + } + } else { + nleft = nright; + for (size_t c = right; c > left; c--) { + nleft = nleft->prev; + } + } + } else { + // search other element starting at the boundary + if (closest == left) { + nright = cx_ll_node_at(ll, other); + } else { + nleft = cx_ll_node_at(ll, other); + } + } + } + + if (list->collection.elem_size > CX_LINKED_LIST_SWAP_SBO_SIZE) { + cx_linked_list_node *prev = nleft->prev; + cx_linked_list_node *next = nright->next; + cx_linked_list_node *midstart = nleft->next; + cx_linked_list_node *midend = nright->prev; + + if (prev == NULL) { + ll->begin = nright; + } else { + prev->next = nright; + } + nright->prev = prev; + if (midstart == nright) { + // special case: both nodes are adjacent + nright->next = nleft; + nleft->prev = nright; + } else { + // likely case: a chain is between the two nodes + nright->next = midstart; + midstart->prev = nright; + midend->next = nleft; + nleft->prev = midend; + } + nleft->next = next; + if (next == NULL) { + ll->end = nleft; + } else { + next->prev = nleft; + } + } else { + // swap payloads to avoid relinking + char buf[CX_LINKED_LIST_SWAP_SBO_SIZE]; + memcpy(buf, nleft->payload, list->collection.elem_size); + memcpy(nleft->payload, nright->payload, list->collection.elem_size); + memcpy(nright->payload, buf, list->collection.elem_size); + } + + return 0; +} + +static void *cx_ll_at( + const struct cx_list_s *list, + size_t index +) { + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_node *node = cx_ll_node_at(ll, index); + return node == NULL ? NULL : node->payload; +} + +static ssize_t cx_ll_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + if (remove) { + cx_linked_list *ll = ((cx_linked_list *) list); + cx_linked_list_node *node; + ssize_t index = cx_linked_list_find_node( + (void **) &node, + ll->begin, + CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc, elem + ); + if (node != NULL) { + cx_invoke_destructor(list, node->payload); + cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + list->collection.size--; + cxFree(list->collection.allocator, node); + } + return index; + } else { + return cx_linked_list_find( + ((cx_linked_list *) list)->begin, + CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc, elem + ); + } +} + +static void cx_ll_sort(struct cx_list_s *list) { + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc); +} + +static void cx_ll_reverse(struct cx_list_s *list) { + cx_linked_list *ll = (cx_linked_list *) list; + cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT); +} + +static int cx_ll_compare( + const struct cx_list_s *list, + const struct cx_list_s *other +) { + cx_linked_list *left = (cx_linked_list *) list; + cx_linked_list *right = (cx_linked_list *) other; + return cx_linked_list_compare(left->begin, right->begin, + CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc); +} + +static bool cx_ll_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle != NULL; +} + +static void cx_ll_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + struct cx_list_s *list = iter->src_handle.m; + cx_linked_list *ll = iter->src_handle.m; + cx_linked_list_node *node = iter->elem_handle; + iter->elem_handle = node->next; + cx_invoke_destructor(list, node->payload); + cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + list->collection.size--; + cxFree(list->collection.allocator, node); + } else { + iter->index++; + cx_linked_list_node *node = iter->elem_handle; + iter->elem_handle = node->next; + } +} + +static void cx_ll_iter_prev(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + iter->base.remove = false; + struct cx_list_s *list = iter->src_handle.m; + cx_linked_list *ll = iter->src_handle.m; + cx_linked_list_node *node = iter->elem_handle; + iter->elem_handle = node->prev; + iter->index--; + cx_invoke_destructor(list, node->payload); + cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + list->collection.size--; + cxFree(list->collection.allocator, node); + } else { + iter->index--; + cx_linked_list_node *node = iter->elem_handle; + iter->elem_handle = node->prev; + } +} + +static void *cx_ll_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + cx_linked_list_node *node = iter->elem_handle; + return node->payload; +} + +static CxIterator cx_ll_iterator( + const struct cx_list_s *list, + size_t index, + bool backwards +) { + CxIterator iter; + iter.index = index; + iter.src_handle.c = list; + iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index); + iter.elem_size = list->collection.elem_size; + iter.elem_count = list->collection.size; + iter.base.valid = cx_ll_iter_valid; + iter.base.current = cx_ll_iter_current; + iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next; + iter.base.mutating = false; + iter.base.remove = false; + return iter; +} + +static int cx_ll_insert_iter( + CxIterator *iter, + const void *elem, + int prepend +) { + struct cx_list_s *list = iter->src_handle.m; + cx_linked_list_node *node = iter->elem_handle; + if (node != NULL) { + assert(prepend >= 0 && prepend <= 1); + cx_linked_list_node *choice[2] = {node, node->prev}; + int result = cx_ll_insert_at(list, choice[prepend], elem); + if (result == 0) { + iter->elem_count++; + if (prepend) { + iter->index++; + } + } + return result; + } else { + int result = cx_ll_insert_element(list, list->collection.size, elem); + if (result == 0) { + iter->elem_count++; + iter->index = list->collection.size; + } + return result; + } +} + +static void cx_ll_destructor(CxList *list) { + cx_linked_list *ll = (cx_linked_list *) list; + + cx_linked_list_node *node = ll->begin; + while (node) { + cx_invoke_destructor(list, node->payload); + void *next = node->next; + cxFree(list->collection.allocator, node); + node = next; + } + + cxFree(list->collection.allocator, list); +} + +static cx_list_class cx_linked_list_class = { + cx_ll_destructor, + cx_ll_insert_element, + cx_ll_insert_array, + cx_ll_insert_sorted, + cx_ll_insert_iter, + cx_ll_remove, + cx_ll_clear, + cx_ll_swap, + cx_ll_at, + cx_ll_find_remove, + cx_ll_sort, + cx_ll_compare, + cx_ll_reverse, + cx_ll_iterator, +}; + +CxList *cxLinkedListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); + if (list == NULL) return NULL; + + list->base.cl = &cx_linked_list_class; + list->base.collection.allocator = allocator; + + if (elem_size > 0) { + list->base.collection.elem_size = elem_size; + list->base.collection.cmpfunc = comparator; + } else { + list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator; + cxListStorePointers((CxList *) list); + } + + return (CxList *) list; +} diff --git a/ucx/list.c b/ucx/list.c index 293592c..8fb18e6 100644 --- a/ucx/list.c +++ b/ucx/list.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,403 +26,463 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx/list.h" +#include "cx/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); +#include + +// + +static _Thread_local cx_compare_func cx_pl_cmpfunc_impl; + +static int cx_pl_cmpfunc( + const void *l, + const void *r +) { + void *const *lptr = l; + void *const *rptr = r; + const void *left = lptr == NULL ? NULL : *lptr; + const void *right = rptr == NULL ? NULL : *rptr; + return cx_pl_cmpfunc_impl(left, right); } -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; +static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) { + // cast away const - this is the hacky thing + struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; + cx_pl_cmpfunc_impl = l->cmpfunc; + l->cmpfunc = cx_pl_cmpfunc; } -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); +static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) { + // cast away const - this is the hacky thing + struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; + l->cmpfunc = cx_pl_cmpfunc_impl; } -void ucx_list_free(UcxList *l) { - ucx_list_free_a(ucx_default_allocator(), l); +static void cx_pl_destructor(struct cx_list_s *list) { + list->climpl->deallocate(list); } -void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) { - UcxList *e = l, *f; - while (e != NULL) { - f = e; - e = e->next; - alfree(alloc, f); - } +static int cx_pl_insert_element( + struct cx_list_s *list, + size_t index, + const void *element +) { + return list->climpl->insert_element(list, index, &element); } -void ucx_list_free_content(UcxList* list, ucx_destructor destr) { - if (!destr) destr = free; - while (list != NULL) { - destr(list->data); - list = list->next; - } +static size_t cx_pl_insert_array( + struct cx_list_s *list, + size_t index, + const void *array, + size_t n +) { + return list->climpl->insert_array(list, index, array, n); } -UcxList *ucx_list_append(UcxList *l, void *data) { - return ucx_list_append_a(ucx_default_allocator(), l, data); +static size_t cx_pl_insert_sorted( + struct cx_list_s *list, + const void *array, + size_t n +) { + cx_pl_hack_cmpfunc(list); + size_t result = list->climpl->insert_sorted(list, array, n); + cx_pl_unhack_cmpfunc(list); + return result; } -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; - } +static int cx_pl_insert_iter( + struct cx_iterator_s *iter, + const void *elem, + int prepend +) { + struct cx_list_s *list = iter->src_handle.m; + return list->climpl->insert_iter(iter, &elem, prepend); } -UcxList *ucx_list_prepend(UcxList *l, void *data) { - return ucx_list_prepend_a(ucx_default_allocator(), l, data); +static size_t cx_pl_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + return list->climpl->remove(list, index, num, targetbuf); } -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; +static void cx_pl_clear(struct cx_list_s *list) { + list->climpl->clear(list); } -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; - } +static int cx_pl_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + return list->climpl->swap(list, i, j); } -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; +static void *cx_pl_at( + const struct cx_list_s *list, + size_t index +) { + void **ptr = list->climpl->at(list, index); + return ptr == NULL ? NULL : *ptr; } -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; +static ssize_t cx_pl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + cx_pl_hack_cmpfunc(list); + ssize_t ret = list->climpl->find_remove(list, &elem, remove); + cx_pl_unhack_cmpfunc(list); + return ret; } -UcxList *ucx_list_get(const UcxList *l, size_t index) { - if (l == NULL) return NULL; +static void cx_pl_sort(struct cx_list_s *list) { + cx_pl_hack_cmpfunc(list); + list->climpl->sort(list); + cx_pl_unhack_cmpfunc(list); +} - const UcxList *e = l; - while (e->next && index > 0) { - e = e->next; - index--; - } - - return (UcxList*)(index == 0 ? e : NULL); +static int cx_pl_compare( + const struct cx_list_s *list, + const struct cx_list_s *other +) { + cx_pl_hack_cmpfunc(list); + int ret = list->climpl->compare(list, other); + cx_pl_unhack_cmpfunc(list); + return ret; } -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; +static void cx_pl_reverse(struct cx_list_s *list) { + list->climpl->reverse(list); +} + +static void *cx_pl_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + void **ptr = iter->base.current_impl(it); + return ptr == NULL ? NULL : *ptr; } -int ucx_list_contains(const UcxList *l, void *elem, - cmp_func fnc, void *cmpdata) { - return ucx_list_find(l, elem, fnc, cmpdata) > -1; +static struct cx_iterator_s cx_pl_iterator( + const struct cx_list_s *list, + size_t index, + bool backwards +) { + struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards); + iter.base.current_impl = iter.base.current; + iter.base.current = cx_pl_iter_current; + return iter; } -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++; +static cx_list_class cx_pointer_list_class = { + cx_pl_destructor, + cx_pl_insert_element, + cx_pl_insert_array, + cx_pl_insert_sorted, + cx_pl_insert_iter, + cx_pl_remove, + cx_pl_clear, + cx_pl_swap, + cx_pl_at, + cx_pl_find_remove, + cx_pl_sort, + cx_pl_compare, + cx_pl_reverse, + cx_pl_iterator, +}; + +void cxListStoreObjects(CxList *list) { + list->collection.store_pointer = false; + if (list->climpl != NULL) { + list->cl = list->climpl; + list->climpl = NULL; } +} - return s; +void cxListStorePointers(CxList *list) { + list->collection.elem_size = sizeof(void *); + list->collection.store_pointer = true; + list->climpl = list->cl; + list->cl = &cx_pointer_list_class; } -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++; - } +static void cx_emptyl_noop(cx_attr_unused CxList *list) { + // this is a noop, but MUST be implemented +} - // 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; +static void *cx_emptyl_at( + cx_attr_unused const struct cx_list_s *list, + cx_attr_unused size_t index +) { + return NULL; +} - UcxList *ret = sorted[0]; - free(sorted); - return ret; +static ssize_t cx_emptyl_find_remove( + cx_attr_unused struct cx_list_s *list, + cx_attr_unused const void *elem, + cx_attr_unused bool remove +) { + return -1; } -UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) { - if (l == NULL) { - return NULL; - } +static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { + return false; +} - UcxList *lc; - size_t ln = 1; +static CxIterator cx_emptyl_iterator( + const struct cx_list_s *list, + size_t index, + cx_attr_unused bool backwards +) { + CxIterator iter = {0}; + iter.src_handle.c = list; + iter.index = index; + iter.base.valid = cx_emptyl_iter_valid; + return iter; +} - 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++; +static cx_list_class cx_empty_list_class = { + cx_emptyl_noop, + NULL, + NULL, + NULL, + NULL, + NULL, + cx_emptyl_noop, + NULL, + cx_emptyl_at, + cx_emptyl_find_remove, + cx_emptyl_noop, + NULL, + cx_emptyl_noop, + cx_emptyl_iterator, +}; + +CxList cx_empty_list = { + { + NULL, + NULL, + 0, + 0, + NULL, + NULL, + NULL, + false + }, + &cx_empty_list_class, + NULL +}; + +CxList *const cxEmptyList = &cx_empty_list; + +// + +#define invoke_list_func(name, list, ...) \ + ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \ + (list, __VA_ARGS__) + +size_t cx_list_default_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +) { + size_t elem_size = list->collection.elem_size; + const char *src = data; + size_t i = 0; + for (; i < n; i++) { + if (0 != invoke_list_func( + insert_element, list, index + i, + src + (i * elem_size))) return i; } - le = lc->next; + return i; +} - 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++; +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // corner case + if (n == 0) return 0; + + size_t elem_size = list->collection.elem_size; + cx_compare_func cmp = list->collection.cmpfunc; + const char *src = sorted_data; + + // track indices and number of inserted items + size_t di = 0, si = 0, inserted = 0; + + // search the list for insertion points + for (; di < list->collection.size; di++) { + const void *list_elm = invoke_list_func(at, list, di); + + // compare current list element with first source element + // if less or equal, skip + if (cmp(list_elm, src) <= 0) { + continue; } - 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); + + // determine number of consecutive elements that can be inserted + size_t ins = 1; + const char *next = src; + while (++si < n) { + next += elem_size; + // once we become larger than the list elem, break + if (cmp(list_elm, next) <= 0) { + break; + } + // otherwise, we can insert one more + ins++; + } + + // insert the elements at location si + if (ins == 1) { + if (0 != invoke_list_func( + insert_element, list, di, src)) return inserted; } else { - // no remainder - we've got our sorted list - l = sorted; + size_t r = invoke_list_func(insert_array, list, di, src, ins); + if (r < ins) return inserted + r; } + inserted += ins; + di += ins; - return l; + // everything inserted? + if (inserted == n) return inserted; + src = next; } + + // insert remaining items + if (si < n) { + inserted += invoke_list_func(insert_array, list, di, src, n - si); + } + + return inserted; } -UcxList *ucx_list_first(const UcxList *l) { - if (!l) { - return NULL; +void cx_list_default_sort(struct cx_list_s *list) { + size_t elem_size = list->collection.elem_size; + size_t list_size = list->collection.size; + void *tmp = malloc(elem_size * list_size); + if (tmp == NULL) abort(); + + // copy elements from source array + char *loc = tmp; + for (size_t i = 0; i < list_size; i++) { + void *src = invoke_list_func(at, list, i); + memcpy(loc, src, elem_size); + loc += elem_size; } - - const UcxList *e = l; - while (e->prev) { - e = e->prev; + + // qsort + qsort(tmp, list_size, elem_size, + list->collection.cmpfunc); + + // copy elements back + loc = tmp; + for (size_t i = 0; i < list_size; i++) { + void *dest = invoke_list_func(at, list, i); + memcpy(dest, loc, elem_size); + loc += elem_size; } - return (UcxList *)e; + + free(tmp); } -UcxList *ucx_list_remove(UcxList *l, UcxList *e) { - return ucx_list_remove_a(ucx_default_allocator(), l, e); +int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { + if (i == j) return 0; + if (i >= list->collection.size) return 1; + if (j >= list->collection.size) return 1; + + size_t elem_size = list->collection.elem_size; + + void *tmp = malloc(elem_size); + if (tmp == NULL) return 1; + + void *ip = invoke_list_func(at, list, i); + void *jp = invoke_list_func(at, list, j); + + memcpy(tmp, ip, elem_size); + memcpy(ip, jp, elem_size); + memcpy(jp, tmp, elem_size); + + free(tmp); + + return 0; } - -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; + +int cxListCompare( + const CxList *list, + const CxList *other +) { + bool cannot_optimize = false; + + // if one is storing pointers but the other is not + cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer; + + // if one class is wrapped but the other is not + cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL); + + // if the compare functions do not match or both are NULL + if (!cannot_optimize) { + cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ? + list->climpl->compare : list->cl->compare); + cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ? + other->climpl->compare : other->cl->compare); + cannot_optimize |= list_cmp != other_cmp; + cannot_optimize |= list_cmp == NULL; } - - 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 (cannot_optimize) { + // lists are definitely different - cannot use internal compare function + if (list->collection.size == other->collection.size) { + CxIterator left = list->cl->iterator(list, 0, false); + CxIterator right = other->cl->iterator(other, 0, false); + for (size_t i = 0; i < list->collection.size; i++) { + void *leftValue = cxIteratorCurrent(left); + void *rightValue = cxIteratorCurrent(right); + int d = list->collection.cmpfunc(leftValue, rightValue); + if (d != 0) { + return d; } - if (cur != NULL) - cur->next = nl; - cur = nl; - if (res == NULL) - res = cur; + cxIteratorNext(left); + cxIteratorNext(right); } + return 0; + } else { + return list->collection.size < other->collection.size ? -1 : 1; } - 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); + } else { + // lists are compatible + return list->cl->compare(list, other); + } +} + +CxIterator cxListMutIteratorAt( + CxList *list, + size_t index +) { + CxIterator it = list->cl->iterator(list, index, false); + it.base.mutating = true; + return it; +} + +CxIterator cxListMutBackwardsIteratorAt( + CxList *list, + size_t index +) { + CxIterator it = list->cl->iterator(list, index, true); + it.base.mutating = true; + return it; +} + +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); } diff --git a/ucx/map.c b/ucx/map.c index ba7961d..8a34dcc 100644 --- a/ucx/map.c +++ b/ucx/map.c @@ -1,402 +1,107 @@ -/* - * 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 +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/map.h" +#include + +// + +static void cx_empty_map_noop(cx_attr_unused CxMap *map) { + // this is a noop, but MUST be implemented +} + +static void *cx_empty_map_get( + cx_attr_unused const CxMap *map, + cx_attr_unused CxHashKey key +) { + return NULL; +} + +static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) { + return false; +} + +static CxIterator cx_empty_map_iterator( + const struct cx_map_s *map, + cx_attr_unused enum cx_map_iterator_type type +) { + CxIterator iter = {0}; + iter.src_handle.c = map; + iter.base.valid = cx_empty_map_iter_valid; + return iter; +} + +static struct cx_map_class_s cx_empty_map_class = { + cx_empty_map_noop, + cx_empty_map_noop, + NULL, + cx_empty_map_get, + NULL, + cx_empty_map_iterator +}; + +CxMap cx_empty_map = { + { + NULL, + NULL, + 0, + 0, + NULL, + NULL, + NULL, + false + }, + &cx_empty_map_class +}; + +CxMap *const cxEmptyMap = &cx_empty_map; + +// + +CxIterator cxMapMutIteratorValues(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); + it.base.mutating = true; + return it; +} + +CxIterator cxMapMutIteratorKeys(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); + it.base.mutating = true; + return it; +} + +CxIterator cxMapMutIterator(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); + it.base.mutating = true; + return it; +} + +void cxMapFree(CxMap *map) { + if (map == NULL) return; + map->cl->deallocate(map); +} diff --git a/ucx/mempool.c b/ucx/mempool.c index beedc31..fea4d70 100644 --- a/ucx/mempool.c +++ b/ucx/mempool.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,212 +26,212 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx/mempool.h" +#include "cx/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)) { +#include + +struct cx_mempool_memory_s { + /** The destructor. */ + cx_destructor_func destructor; + /** The actual memory. */ + char c[]; +}; + +static void *cx_mempool_malloc( + void *p, + size_t n +) { + struct cx_mempool_s *pool = p; + + if (pool->size >= pool->capacity) { + size_t newcap = pool->capacity - (pool->capacity % 16) + 16; + size_t newmsize; + if (pool->capacity > newcap || cx_szmul(newcap, + sizeof(struct cx_mempool_memory_s*), &newmsize)) { + errno = EOVERFLOW; return NULL; } + struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize); + if (newdata == NULL) return NULL; + pool->data = newdata; + pool->capacity = newcap; } - void *p = malloc(sizeof(ucx_destructor) + n); - ucx_memchunk *mem = (ucx_memchunk*)p; - if (!mem) { - return NULL; - } + struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n); + if (mem == NULL) return NULL; - mem->destructor = NULL; - pool->data[pool->ndata] = mem; - pool->ndata++; + mem->destructor = pool->auto_destr; + pool->data[pool->size] = mem; + pool->size++; - return &(mem->c); + return mem->c; } -void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) { +static void *cx_mempool_calloc( + void *p, + 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) { + if (cx_szmul(nelem, elsize, &msz)) { + errno = EOVERFLOW; return NULL; } + void *ptr = cx_mempool_malloc(p, msz); + if (ptr == NULL) 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; - } +static void *cx_mempool_realloc( + void *p, + void *ptr, + size_t n +) { + struct cx_mempool_s *pool = p; + + struct cx_mempool_memory_s *mem, *newm; + mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); + newm = realloc(mem, n + sizeof(cx_destructor_func)); + + if (newm == NULL) return NULL; if (mem != newm) { - for(size_t i=0 ; i < pool->ndata ; i++) { - if(pool->data[i] == mem) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == mem) { pool->data[i] = newm; - return newm + sizeof(ucx_destructor); + return ((char*)newm) + sizeof(cx_destructor_func); } } - fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", - (intptr_t)ptr, (intptr_t)pool); - abort(); + abort(); // LCOV_EXCL_LINE } else { - return newm + sizeof(ucx_destructor); + return ptr; } } -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)); +static void cx_mempool_free( + void *p, + void *ptr +) { + if (!ptr) return; + struct cx_mempool_s *pool = p; + + struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *) + ((char *) ptr - sizeof(cx_destructor_func)); + + for (size_t i = 0; i < pool->size; i++) { + if (mem == pool->data[i]) { + if (mem->destructor) { + mem->destructor(mem->c); } - free(chunk); - size_t last_index = pool->ndata - 1; - if(i != last_index) { + free(mem); + size_t last_index = pool->size - 1; + if (i != last_index) { pool->data[i] = pool->data[last_index]; pool->data[last_index] = NULL; } - pool->ndata--; + pool->size--; return; } } - fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", - (intptr_t)ptr, (intptr_t)pool); - abort(); + abort(); // LCOV_EXCL_LINE } -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); +void cxMempoolFree(CxMempool *pool) { + if (pool == NULL) return; + struct cx_mempool_memory_s *mem; + for (size_t i = 0; i < pool->size; i++) { + mem = pool->data[i]; + if (mem->destructor) { + mem->destructor(mem->c); } + free(mem); } free(pool->data); - free(pool->allocator); + free((void*) pool->allocator); free(pool); } -void ucx_mempool_set_destr(void *ptr, ucx_destructor func) { - *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func; +void cxMempoolSetDestructor( + void *ptr, + cx_destructor_func func +) { + *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } -void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) { - ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc( +void cxMempoolRemoveDestructor(void *ptr) { + *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; +} + +struct cx_mempool_foreign_mem_s { + cx_destructor_func destr; + void* mem; +}; + +static void cx_mempool_destr_foreign_mem(void* ptr) { + struct cx_mempool_foreign_mem_s *fm = ptr; + fm->destr(fm->mem); +} + +int cxMempoolRegister( + CxMempool *pool, + void *memory, + cx_destructor_func destr +) { + struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc( pool, - sizeof(ucx_regdestr)); - rd->destructor = destr; - rd->ptr = ptr; - ucx_mempool_set_destr(rd, ucx_mempool_shared_destr); + sizeof(struct cx_mempool_foreign_mem_s) + ); + if (fm == NULL) return 1; + + fm->mem = memory; + fm->destr = destr; + *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem; + + return 0; } +static cx_allocator_class cx_mempool_allocator_class = { + cx_mempool_malloc, + cx_mempool_realloc, + cx_mempool_calloc, + cx_mempool_free +}; + +CxMempool *cxMempoolCreate( + size_t capacity, + cx_destructor_func destr +) { + size_t poolsize; + if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { + errno = EOVERFLOW; + return NULL; + } + + struct cx_mempool_s *pool = + malloc(sizeof(struct cx_mempool_s)); + if (pool == NULL) return NULL; + + CxAllocator *provided_allocator = malloc(sizeof(CxAllocator)); + if (provided_allocator == NULL) { // LCOV_EXCL_START + free(pool); + return NULL; + } // LCOV_EXCL_STOP + provided_allocator->cl = &cx_mempool_allocator_class; + provided_allocator->data = pool; + + pool->allocator = provided_allocator; + + pool->data = malloc(poolsize); + if (pool->data == NULL) { // LCOV_EXCL_START + free(provided_allocator); + free(pool); + return NULL; + } // LCOV_EXCL_STOP + + pool->size = 0; + pool->capacity = capacity; + pool->auto_destr = destr; + + return pool; +} diff --git a/ucx/printf.c b/ucx/printf.c new file mode 100644 index 0000000..bf691cf --- /dev/null +++ b/ucx/printf.c @@ -0,0 +1,194 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/printf.h" + +#include +#include + +#ifndef CX_PRINTF_SBO_SIZE +#define CX_PRINTF_SBO_SIZE 512 +#endif +const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; + +int cx_fprintf( + void *stream, + cx_write_func wfc, + const char *fmt, + ... +) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = cx_vfprintf(stream, wfc, fmt, ap); + va_end(ap); + return ret; +} + +int cx_vfprintf( + void *stream, + cx_write_func wfc, + const char *fmt, + va_list ap +) { + char buf[CX_PRINTF_SBO_SIZE]; + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap); + if (ret < 0) { + va_end(ap2); + return ret; + } else if (ret < CX_PRINTF_SBO_SIZE) { + va_end(ap2); + return (int) wfc(buf, 1, ret, stream); + } else { + int len = ret + 1; + char *newbuf = malloc(len); + if (!newbuf) { // LCOV_EXCL_START + va_end(ap2); + return -1; + } // LCOV_EXCL_STOP + + ret = vsnprintf(newbuf, len, fmt, ap2); + va_end(ap2); + if (ret > 0) { + ret = (int) wfc(newbuf, 1, ret, stream); + } + free(newbuf); + } + return ret; +} + +cxmutstr cx_asprintf_a( + const CxAllocator *allocator, + const char *fmt, + ... +) { + va_list ap; + va_start(ap, fmt); + cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap); + va_end(ap); + return ret; +} + +cxmutstr cx_vasprintf_a( + const CxAllocator *a, + const char *fmt, + va_list ap +) { + cxmutstr s; + s.ptr = NULL; + s.length = 0; + char buf[CX_PRINTF_SBO_SIZE]; + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap); + if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) { + s.ptr = cxMalloc(a, ret + 1); + if (s.ptr) { + s.length = (size_t) ret; + memcpy(s.ptr, buf, ret); + s.ptr[s.length] = '\0'; + } + } else { + int len = ret + 1; + s.ptr = cxMalloc(a, len); + if (s.ptr) { + ret = vsnprintf(s.ptr, len, fmt, ap2); + if (ret < 0) { + free(s.ptr); + s.ptr = NULL; + } else { + s.length = (size_t) ret; + } + } + } + va_end(ap2); + return s; +} + +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) { + va_list ap; + va_start(ap, fmt); + int ret = cx_vsprintf_a(alloc, str, len, fmt, ap); + va_end(ap); + return ret; +} + +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(*str, *len, fmt, ap); + if ((unsigned) ret >= *len) { + unsigned newlen = ret + 1; + char *ptr = cxRealloc(alloc, *str, newlen); + if (ptr) { + int newret = vsnprintf(ptr, newlen, fmt, ap2); + if (newret < 0) { + cxFree(alloc, ptr); + } else { + *len = newlen; + *str = ptr; + ret = newret; + } + } + } + va_end(ap2); + return ret; +} + +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) { + va_list ap; + va_start(ap, fmt); + int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap); + va_end(ap); + return ret; +} + +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, *len, fmt, ap); + *str = buf; + if ((unsigned) ret >= *len) { + unsigned newlen = ret + 1; + char *ptr = cxMalloc(alloc, newlen); + if (ptr) { + int newret = vsnprintf(ptr, newlen, fmt, ap2); + if (newret < 0) { + cxFree(alloc, ptr); + } else { + *len = newlen; + *str = ptr; + ret = newret; + } + } + } + va_end(ap2); + return ret; +} diff --git a/ucx/properties.c b/ucx/properties.c index 1cb4de0..265d0c3 100644 --- a/ucx/properties.c +++ b/ucx/properties.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,239 +26,381 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx/properties.h" +#include "cx/properties.h" -#include -#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; +const CxPropertiesConfig cx_properties_config_default = { + '=', + //'\\', + '#', + '\0', + '\0' +}; + +void cxPropertiesInit( + CxProperties *prop, + CxPropertiesConfig config +) { + memset(prop, 0, sizeof(CxProperties)); + prop->config = config; +} + +void cxPropertiesDestroy(CxProperties *prop) { + cxBufferDestroy(&prop->input); + cxBufferDestroy(&prop->buffer); } -void ucx_properties_free(UcxProperties *parser) { - if(parser->tmp) { - free(parser->tmp); +int cxPropertiesFilln( + CxProperties *prop, + const char *buf, + size_t len +) { + if (cxBufferEof(&prop->input)) { + // destroy a possible previously initialized buffer + cxBufferDestroy(&prop->input); + cxBufferInit(&prop->input, (void*) buf, len, + NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + prop->input.size = len; + } else { + if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; } - free(parser); + return 0; } -void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) { - parser->buffer = buf; - parser->buflen = len; - parser->pos = 0; +void cxPropertiesUseStack( + CxProperties *prop, + char *buf, + size_t capacity +) { + cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); } -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; +CxPropertiesStatus cxPropertiesNext( + CxProperties *prop, + cxstring *key, + cxstring *value +) { + // check if we have a text buffer + if (prop->input.space == NULL) { + return CX_PROPERTIES_NULL_INPUT; } - 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); + // a pointer to the buffer we want to read from + CxBuffer *current_buffer = &prop->input; + + // check if we have rescued data + if (!cxBufferEof(&prop->buffer)) { + // check if we can now get a complete line + cxstring input = cx_strn(prop->input.space + prop->input.pos, + prop->input.size - prop->input.pos); + cxstring nl = cx_strchr(input, '\n'); + if (nl.length > 0) { + // we add as much data to the rescue buffer as we need + // to complete the line + size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; + + if (cxBufferAppend(input.ptr, 1, + len_until_nl, &prop->buffer) < len_until_nl) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + + // advance the position in the input buffer + prop->input.pos += len_until_nl; + + // we now want to read from the rescue buffer + current_buffer = &prop->buffer; } else { - parser_tmp_append(parser, buf, len); - return 0; + // still not enough data, copy input buffer to internal buffer + if (cxBufferAppend(input.ptr, 1, + input.length, &prop->buffer) < input.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; } - } 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; - + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.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; - + while (!cxBufferEof(current_buffer)) { + const char *buf = current_buffer->space + current_buffer->pos; + size_t len = current_buffer->size - current_buffer->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; + bool has_comment = false; 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; + if (c != '\n') { + // we don't have enough data for a line, use the rescue buffer + assert(current_buffer != &prop->buffer); + // make sure that the rescue buffer does not already contain something + assert(cxBufferEof(&prop->buffer)); + if (prop->buffer.space == NULL) { + // initialize a rescue buffer, if the user did not provide one + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } else { + // from a previous rescue there might be already read data + // reset the buffer to avoid unnecessary buffer extension + cxBufferReset(&prop->buffer); + } + if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); + return CX_PROPERTIES_INCOMPLETE_DATA; } - - sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i); + + cxstring line = has_comment ? + cx_strn(buf, comment_index) : + cx_strn(buf, i); // check line - if(delimiter_index == 0) { - line = sstrtrim(line); - if(line.length != 0) { - parser->error = 1; + if (delimiter_index == 0) { + // if line is not blank ... + line = cx_strtrim(line); + // ... either no delimiter found, or key is empty + if (line.length > 0) { + if (line.ptr[0] == delimiter) { + return CX_PROPERTIES_INVALID_EMPTY_KEY; + } else { + return CX_PROPERTIES_INVALID_MISSING_DELIMITER; + } + } else { + // skip blank line + // if it was the rescue buffer, return to the original buffer + if (current_buffer == &prop->buffer) { + // assert that the rescue buffer really does not contain more data + assert(current_buffer->pos + i + 1 == current_buffer->size); + // reset the rescue buffer, but don't destroy it! + cxBufferReset(&prop->buffer); + // continue with the input buffer + current_buffer = &prop->input; + } else { + // if it was the input buffer already, just advance the position + current_buffer->pos += i + 1; + } + continue; } } else { - sstr_t n = sstrn(buf, delimiter_index); - sstr_t v = sstrn( + cxstring k = cx_strn(buf, delimiter_index); + cxstring val = cx_strn( 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; + line.length - delimiter_index - 1); + k = cx_strtrim(k); + val = cx_strtrim(val); + if (k.length > 0) { + *key = k; + *value = val; + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; } else { - parser->error = 1; + return CX_PROPERTIES_INVALID_EMPTY_KEY; } } - - parser->pos += i + 1; + // unreachable - either we returned or skipped a blank line + assert(false); } - - return 0; + + // when we come to this point, all data must have been read + assert(cxBufferEof(&prop->buffer)); + assert(cxBufferEof(&prop->input)); + + return CX_PROPERTIES_NO_DATA; } -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; +static int cx_properties_sink_map( + cx_attr_unused CxProperties *prop, + CxPropertiesSink *sink, + cxstring key, + cxstring value +) { + CxMap *map = sink->sink; + CxAllocator *alloc = sink->data; + cxmutstr v = cx_strdup_a(alloc, value); + int r = cx_map_put_cxstr(map, key, v.ptr); + if (r != 0) cx_strfree_a(alloc, &v); + return r; +} + +CxPropertiesSink cxPropertiesMapSink(CxMap *map) { + CxPropertiesSink sink; + sink.sink = map; + sink.data = cxDefaultAllocator; + sink.sink_func = cx_properties_sink_map; + return sink; +} + +static int cx_properties_read_string( + CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + if (prop->input.space == src->src) { + // when the input buffer already contains the string + // we have nothing more to provide + target->length = 0; } else { - return 0; + target->ptr = src->src; + target->length = src->data_size; } + return 0; +} + +static int cx_properties_read_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src, + cxstring *target +) { + target->ptr = src->data_ptr; + target->length = fread(src->data_ptr, 1, src->data_size, src->src); + return ferror(src->src); +} + +static int cx_properties_read_init_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + src->data_ptr = malloc(src->data_size); + if (src->data_ptr == NULL) return 1; + return 0; +} + +static void cx_properties_read_clean_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + free(src->data_ptr); +} + +CxPropertiesSource cxPropertiesStringSource(cxstring str) { + CxPropertiesSource src; + src.src = (void*) str.ptr; + src.data_size = str.length; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = len; + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; +} + +CxPropertiesSource cxPropertiesCstrSource(const char *str) { + CxPropertiesSource src; + src.src = (void*) str; + src.data_size = strlen(str); + src.data_ptr = NULL; + src.read_func = cx_properties_read_string; + src.read_init_func = NULL; + src.read_clean_func = NULL; + return src; } -// 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; +CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { + CxPropertiesSource src; + src.src = file; + src.data_size = chunk_size; + src.data_ptr = NULL; + src.read_func = cx_properties_read_file; + src.read_init_func = cx_properties_read_init_file; + src.read_clean_func = cx_properties_read_clean_file; + return src; +} + +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +) { + assert(source.read_func != NULL); + assert(sink.sink_func != NULL); + + // initialize reader + if (source.read_init_func != NULL) { + if (source.read_init_func(prop, &source)) { + return CX_PROPERTIES_READ_INIT_FAILED; + } } - - 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) { + + // transfer the data from the source to the sink + CxPropertiesStatus status; + bool found = false; + while (true) { + // read input + cxstring input; + if (source.read_func(prop, &source, &input)) { + status = CX_PROPERTIES_READ_FAILED; 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; + // no more data - break + if (input.length == 0) { + status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; + break; + } - UCX_MAP_FOREACH(k, v, iter) { - value = sstr((char*)v); + // set the input buffer and read the k/v-pairs + cxPropertiesFill(prop, input); - 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); + CxPropertiesStatus kv_status; + do { + cxstring key, value; + kv_status = cxPropertiesNext(prop, &key, &value); + if (kv_status == CX_PROPERTIES_NO_ERROR) { + found = true; + if (sink.sink_func(prop, &sink, key, value)) { + kv_status = CX_PROPERTIES_SINK_FAILED; + } + } + } while (kv_status == CX_PROPERTIES_NO_ERROR); - if (written != k.len + value.length + 4) { - return 1; + if (kv_status > CX_PROPERTIES_OK) { + status = kv_status; + break; } } - return 0; -} + if (source.read_clean_func != NULL) { + source.read_clean_func(prop, &source); + } + return status; +} diff --git a/ui/motif/list.h b/ucx/streams.c similarity index 51% rename from ui/motif/list.h rename to ucx/streams.c index 35f97ea..2efc035 100644 --- a/ui/motif/list.h +++ b/ucx/streams.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,39 +26,68 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LIST_H -#define LIST_H +#include "cx/streams.h" -#include "toolkit.h" -#include "../ui/tree.h" -#include "../common/context.h" +#ifndef CX_STREAM_BCOPY_BUF_SIZE +#define CX_STREAM_BCOPY_BUF_SIZE 8192 +#endif -#ifdef __cplusplus -extern "C" { +#ifndef CX_STREAM_COPY_BUF_SIZE +#define CX_STREAM_COPY_BUF_SIZE 1024 #endif -typedef struct UiListView { - Widget widget; - UiVar *list; - ui_getvaluefunc getvalue; -} UiListView; +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +) { + if (n == 0) { + return 0; + } -typedef struct UiListViewEventData { - UiEventData event; - UiVar *var; -} UiListViewEventData; + char *lbuf; + size_t ncp = 0; -void* ui_strmodel_getvalue(void *elm, int column); + if (buf) { + if (bufsize == 0) return 0; + lbuf = buf; + } else { + if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; + lbuf = malloc(bufsize); + if (lbuf == NULL) return 0; + } -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); + size_t r; + size_t rn = bufsize > n ? n : bufsize; + while ((r = rfnc(lbuf, 1, rn, src)) != 0) { + r = wfnc(lbuf, 1, r, dest); + ncp += r; + n -= r; + rn = bufsize > n ? n : bufsize; + if (r == 0 || n == 0) { + break; + } + } -UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); + if (lbuf != buf) { + free(lbuf); + } -#ifdef __cplusplus + return ncp; } -#endif - -#endif /* LIST_H */ +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +) { + char buf[CX_STREAM_COPY_BUF_SIZE]; + return cx_stream_bncopy(src, dest, rfnc, wfnc, + buf, CX_STREAM_COPY_BUF_SIZE, n); +} diff --git a/ucx/string.c b/ucx/string.c index 5ea54f3..e532ea1 100644 --- a/ucx/string.c +++ b/ucx/string.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,64 +25,75 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +#define CX_STR_IMPLEMENTATION +#include "cx/string.h" -#include "ucx/string.h" - -#include "ucx/allocator.h" - -#include #include #include -#include #include +#include +#include +#include +#include + +#ifdef _WIN32 +#define cx_strcasecmp_impl _strnicmp +#else +#include +#define cx_strcasecmp_impl strncasecmp +#endif -#ifndef _WIN32 -#include /* for strncasecmp() */ -#endif /* _WIN32 */ +cxmutstr cx_mutstr(char *cstring) { + return (cxmutstr) {cstring, strlen(cstring)}; +} -sstr_t sstr(char *cstring) { - sstr_t string; - string.ptr = cstring; - string.length = strlen(cstring); - return string; +cxmutstr cx_mutstrn( + char *cstring, + size_t length +) { + return (cxmutstr) {cstring, length}; } -sstr_t sstrn(char *cstring, size_t length) { - sstr_t string; - string.ptr = cstring; - string.length = length; - return string; +cxstring cx_str(const char *cstring) { + return (cxstring) {cstring, strlen(cstring)}; } -scstr_t scstr(const char *cstring) { - scstr_t string; - string.ptr = cstring; - string.length = strlen(cstring); - return string; +cxstring cx_strn( + const char *cstring, + size_t length +) { + return (cxstring) {cstring, length}; } -scstr_t scstrn(const char *cstring, size_t length) { - scstr_t string; - string.ptr = cstring; - string.length = length; - return string; +void cx_strfree(cxmutstr *str) { + if (str == NULL) return; + free(str->ptr); + str->ptr = NULL; + str->length = 0; } +void cx_strfree_a( + const CxAllocator *alloc, + cxmutstr *str +) { + if (str == NULL) return; + cxFree(alloc, str->ptr); + str->ptr = NULL; + str->length = 0; +} + +size_t cx_strlen( + size_t count, + ... +) { + if (count == 0) return 0; -size_t scstrnlen(size_t n, ...) { - if (n == 0) return 0; - va_list ap; - va_start(ap, n); - + va_start(ap, count); 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; - } + for (size_t i = 0; i < count; i++) { + cxstring str = va_arg(ap, cxstring); + if (size > SIZE_MAX - str.length) errno = EOVERFLOW; size += str.length; } va_end(ap); @@ -90,643 +101,579 @@ size_t scstrnlen(size_t n, ...) { return size; } -static sstr_t sstrvcat_a( - UcxAllocator *a, +cxmutstr cx_strcat_ma( + const CxAllocator *alloc, + cxmutstr str, 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; - } - + ... +) { + if (count == 0) return str; + + cxstring strings_stack[8]; + cxstring *strings; + if (count > 8) { + strings = calloc(count, sizeof(cxstring)); + if (strings == NULL) { + return (cxmutstr) {NULL, 0}; + } + } else { + strings = strings_stack; + } + + va_list ap; + va_start(ap, count); + // 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 SIZE_MAX - str.length) overflow = true; slen += s.length; } - if(error) { - free(strings); - return str; + va_end(ap); + + // abort in case of overflow + if (overflow) { + errno = EOVERFLOW; + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) { NULL, 0 }; } - - // create new string - str.ptr = (char*) almalloc(a, slen + 1); - str.length = slen; - if(!str.ptr) { - free(strings); - str.length = 0; - return str; + + // reallocate or create new string + char *newstr; + if (str.ptr == NULL) { + newstr = cxMalloc(alloc, slen + 1); + } else { + newstr = cxRealloc(alloc, str.ptr, slen + 1); + } + if (newstr == NULL) { + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) {NULL, 0}; } - + str.ptr = newstr; + // concatenate strings - size_t pos = 0; - for (size_t i=0;i str_length) { - return 0; - } - - if(length > str_length - start) { - length = str_length - start; + size_t length +) { + if (start > string.length) { + return (cxstring) {NULL, 0}; } - *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; + size_t rem_len = string.length - start; + if (length > rem_len) { + length = rem_len; } - return ret; -} -scstr_t scstrsubs(scstr_t string, size_t start) { - return scstrsubsl(string, start, string.length-start); + return (cxstring) {string.ptr + start, length}; } -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; +cxmutstr cx_strsubsl_m( + cxmutstr string, + size_t start, + size_t length +) { + cxstring result = cx_strsubsl(cx_strcast(string), start, length); + return (cxmutstr) {(char *) result.ptr, result.length}; } - -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; +cxmutstr cx_strchr_m( + cxmutstr string, + int chr +) { + cxstring result = cx_strchr(cx_strcast(string), chr); + return (cxmutstr) {(char *) result.ptr, result.length}; } -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); +cxstring cx_strrchr( + cxstring string, + int chr +) { + chr = 0xFF & chr; + size_t i = string.length; + while (i > 0) { + i--; + // TODO: improve by comparing multiple bytes at once + if (string.ptr[i] == chr) { + return cx_strsubs(string, i); + } } - return sstrn(NULL, 0); + return (cxstring) {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); +cxmutstr cx_strrchr_m( + cxmutstr string, + int chr +) { + cxstring result = cx_strrchr(cx_strcast(string), chr); + return (cxmutstr) {(char *) result.ptr, result.length}; } -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); +#ifndef CX_STRSTR_SBO_SIZE +#define CX_STRSTR_SBO_SIZE 512 +#endif +const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; + +cxstring cx_strstr( + cxstring haystack, + cxstring needle +) { + if (needle.length == 0) { + return haystack; } - 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); + // optimize for single-char needles + if (needle.length == 1) { + return cx_strchr(haystack, *needle.ptr); } - 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 + * 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 */ + + // local prefix table + size_t s_prefix_table[CX_STRSTR_SBO_SIZE]; + + // check needle length and use appropriate prefix table + // if the pattern exceeds static prefix table, allocate on the heap + bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; + register size_t *ptable = useheap ? calloc(needle.length + 1, + sizeof(size_t)) : s_prefix_table; + + // keep counter in registers register size_t i, j; - - /* fill prefix table */ - i = 0; j = 0; - ptable_w(useheap, ptable, i, j); - while (i < matchlen) { - while (j >= 1 && match[j-1] != match[i]) { - ptable_r(j, useheap, ptable, j-1); + + // fill prefix table + i = 0; + j = 0; + ptable[i] = j; + while (i < needle.length) { + while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) { + j = ptable[j - 1]; } - i++; j++; - ptable_w(useheap, ptable, i, j); + i++; + j++; + 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); + // search + cxstring result = {NULL, 0}; + i = 0; + j = 1; + while (i < haystack.length) { + while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) { + j = ptable[j - 1]; } - i++; j++; - if (j-1 == matchlen) { - size_t start = i - matchlen; - result = str + start; - resultlen = length - start; + i++; + j++; + if (j - 1 == needle.length) { + size_t start = i - needle.length; + result.ptr = haystack.ptr + start; + result.length = haystack.length - start; break; } } - /* if prefix table was allocated on the heap, free it */ + // if 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; +cxmutstr cx_strstr_m( + cxmutstr haystack, + cxstring needle +) { + cxstring result = cx_strstr(cx_strcast(haystack), needle); + return (cxmutstr) {(char *) result.ptr, result.length}; } -#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); -} +size_t cx_strsplit( + cxstring string, + cxstring delim, + size_t limit, + cxstring *output +) { + // special case: output limit is zero + if (limit == 0) return 0; + + // special case: delimiter is empty + if (delim.length == 0) { + output[0] = string; + return 1; + } -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 (delim.length >= string.length) { + // exact match + if (cx_strcmp(string, delim) == 0) { + output[0] = cx_strn(string.ptr, 0); + output[1] = cx_strn(string.ptr + string.length, 0); + return 2; + } else { + // no match possible + output[0] = string; + return 1; + } } - - /* 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); + + size_t n = 0; + cxstring curpos = string; + while (1) { + ++n; + cxstring match = cx_strstr(curpos, delim); + if (match.length > 0) { + // is the limit reached? + if (n < limit) { + // copy the current string to the array + cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr); + output[n - 1] = item; + size_t processed = item.length + delim.length; + curpos.ptr += processed; + curpos.length -= processed; } else { - *n = -2; + // limit reached, copy the _full_ remaining string + output[n - 1] = curpos; + break; } - return result; + } else { + // no more matches, copy last string + output[n - 1] = curpos; + break; } } - - 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; - } + + return n; +} + +size_t cx_strsplit_a( + const CxAllocator *allocator, + cxstring string, + cxstring delim, + size_t limit, + cxstring **output +) { + // find out how many splits we're going to make and allocate memory + size_t n = 0; + cxstring curpos = string; + while (1) { + ++n; + cxstring match = cx_strstr(curpos, delim); + if (match.length > 0) { + // is the limit reached? + if (n < limit) { + size_t processed = match.ptr - curpos.ptr + delim.length; + curpos.ptr += processed; + curpos.length -= processed; } else { - /* no more matches, copy last string */ - result[j-1] = sstrdup_a(allocator, curpos); + // limit reached break; } + } else { + // no more matches + break; } - *n = j; - } else { - *n = -2; } + *output = cxCalloc(allocator, n, sizeof(cxstring)); + return cx_strsplit(string, delim, n, *output); +} - return result; +size_t cx_strsplit_m( + cxmutstr string, + cxstring delim, + size_t limit, + cxmutstr *output +) { + return cx_strsplit(cx_strcast(string), + delim, limit, (cxstring *) output); +} + +size_t cx_strsplit_ma( + const CxAllocator *allocator, + cxmutstr string, + cxstring delim, + size_t limit, + cxmutstr **output +) { + return cx_strsplit_a(allocator, cx_strcast(string), + delim, limit, (cxstring **) output); } -int scstrcmp(scstr_t s1, scstr_t s2) { +int cx_strcmp( + cxstring s1, + cxstring s2 +) { if (s1.length == s2.length) { - return memcmp(s1.ptr, s2.ptr, s1.length); + return strncmp(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = strncmp(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = strncmp(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } -int scstrcasecmp(scstr_t s1, scstr_t s2) { +int cx_strcasecmp( + cxstring s1, + cxstring s2 +) { if (s1.length == s2.length) { -#ifdef _WIN32 - return _strnicmp(s1.ptr, s2.ptr, s1.length); -#else - return strncasecmp(s1.ptr, s2.ptr, s1.length); -#endif + return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length); + if (r != 0) return r; return 1; } else { + int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length); + if (r != 0) return r; return -1; } } -sstr_t scstrdup(scstr_t s) { - return sstrdup_a(ucx_default_allocator(), s); +int cx_strcmp_p( + const void *s1, + const void *s2 +) { + const cxstring *left = s1; + const cxstring *right = s2; + return cx_strcmp(*left, *right); } -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; +int cx_strcasecmp_p( + const void *s1, + const void *s2 +) { + const cxstring *left = s1; + const cxstring *right = s2; + return cx_strcasecmp(*left, *right); } - -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--; +cxmutstr cx_strdup_a( + const CxAllocator *allocator, + cxstring string +) { + cxmutstr result = { + cxMalloc(allocator, string.length + 1), + string.length + }; + if (result.ptr == NULL) { + result.length = 0; + return result; } - - *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; + memcpy(result.ptr, string.ptr, string.length); + result.ptr[string.length] = '\0'; + return result; } -int scstrprefix(scstr_t string, scstr_t prefix) { - if (string.length == 0) { - return prefix.length == 0; +cxstring cx_strtrim(cxstring string) { + cxstring result = string; + // TODO: optimize by comparing multiple bytes at once + while (result.length > 0 && isspace(*result.ptr)) { + result.ptr++; + result.length--; } - if (prefix.length == 0) { - return 1; - } - - if (prefix.length > string.length) { - return 0; - } else { - return memcmp(string.ptr, prefix.ptr, prefix.length) == 0; + while (result.length > 0 && isspace(result.ptr[result.length - 1])) { + result.length--; } + return result; } -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; - } +cxmutstr cx_strtrim_m(cxmutstr string) { + cxstring result = cx_strtrim(cx_strcast(string)); + return (cxmutstr) {(char *) result.ptr, result.length}; } -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; - } +bool cx_strprefix( + cxstring string, + cxstring prefix +) { + if (string.length < prefix.length) return false; + return memcmp(string.ptr, prefix.ptr, prefix.length) == 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; - } +bool cx_strsuffix( + cxstring string, + cxstring suffix +) { + if (string.length < suffix.length) return false; + return memcmp(string.ptr + string.length - suffix.length, + suffix.ptr, suffix.length) == 0; } -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; +bool cx_strcaseprefix( + cxstring string, + cxstring prefix +) { + if (string.length < prefix.length) return false; +#ifdef _WIN32 + return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0; +#else + return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0; +#endif } -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; +bool cx_strcasesuffix( + cxstring string, + cxstring suffix +) { + if (string.length < suffix.length) return false; +#ifdef _WIN32 + return _strnicmp(string.ptr+string.length-suffix.length, + suffix.ptr, suffix.length) == 0; +#else + return strncasecmp(string.ptr + string.length - suffix.length, + suffix.ptr, suffix.length) == 0; +#endif } -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]); +void cx_strlower(cxmutstr string) { + for (size_t i = 0; i < string.length; i++) { + string.ptr[i] = (char) tolower(string.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]); +void cx_strupper(cxmutstr string) { + for (size_t i = 0; i < string.length; i++) { + string.ptr[i] = (char) toupper(string.ptr[i]); } - return ret; } -#define REPLACE_INDEX_BUFFER_MAX 100 +#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE +#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64 +#endif -struct scstrreplace_ibuf { - size_t* buf; - unsigned int len; /* small indices */ - struct scstrreplace_ibuf* next; +struct cx_strreplace_ibuf { + size_t *buf; + struct cx_strreplace_ibuf *next; + unsigned int len; }; -static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) { +static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) { while (buf) { - struct scstrreplace_ibuf *next = buf->next; + struct cx_strreplace_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) { +cxmutstr cx_strreplacen_a( + const CxAllocator *allocator, + cxstring str, + cxstring pattern, + cxstring replacement, + size_t replmax +) { if (pattern.length == 0 || pattern.length > str.length || replmax == 0) - return sstrdup(str); + return cx_strdup_a(allocator, str); - /* Compute expected buffer length */ + // 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; + if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) { + ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE; } - /* Allocate first index buffer */ - struct scstrreplace_ibuf *firstbuf, *curbuf; - firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf)); - if (!firstbuf) return sstrn(NULL, 0); + // Allocate first index buffer + struct cx_strreplace_ibuf *firstbuf, *curbuf; + firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf)); + if (!firstbuf) return cx_mutstrn(NULL, 0); firstbuf->buf = calloc(ibuflen, sizeof(size_t)); if (!firstbuf->buf) { free(firstbuf); - return sstrn(NULL, 0); + return cx_mutstrn(NULL, 0); } - /* Search occurrences */ - scstr_t searchstr = str; + // Search occurrences + cxstring searchstr = str; size_t found = 0; do { - scstr_t match = scstrscstr(searchstr, pattern); + cxstring match = cx_strstr(searchstr, pattern); if (match.length > 0) { - /* Allocate next buffer in chain, if required */ + // Allocate next buffer in chain, if required if (curbuf->len == ibuflen) { - struct scstrreplace_ibuf *nextbuf = - calloc(1, sizeof(struct scstrreplace_ibuf)); + struct cx_strreplace_ibuf *nextbuf = + calloc(1, sizeof(struct cx_strreplace_ibuf)); if (!nextbuf) { - scstrrepl_free_ibuf(firstbuf); - return sstrn(NULL, 0); + cx_strrepl_free_ibuf(firstbuf); + return cx_mutstrn(NULL, 0); } nextbuf->buf = calloc(ibuflen, sizeof(size_t)); if (!nextbuf->buf) { free(nextbuf); - scstrrepl_free_ibuf(firstbuf); - return sstrn(NULL, 0); + cx_strrepl_free_ibuf(firstbuf); + return cx_mutstrn(NULL, 0); } curbuf->next = nextbuf; curbuf = nextbuf; } - /* Record match index */ + // Record match index found++; size_t idx = match.ptr - str.ptr; curbuf->buf[curbuf->len++] = idx; @@ -737,8 +684,8 @@ sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, } } while (searchstr.length > 0 && found < replmax); - /* Allocate result string */ - sstr_t result; + // Allocate result string + cxmutstr result; { ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; size_t rcount = 0; @@ -748,60 +695,494 @@ sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, curbuf = curbuf->next; } while (curbuf); result.length = str.length + rcount * adjlen; - result.ptr = almalloc(allocator, result.length); + result.ptr = cxMalloc(allocator, result.length + 1); if (!result.ptr) { - scstrrepl_free_ibuf(firstbuf); - return sstrn(NULL, 0); + cx_strrepl_free_ibuf(firstbuf); + return cx_mutstrn(NULL, 0); } } - /* Build result string */ + // Build result string curbuf = firstbuf; size_t srcidx = 0; - char* destptr = result.ptr; + char *destptr = result.ptr; do { for (size_t i = 0; i < curbuf->len; i++) { - /* Copy source part up to next match*/ + // 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); + memcpy(destptr, str.ptr + srcidx, srclen); destptr += srclen; srcidx += srclen; } - /* Copy the replacement and skip the source pattern */ + // 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); + memcpy(destptr, str.ptr + srcidx, str.length - srcidx); + + // Result is guaranteed to be zero-terminated + result.ptr[result.length] = '\0'; - /* Free index buffer */ - scstrrepl_free_ibuf(firstbuf); + // Free index buffer + cx_strrepl_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); +CxStrtokCtx cx_strtok( + cxstring str, + cxstring delim, + size_t limit +) { + CxStrtokCtx ctx; + ctx.str = str; + ctx.delim = delim; + ctx.limit = limit; + ctx.pos = 0; + ctx.next_pos = 0; + ctx.delim_pos = 0; + ctx.found = 0; + ctx.delim_more = NULL; + ctx.delim_more_count = 0; + return ctx; } +CxStrtokCtx cx_strtok_m( + cxmutstr str, + cxstring delim, + size_t limit +) { + return cx_strtok(cx_strcast(str), delim, limit); +} -// type adjustment functions -scstr_t ucx_sc2sc(scstr_t str) { - return str; +bool cx_strtok_next( + CxStrtokCtx *ctx, + cxstring *token +) { + // abortion criteria + if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) { + return false; + } + + // determine the search start + cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos); + + // search the next delimiter + cxstring delim = cx_strstr(haystack, ctx->delim); + + // if found, make delim capture exactly the delimiter + if (delim.length > 0) { + delim.length = ctx->delim.length; + } + + // if more delimiters are specified, check them now + if (ctx->delim_more_count > 0) { + for (size_t i = 0; i < ctx->delim_more_count; i++) { + cxstring d = cx_strstr(haystack, ctx->delim_more[i]); + if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) { + delim.ptr = d.ptr; + delim.length = ctx->delim_more[i].length; + } + } + } + + // store the token information and adjust the context + ctx->found++; + ctx->pos = ctx->next_pos; + token->ptr = &ctx->str.ptr[ctx->pos]; + ctx->delim_pos = delim.length == 0 ? + ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr); + token->length = ctx->delim_pos - ctx->pos; + ctx->next_pos = ctx->delim_pos + delim.length; + + return true; +} + +bool cx_strtok_next_m( + CxStrtokCtx *ctx, + cxmutstr *token +) { + return cx_strtok_next(ctx, (cxstring *) token); +} + +void cx_strtok_delim( + CxStrtokCtx *ctx, + const cxstring *delim, + size_t count +) { + ctx->delim_more = delim; + ctx->delim_more_count = count; +} + +#define cx_strtoX_signed_impl(rtype, rmin, rmax) \ + long long result; \ + if (cx_strtoll_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result < rmin || result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX); +} + +int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int, INT_MIN, INT_MAX); +} + +int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX); +} + +int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep) { + // strategy: parse as unsigned, check range, negate if required + bool neg = false; + size_t start_unsigned = 0; + + // trim already, to search for a sign character + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // test if we have a negative sign character + if (str.ptr[start_unsigned] == '-') { + neg = true; + start_unsigned++; + // must not be followed by positive sign character + if (str.length == 1 || str.ptr[start_unsigned] == '+') { + errno = EINVAL; + return -1; + } + } + + // now parse the number with strtoull + unsigned long long v; + cxstring ustr = start_unsigned == 0 ? str + : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned); + int ret = cx_strtoull_lc(ustr, &v, base, groupsep); + if (ret != 0) return ret; + if (neg) { + if (v - 1 > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = -(long long) v; + return 0; + } else { + if (v > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = (long long) v; + return 0; + } +} + +int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX); +} + +int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX); +} + +int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep) { + cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX); +} + +int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep) { + assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +} + +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep) { +#if SSIZE_MAX == INT32_MAX + return cx_strtoi32_lc(str, (int32_t*) output, base, groupsep); +#elif SSIZE_MAX == INT64_MAX + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +#else +#error "unsupported ssize_t size" +#endif +} + +#define cx_strtoX_unsigned_impl(rtype, rmax) \ + uint64_t result; \ + if (cx_strtou64_lc(str, &result, base, groupsep)) { \ + return -1; \ + } \ + if (result > rmax) { \ + errno = ERANGE; \ + return -1; \ + } \ + *output = (rtype) result; \ + return 0 + +int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX); +} + +int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned int, UINT_MAX); +} + +int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX); +} + +int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep) { + // some sanity checks + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + if (!(base == 2 || base == 8 || base == 10 || base == 16)) { + errno = EINVAL; + return -1; + } + if (groupsep == NULL) groupsep = ""; + + // find the actual start of the number + if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + if (str.length == 0) { + errno = EINVAL; + return -1; + } + } + size_t start = 0; + + // if base is 2 or 16, some leading stuff may appear + if (base == 2) { + if ((str.ptr[0] | 32) == 'b') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'b') { + start = 2; + } + } + } else if (base == 16) { + if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if ((str.ptr[1] | 32) == 'x') { + start = 2; + } + } + } + + // check if there are digits left + if (start >= str.length) { + errno = EINVAL; + return -1; + } + + // now parse the number + unsigned long long result = 0; + for (size_t i = start; i < str.length; i++) { + // ignore group separators + if (strchr(groupsep, str.ptr[i])) continue; + + // determine the digit value of the character + unsigned char c = str.ptr[i]; + if (c >= 'a') c = 10 + (c - 'a'); + else if (c >= 'A') c = 10 + (c - 'A'); + else if (c >= '0') c = c - '0'; + else c = 255; + if (c >= base) { + errno = EINVAL; + return -1; + } + + // now combine the digit with what we already have + unsigned long right = (result & 0xff) * base + c; + unsigned long long left = (result >> 8) * base + (right >> 8); + if (left > (ULLONG_MAX >> 8)) { + errno = ERANGE; + return -1; + } + result = (left << 8) + (right & 0xff); + } + + *output = result; + return 0; } -scstr_t ucx_ss2sc(sstr_t str) { - scstr_t cs; - cs.ptr = str.ptr; - cs.length = str.length; - return cs; + +int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX); +} + +int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX); } -scstr_t ucx_ss2c_s(scstr_t c) { - return c; + +int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep) { + cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX); +} + +int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep) { + assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms + return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep); +} + +int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep) { +#if SIZE_MAX == UINT32_MAX + return cx_strtou32_lc(str, (uint32_t*) output, base, groupsep); +#elif SIZE_MAX == UINT64_MAX + return cx_strtoull_lc(str, (unsigned long long *) output, base, groupsep); +#else +#error "unsupported size_t size" +#endif +} + +int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep) { + // use string to double and add a range check + double d; + int ret = cx_strtod_lc(str, &d, decsep, groupsep); + if (ret != 0) return ret; + // note: FLT_MIN is the smallest POSITIVE number that can be represented + double test = d < 0 ? -d : d; + if (test < FLT_MIN || test > FLT_MAX) { + errno = ERANGE; + return -1; + } + *output = (float) d; + return 0; +} + +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep) { + // TODO: overflow check + // TODO: increase precision + + // trim and check + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + double result = 0.; + int sign = 1; + + // check if there is a sign + if (str.ptr[0] == '-') { + sign = -1; + str.ptr++; + str.length--; + } else if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + } + + // there must be at least one char to parse + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // parse all digits until we find the decsep + size_t pos = 0; + do { + if (isdigit(str.ptr[pos])) { + result = result * 10 + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + + // already done? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // is the next char the decsep? + if (str.ptr[pos] == decsep) { + pos++; + // it may end with the decsep, if it did not start with it + if (pos == str.length) { + if (str.length == 1) { + errno = EINVAL; + return -1; + } else { + *output = result * sign; + return 0; + } + } + // parse everything until exponent or end + double factor = 1.; + do { + if (isdigit(str.ptr[pos])) { + factor *= 0.1; + result = result + factor * (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + break; + } + } while (++pos < str.length); + } + + // no exponent? + if (pos == str.length) { + *output = result * sign; + return 0; + } + + // now the next separator MUST be the exponent separator + // and at least one char must follow + if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) { + errno = EINVAL; + return -1; + } + pos++; + + // check if we have a sign for the exponent + double factor = 10.; + if (str.ptr[pos] == '-') { + factor = .1; + pos++; + } else if (str.ptr[pos] == '+') { + pos++; + } + + // at least one digit must follow + if (pos == str.length) { + errno = EINVAL; + return -1; + } + + // parse the exponent + unsigned int exp = 0; + do { + if (isdigit(str.ptr[pos])) { + exp = 10 * exp + (str.ptr[pos] - '0'); + } else if (strchr(groupsep, str.ptr[pos]) == NULL) { + errno = EINVAL; + return -1; + } + } while (++pos < str.length); + + // apply the exponent by fast exponentiation + do { + if (exp & 1) { + result *= factor; + } + factor *= factor; + } while ((exp >>= 1) > 0); + + // store the result and exit + *output = result * sign; + return 0; } diff --git a/ucx/szmul.c b/ucx/szmul.c new file mode 100644 index 0000000..88ce08a --- /dev/null +++ b/ucx/szmul.c @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/common.h" + +#ifndef CX_SZMUL_BUILTIN +int cx_szmul_impl( + size_t a, + size_t b, + size_t *result +) { + if (a == 0 || b == 0) { + *result = 0; + return 0; + } + size_t r = a * b; + if (r / b == a) { + *result = r; + return 0; + } else { + *result = 0; + return 1; + } +} +#endif // CX_SZMUL_BUILTIN diff --git a/ucx/tree.c b/ucx/tree.c new file mode 100644 index 0000000..b2fbb04 --- /dev/null +++ b/ucx/tree.c @@ -0,0 +1,1054 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/tree.h" + +#include "cx/array_list.h" + +#include + +#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) +#define tree_parent(node) CX_TREE_PTR(node, loc_parent) +#define tree_children(node) CX_TREE_PTR(node, loc_children) +#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child) +#define tree_prev(node) CX_TREE_PTR(node, loc_prev) +#define tree_next(node) CX_TREE_PTR(node, loc_next) + +#define cx_tree_ptr_locations \ + loc_parent, loc_children, loc_last_child, loc_prev, loc_next + +#define cx_tree_node_layout(tree) \ + (tree)->loc_parent,\ + (tree)->loc_children,\ + (tree)->loc_last_child,\ + (tree)->loc_prev, \ + (tree)->loc_next + +static void cx_tree_zero_pointers( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + tree_parent(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } + tree_next(node) = NULL; + tree_children(node) = NULL; + if (loc_last_child >= 0) { + tree_last_child(node) = NULL; + } +} + +void cx_tree_link( + void *parent, + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert(loc_parent >= 0); + assert(loc_children >= 0); + assert(loc_next >= 0); + + void *current_parent = tree_parent(node); + if (current_parent == parent) return; + if (current_parent != NULL) { + cx_tree_unlink(node, cx_tree_ptr_locations); + } + + if (tree_children(parent) == NULL) { + tree_children(parent) = node; + if (loc_last_child >= 0) { + tree_last_child(parent) = node; + } + } else { + void *child; + if (loc_last_child >= 0) { + child = tree_last_child(parent); + tree_last_child(parent) = node; + } else { + child = tree_children(parent); + void *next; + while ((next = tree_next(child)) != NULL) { + child = next; + } + } + if (loc_prev >= 0) { + tree_prev(node) = child; + } + tree_next(child) = node; + } + tree_parent(node) = parent; +} + +static void *cx_tree_node_prev( + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_next, + const void *node +) { + void *parent = tree_parent(node); + void *begin = tree_children(parent); + if (begin == node) return NULL; + const void *cur = begin; + const void *next; + while (1) { + next = tree_next(cur); + if (next == node) return (void *) cur; + cur = next; + } +} + +void cx_tree_unlink( + void *node, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (tree_parent(node) == NULL) return; + + assert(loc_children >= 0); + assert(loc_next >= 0); + assert(loc_parent >= 0); + void *left; + if (loc_prev >= 0) { + left = tree_prev(node); + } else { + left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node); + } + void *right = tree_next(node); + void *parent = tree_parent(node); + assert(left == NULL || tree_children(parent) != node); + assert(right == NULL || loc_last_child < 0 || + tree_last_child(parent) != node); + + if (left == NULL) { + tree_children(parent) = right; + } else { + tree_next(left) = right; + } + if (right == NULL) { + if (loc_last_child >= 0) { + tree_last_child(parent) = left; + } + } else { + if (loc_prev >= 0) { + tree_prev(right) = left; + } + } + + tree_parent(node) = NULL; + tree_next(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } +} + +int cx_tree_search( + const void *root, + size_t depth, + const void *node, + cx_tree_search_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + // help avoiding bugs due to uninitialized memory + assert(result != NULL); + *result = NULL; + + // remember return value for best match + int ret = sfunc(root, node); + if (ret < 0) { + // not contained, exit + return -1; + } + *result = (void*) root; + // if root is already exact match, exit + if (ret == 0) { + return 0; + } + + // when depth is one, we are already done + if (depth == 1) { + return ret; + } + + // special case: indefinite depth + if (depth == 0) { + depth = SIZE_MAX; + } + + // create an iterator + CxTreeIterator iter = cx_tree_iterator( + (void*) root, false, loc_children, loc_next + ); + + // skip root, we already handled it + cxIteratorNext(iter); + + // loop through the remaining tree + cx_foreach(void *, elem, iter) { + // investigate the current node + int ret_elem = sfunc(elem, node); + if (ret_elem == 0) { + // if found, exit the search + *result = (void *) elem; + ret = 0; + break; + } else if (ret_elem > 0 && ret_elem < ret) { + // new distance is better + *result = elem; + ret = ret_elem; + } else { + // not contained or distance is worse, skip entire subtree + cxTreeIteratorContinue(iter); + } + + // when we reached the max depth, skip the subtree + if (iter.depth == depth) { + cxTreeIteratorContinue(iter); + } + } + + // dispose the iterator as we might have exited the loop early + cxTreeIteratorDispose(&iter); + + assert(ret < 0 || *result != NULL); + return ret; +} + +int cx_tree_search_data( + const void *root, + size_t depth, + const void *data, + cx_tree_search_data_func sfunc, + void **result, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + // it is basically the same implementation + return cx_tree_search( + root, depth, data, + (cx_tree_search_func) sfunc, + result, + loc_children, loc_next); +} + +static bool cx_tree_iter_valid(const void *it) { + const struct cx_tree_iterator_s *iter = it; + return iter->node != NULL; +} + +static void *cx_tree_iter_current(const void *it) { + const struct cx_tree_iterator_s *iter = it; + return iter->node; +} + +static void cx_tree_iter_next(void *it) { + struct cx_tree_iterator_s *iter = it; + ptrdiff_t const loc_next = iter->loc_next; + ptrdiff_t const loc_children = iter->loc_children; + // protect us from misuse + if (!iter->base.valid(iter)) return; + + void *children; + + // check if we are currently exiting or entering nodes + if (iter->exiting) { + children = NULL; + // skipping on exit is pointless, just clear the flag + iter->skip = false; + } else { + if (iter->skip) { + // skip flag is set, pretend that there are no children + iter->skip = false; + children = NULL; + } else { + // try to enter the children (if any) + children = tree_children(iter->node); + } + } + + if (children == NULL) { + // search for the next node + void *next; + cx_tree_iter_search_next: + // check if there is a sibling + if (iter->exiting) { + next = iter->node_next; + } else { + next = tree_next(iter->node); + iter->node_next = next; + } + if (next == NULL) { + // no sibling, we are done with this node and exit + if (iter->visit_on_exit && !iter->exiting) { + // iter is supposed to visit the node again + iter->exiting = true; + } else { + iter->exiting = false; + if (iter->depth == 1) { + // there is no parent - we have iterated the entire tree + // invalidate the iterator and free the node stack + iter->node = iter->node_next = NULL; + iter->stack_capacity = iter->depth = 0; + free(iter->stack); + iter->stack = NULL; + } else { + // the parent node can be obtained from the top of stack + // this way we can avoid the loc_parent in the iterator + iter->depth--; + iter->node = iter->stack[iter->depth - 1]; + // retry with the parent node to find a sibling + goto cx_tree_iter_search_next; + } + } + } else { + if (iter->visit_on_exit && !iter->exiting) { + // iter is supposed to visit the node again + iter->exiting = true; + } else { + iter->exiting = false; + // move to the sibling + iter->counter++; + iter->node = next; + // new top of stack is the sibling + iter->stack[iter->depth - 1] = next; + } + } + } else { + // node has children, push the first child onto the stack and enter it + cx_array_simple_add(iter->stack, children); + iter->node = children; + iter->counter++; + } +} + +CxTreeIterator cx_tree_iterator( + void *root, + bool visit_on_exit, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + CxTreeIterator iter; + iter.loc_children = loc_children; + iter.loc_next = loc_next; + iter.visit_on_exit = visit_on_exit; + + // initialize members + iter.node_next = NULL; + iter.exiting = false; + iter.skip = false; + + // assign base iterator functions + iter.base.mutating = false; + iter.base.remove = false; + iter.base.current_impl = NULL; + iter.base.valid = cx_tree_iter_valid; + iter.base.next = cx_tree_iter_next; + iter.base.current = cx_tree_iter_current; + + // visit the root node + iter.node = root; + if (root != NULL) { + iter.stack_capacity = 16; + iter.stack = malloc(sizeof(void *) * 16); + iter.stack[0] = root; + iter.counter = 1; + iter.depth = 1; + } else { + iter.stack_capacity = 0; + iter.stack = NULL; + iter.counter = 0; + iter.depth = 0; + } + + return iter; +} + +static bool cx_tree_visitor_valid(const void *it) { + const struct cx_tree_visitor_s *iter = it; + return iter->node != NULL; +} + +static void *cx_tree_visitor_current(const void *it) { + const struct cx_tree_visitor_s *iter = it; + return iter->node; +} + +cx_attr_nonnull +static void cx_tree_visitor_enqueue_siblings( + struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) { + node = tree_next(node); + while (node != NULL) { + struct cx_tree_visitor_queue_s *q; + q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q->depth = iter->queue_last->depth; + q->node = node; + iter->queue_last->next = q; + iter->queue_last = q; + node = tree_next(node); + } + iter->queue_last->next = NULL; +} + +static void cx_tree_visitor_next(void *it) { + struct cx_tree_visitor_s *iter = it; + // protect us from misuse + if (!iter->base.valid(iter)) return; + + ptrdiff_t const loc_next = iter->loc_next; + ptrdiff_t const loc_children = iter->loc_children; + + // add the children of the current node to the queue + // unless the skip flag is set + void *children; + if (iter->skip) { + iter->skip = false; + children = NULL; + } else { + children = tree_children(iter->node); + } + if (children != NULL) { + struct cx_tree_visitor_queue_s *q; + q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q->depth = iter->depth + 1; + q->node = children; + if (iter->queue_last == NULL) { + assert(iter->queue_next == NULL); + iter->queue_next = q; + } else { + iter->queue_last->next = q; + } + iter->queue_last = q; + cx_tree_visitor_enqueue_siblings(iter, children, loc_next); + } + + // check if there is a next node + if (iter->queue_next == NULL) { + iter->node = NULL; + return; + } + + // dequeue the next node + iter->node = iter->queue_next->node; + iter->depth = iter->queue_next->depth; + { + struct cx_tree_visitor_queue_s *q = iter->queue_next; + iter->queue_next = q->next; + if (iter->queue_next == NULL) { + assert(iter->queue_last == q); + iter->queue_last = NULL; + } + free(q); + } + + // increment the node counter + iter->counter++; +} + +CxTreeVisitor cx_tree_visitor( + void *root, + ptrdiff_t loc_children, + ptrdiff_t loc_next +) { + CxTreeVisitor iter; + iter.loc_children = loc_children; + iter.loc_next = loc_next; + + // initialize members + iter.skip = false; + iter.queue_next = NULL; + iter.queue_last = NULL; + + // assign base iterator functions + iter.base.mutating = false; + iter.base.remove = false; + iter.base.current_impl = NULL; + iter.base.valid = cx_tree_visitor_valid; + iter.base.next = cx_tree_visitor_next; + iter.base.current = cx_tree_visitor_current; + + // visit the root node + iter.node = root; + if (root != NULL) { + iter.counter = 1; + iter.depth = 1; + } else { + iter.counter = 0; + iter.depth = 0; + } + + return iter; +} + +static void cx_tree_add_link_duplicate( + void *original, void *duplicate, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next +) { + void *shared_parent = tree_parent(original); + if (shared_parent == NULL) { + cx_tree_link(original, duplicate, cx_tree_ptr_locations); + } else { + cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations); + } +} + +static void cx_tree_add_link_new( + void *parent, void *node, cx_tree_search_func sfunc, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next +) { + // check the current children one by one, + // if they could be children of the new node + void *child = tree_children(parent); + while (child != NULL) { + void *next = tree_next(child); + + if (sfunc(node, child) > 0) { + // the sibling could be a child -> re-link + cx_tree_link(node, child, cx_tree_ptr_locations); + } + + child = next; + } + + // add new node as new child + cx_tree_link(parent, node, cx_tree_ptr_locations); +} + +int cx_tree_add( + const void *src, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **cnode, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + *cnode = cfunc(src, cdata); + if (*cnode == NULL) return 1; + cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations); + + void *match = NULL; + int result = cx_tree_search( + root, + 0, + *cnode, + sfunc, + &match, + loc_children, + loc_next + ); + + if (result < 0) { + // node does not fit into the tree - return non-zero value + return 1; + } else if (result == 0) { + // data already found in the tree, link duplicate + cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations); + } else { + // closest match found, add new node + cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations); + } + + return 0; +} + +unsigned int cx_tree_add_look_around_depth = 3; + +size_t cx_tree_add_iter( + struct cx_iterator_base_s *iter, + size_t num, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + // erase the failed pointer + *failed = NULL; + + // iter not valid? cancel... + if (!iter->valid(iter)) return 0; + + size_t processed = 0; + void *current_node = root; + const void *elem; + + for (void **eptr; processed < num && + iter->valid(iter) && (eptr = iter->current(iter)) != NULL; + iter->next(iter)) { + elem = *eptr; + + // create the new node + void *new_node = cfunc(elem, cdata); + if (new_node == NULL) return processed; + cx_tree_zero_pointers(new_node, cx_tree_ptr_locations); + + // start searching from current node + void *match; + int result; + unsigned int look_around_retries = cx_tree_add_look_around_depth; + cx_tree_add_look_around_retry: + result = cx_tree_search( + current_node, + 0, + new_node, + sfunc, + &match, + loc_children, + loc_next + ); + + if (result < 0) { + // traverse upwards and try to find better parents + void *parent = tree_parent(current_node); + if (parent != NULL) { + if (look_around_retries > 0) { + look_around_retries--; + current_node = parent; + } else { + // look around retries exhausted, start from the root + current_node = root; + } + goto cx_tree_add_look_around_retry; + } else { + // no parents. so we failed + *failed = new_node; + return processed; + } + } else if (result == 0) { + // data already found in the tree, link duplicate + cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations); + // but stick with the original match, in case we needed a new root + current_node = match; + } else { + // closest match found, add new node as child + cx_tree_add_link_new(match, new_node, sfunc, + cx_tree_ptr_locations); + current_node = match; + } + + processed++; + } + return processed; +} + +size_t cx_tree_add_array( + const void *src, + size_t num, + size_t elem_size, + cx_tree_search_func sfunc, + cx_tree_node_create_func cfunc, + void *cdata, + void **failed, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + // erase failed pointer + *failed = NULL; + + // super special case: zero elements + if (num == 0) { + return 0; + } + + // special case: one element does not need an iterator + if (num == 1) { + void *node; + if (0 == cx_tree_add( + src, sfunc, cfunc, cdata, &node, root, + loc_parent, loc_children, loc_last_child, + loc_prev, loc_next)) { + return 1; + } else { + *failed = node; + return 0; + } + } + + // otherwise, create iterator and hand over to other function + CxIterator iter = cxIterator(src, elem_size, num); + return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc, + cfunc, cdata, failed, root, + loc_parent, loc_children, loc_last_child, + loc_prev, loc_next); +} + +static int cx_tree_default_insert_element( + CxTree *tree, + const void *data +) { + void *node; + if (tree->root == NULL) { + node = tree->node_create(data, tree); + if (node == NULL) return 1; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + tree->root = node; + tree->size = 1; + return 0; + } + int result = cx_tree_add(data, tree->search, tree->node_create, + tree, &node, tree->root, cx_tree_node_layout(tree)); + if (0 == result) { + tree->size++; + } else { + cxFree(tree->allocator, node); + } + return result; +} + +static size_t cx_tree_default_insert_many( + CxTree *tree, + struct cx_iterator_base_s *iter, + size_t n +) { + size_t ins = 0; + if (!iter->valid(iter)) return 0; + if (tree->root == NULL) { + // use the first element from the iter to create the root node + void **eptr = iter->current(iter); + void *node = tree->node_create(*eptr, tree); + if (node == NULL) return 0; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + tree->root = node; + ins = 1; + iter->next(iter); + } + void *failed; + ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, + tree, &failed, tree->root, cx_tree_node_layout(tree)); + tree->size += ins; + if (ins < n) { + cxFree(tree->allocator, failed); + } + return ins; +} + +static void *cx_tree_default_find( + CxTree *tree, + const void *subtree, + const void *data, + size_t depth +) { + if (tree->root == NULL) return NULL; + + void *found; + if (0 == cx_tree_search_data( + subtree, + depth, + data, + tree->search_data, + &found, + tree->loc_children, + tree->loc_next + )) { + return found; + } else { + return NULL; + } +} + +static cx_tree_class cx_tree_default_class = { + cx_tree_default_insert_element, + cx_tree_default_insert_many, + cx_tree_default_find +}; + +CxTree *cxTreeCreate( + const CxAllocator *allocator, + cx_tree_node_create_func create_func, + cx_tree_search_func search_func, + cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(create_func != NULL); + assert(search_func != NULL); + assert(search_data_func != NULL); + + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; + + tree->cl = &cx_tree_default_class; + tree->allocator = allocator; + tree->node_create = create_func; + tree->search = search_func; + tree->search_data = search_data_func; + tree->simple_destructor = NULL; + tree->advanced_destructor = (cx_destructor_func2) cxFree; + tree->destructor_data = (void *) allocator; + tree->loc_parent = loc_parent; + tree->loc_children = loc_children; + tree->loc_last_child = loc_last_child; + tree->loc_prev = loc_prev; + tree->loc_next = loc_next; + tree->root = NULL; + tree->size = 0; + + return tree; +} + +void cxTreeFree(CxTree *tree) { + if (tree == NULL) return; + if (tree->root != NULL) { + cxTreeClear(tree); + } + cxFree(tree->allocator, tree); +} + +CxTree *cxTreeCreateWrapped( + const CxAllocator *allocator, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + assert(root != NULL); + + CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; + + tree->cl = &cx_tree_default_class; + // set the allocator anyway, just in case... + tree->allocator = allocator; + tree->node_create = NULL; + tree->search = NULL; + tree->search_data = NULL; + tree->simple_destructor = NULL; + tree->advanced_destructor = NULL; + tree->destructor_data = NULL; + tree->loc_parent = loc_parent; + tree->loc_children = loc_children; + tree->loc_last_child = loc_last_child; + tree->loc_prev = loc_prev; + tree->loc_next = loc_next; + tree->root = root; + tree->size = cxTreeSubtreeSize(tree, root); + return tree; +} + +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +) { + size_t loc_parent = tree->loc_parent; + if (tree_parent(child) == NULL) { + tree->size++; + } + cx_tree_link(parent, child, cx_tree_node_layout(tree)); +} + +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +) { + cx_tree_link(parent, child, cx_tree_node_layout(tree)); + tree->size++; +} + +int cxTreeAddChild( + CxTree *tree, + void *parent, + const void *data) { + void *node = tree->node_create(data, tree); + if (node == NULL) return 1; + cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); + cx_tree_link(parent, node, cx_tree_node_layout(tree)); + tree->size++; + return 0; +} + +size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) { + CxTreeVisitor visitor = cx_tree_visitor( + subtree_root, + tree->loc_children, + tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.counter; +} + +size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) { + CxTreeVisitor visitor = cx_tree_visitor( + subtree_root, + tree->loc_children, + tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.depth; +} + +size_t cxTreeDepth(CxTree *tree) { + CxTreeVisitor visitor = cx_tree_visitor( + tree->root, tree->loc_children, tree->loc_next + ); + while (cxIteratorValid(visitor)) { + cxIteratorNext(visitor); + } + return visitor.depth; +} + +int cxTreeRemoveNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +) { + if (node == tree->root) return 1; + + // determine the new parent + ptrdiff_t loc_parent = tree->loc_parent; + void *new_parent = tree_parent(node); + + // first, unlink from the parent + cx_tree_unlink(node, cx_tree_node_layout(tree)); + + // then relink each child + ptrdiff_t loc_children = tree->loc_children; + ptrdiff_t loc_next = tree->loc_next; + void *child = tree_children(node); + while (child != NULL) { + // forcibly set the parent to NULL - we do not use the unlink function + // because that would unnecessarily modify the children linked list + tree_parent(child) = NULL; + + // update contents, if required + if (relink_func != NULL) { + relink_func(child, node, new_parent); + } + + // link to new parent + cx_tree_link(new_parent, child, cx_tree_node_layout(tree)); + + // proceed to next child + child = tree_next(child); + } + + // clear the linked list of the removed node + tree_children(node) = NULL; + ptrdiff_t loc_last_child = tree->loc_last_child; + if (loc_last_child >= 0) tree_last_child(node) = NULL; + + // the tree now has one member less + tree->size--; + + return 0; +} + +void cxTreeRemoveSubtree(CxTree *tree, void *node) { + if (node == tree->root) { + tree->root = NULL; + tree->size = 0; + return; + } + size_t subtree_size = cxTreeSubtreeSize(tree, node); + cx_tree_unlink(node, cx_tree_node_layout(tree)); + tree->size -= subtree_size; +} + +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +) { + int result = cxTreeRemoveNode(tree, node, relink_func); + if (result == 0) { + if (tree->simple_destructor) { + tree->simple_destructor(node); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, node); + } + return 0; + } else { + return result; + } +} + +void cxTreeDestroySubtree(CxTree *tree, void *node) { + cx_tree_unlink(node, cx_tree_node_layout(tree)); + CxTreeIterator iter = cx_tree_iterator( + node, true, + tree->loc_children, tree->loc_next + ); + cx_foreach(void *, child, iter) { + if (iter.exiting) { + if (tree->simple_destructor) { + tree->simple_destructor(child); + } + if (tree->advanced_destructor) { + tree->advanced_destructor(tree->destructor_data, child); + } + } + } + tree->size -= iter.counter; + if (node == tree->root) { + tree->root = NULL; + } +} diff --git a/ucx/ucx/allocator.h b/ucx/ucx/allocator.h deleted file mode 100644 index 2159272..0000000 --- a/ucx/ucx/allocator.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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 deleted file mode 100644 index 5b02ebf..0000000 --- a/ucx/ucx/array.h +++ /dev/null @@ -1,460 +0,0 @@ -/* - * 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 deleted file mode 100644 index 8b251a5..0000000 --- a/ucx/ucx/avl.h +++ /dev/null @@ -1,353 +0,0 @@ -/* - * 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 deleted file mode 100644 index 25f659f..0000000 --- a/ucx/ucx/buffer.h +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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 deleted file mode 100644 index 2bda6b8..0000000 --- a/ucx/ucx/list.h +++ /dev/null @@ -1,512 +0,0 @@ -/* - * 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 deleted file mode 100644 index 32bbbae..0000000 --- a/ucx/ucx/logging.h +++ /dev/null @@ -1,253 +0,0 @@ -/* - * 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 deleted file mode 100644 index 4a9b9a2..0000000 --- a/ucx/ucx/map.h +++ /dev/null @@ -1,549 +0,0 @@ -/* - * 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 deleted file mode 100644 index e4a8ce3..0000000 --- a/ucx/ucx/mempool.h +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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/stack.h b/ucx/ucx/stack.h deleted file mode 100644 index 6fe8a06..0000000 --- a/ucx/ucx/stack.h +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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 deleted file mode 100644 index 90b437a..0000000 --- a/ucx/ucx/string.h +++ /dev/null @@ -1,1201 +0,0 @@ -/* - * 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 deleted file mode 100644 index b45b1d1..0000000 --- a/ucx/ucx/test.h +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0944def..0000000 --- a/ucx/ucx/ucx.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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 deleted file mode 100644 index 642397a..0000000 --- a/ucx/ucx/utils.h +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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/ui/Makefile b/ui/Makefile index 865a03c..a0d1148 100644 --- a/ui/Makefile +++ b/ui/Makefile @@ -33,7 +33,7 @@ OBJ_DIR = ../build/ include common/objs.mk -UI_LIB = ../build/lib/libuitk.$(LIB_EXT) +UI_LIB = ../build/lib/libuitk$(LIB_EXT) include $(TOOLKIT)/objs.mk OBJ = $(TOOLKITOBJS) $(COMMONOBJS) @@ -43,5 +43,5 @@ all: $(UI_LIB) include $(TOOLKIT)/Makefile $(COMMON_OBJPRE)%.o: common/%.c - $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + $(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $< diff --git a/ui/motif/label.c b/ui/common/condvar.c similarity index 57% rename from ui/motif/label.c rename to ui/common/condvar.c index dd1444e..c61e70c 100644 --- a/ui/motif/label.c +++ b/ui/common/condvar.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,45 +26,45 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include +#include "condvar.h" + #include -#include "label.h" -#include "container.h" -#include "../common/context.h" -#include "../common/object.h" -#include +UiCondVar* ui_condvar_create(void) { + UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar)); + var->var.data = NULL; + var->var.intdata = 0; + var->set = 0; + pthread_mutex_init(&var->lock, NULL); + pthread_cond_init(&var->cond, NULL); + return (UiCondVar*)var; +} -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); +void ui_condvar_wait(UiCondVar *var) { + UiPosixCondVar *p = (UiPosixCondVar*)var; + pthread_mutex_lock(&p->lock); + if(!p->set) { + pthread_cond_wait(&p->cond, &p->lock); + } + p->set = 0; + pthread_mutex_unlock(&p->lock); - 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); +void ui_condvar_signal(UiCondVar *var, void *data, int intdata) { + UiPosixCondVar *p = (UiPosixCondVar*)var; + pthread_mutex_lock(&p->lock); + p->var.data = data; + p->var.intdata = intdata; + p->set = 1; + pthread_cond_signal(&p->cond); + pthread_mutex_unlock(&p->lock); +} + +void ui_condvar_destroy(UiCondVar *var) { + UiPosixCondVar *p = (UiPosixCondVar*)var; + pthread_mutex_destroy(&p->lock); + pthread_cond_destroy(&p->cond); + free(p); - return widget; } diff --git a/ui/common/condvar.h b/ui/common/condvar.h new file mode 100644 index 0000000..bf4b8e4 --- /dev/null +++ b/ui/common/condvar.h @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_CONDVAR_H +#define UIC_CONDVAR_H + +#include "../ui/toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiPosixCondVar { + UiCondVar var; + int set; + pthread_mutex_t lock; + pthread_cond_t cond; +} UiPosixCondVar; + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_CONDVAR_H */ + diff --git a/ui/common/context.c b/ui/common/context.c index 628a4b4..3483eae 100644 --- a/ui/common/context.c +++ b/ui/common/context.c @@ -32,15 +32,20 @@ #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); + CxMempool *mp = cxBasicMempoolCreate(32); global_context = uic_context(NULL, mp); } @@ -48,17 +53,22 @@ UiContext* ui_global_context(void) { return global_context; } -UiContext* uic_context(UiObject *toplevel, UcxMempool *mp) { - UiContext *ctx = ucx_mempool_malloc(mp, sizeof(UiContext)); +UiContext* uic_context(UiObject *toplevel, CxMempool *mp) { + UiContext *ctx = cxMalloc(mp->allocator, sizeof(UiContext)); memset(ctx, 0, sizeof(UiContext)); - ctx->mempool = mp; + ctx->mp = mp; + ctx->allocator = mp->allocator; ctx->obj = toplevel; - ctx->vars = ucx_map_new_a(mp->allocator, 16); + ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); + + ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS); + ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); + ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document2; -#ifdef UI_GTK +#if UI_GTK2 || UI_GTK3 if(toplevel && toplevel->widget) { ctx->accel_group = gtk_accel_group_new(); gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group); @@ -72,9 +82,14 @@ UiContext* uic_root_context(UiContext *ctx) { return ctx->parent ? uic_root_context(ctx->parent) : ctx; } +void uic_context_prepare_close(UiContext *ctx) { + cxListClear(ctx->groups); + cxListClear(ctx->group_widgets); +} + void uic_context_attach_document(UiContext *ctx, void *document) { - ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document); - ctx->document = ctx->documents->data; + cxListAdd(ctx->documents, document); + ctx->document = document; UiContext *doc_ctx = ui_document_context(document); @@ -82,25 +97,18 @@ void uic_context_attach_document(UiContext *ctx, void *document) { // 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(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) { + CxIterator i = cxMapIterator(var_ctx->vars_unbound); + cx_foreach(CxMapEntry*, entry, i) { + printf("attach %s\n", entry->key->data); + UiVar *var = entry->value; + UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) { // bind var to document var uic_copy_binding(var, docvar, TRUE); - rmkeys[numkeys++] = key; // save the key for removal + cxIteratorFlagRemoval(i); } } - // 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; @@ -108,56 +116,63 @@ void uic_context_attach_document(UiContext *ctx, void *document) { } static void uic_context_unbind_vars(UiContext *ctx) { - UcxMapIterator i = ucx_map_iterator(ctx->vars); - UiVar *var; - UCX_MAP_FOREACH(key, var, i) { + CxIterator i = cxMapIterator(ctx->vars); + cx_foreach(CxMapEntry*, entry, i) { + UiVar *var = entry->value; if(var->from && var->from_ctx) { uic_save_var2(var); uic_copy_binding(var, var->from, FALSE); - ucx_map_put(var->from_ctx->vars_unbound, key, var->from); + cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from); var->from_ctx = ctx; } } - UCX_FOREACH(elm, ctx->documents) { - UiContext *subctx = ui_document_context(elm->data); - uic_context_unbind_vars(subctx); + if(ctx->documents) { + i = cxListIterator(ctx->documents); + cx_foreach(void *, doc, i) { + UiContext *subctx = ui_document_context(doc); + uic_context_unbind_vars(subctx); + } } } void uic_context_detach_document2(UiContext *ctx, void *document) { // find the document in the documents list - 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 + ssize_t docIndex = cxListFind(ctx->documents, document); + if(docIndex < 0) { + return; } - ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc); - ctx->document = ctx->documents ? ctx->documents->data : NULL; + cxListRemove(ctx->documents, docIndex); + ctx->document = cxListAt(ctx->documents, 0); UiContext *docctx = ui_document_context(document); uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent } void uic_context_detach_all(UiContext *ctx) { - UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL); - UCX_FOREACH(elm, ls) { - ctx->detach_document2(ctx, elm->data); + // copy list + CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + CxIterator i = cxListIterator(ctx->documents); + cx_foreach(void *, doc, i) { + cxListAdd(ls, doc); + } + + // detach documents + i = cxListIterator(ls); + cx_foreach(void *, doc, i) { + ctx->detach_document2(ctx, doc); } - ucx_list_free(ls); + + cxListFree(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); +static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) { + UiVar *var = cxMapGet(ctx->vars, key); + if(!var && ctx->documents) { + CxIterator i = cxListIterator(ctx->documents); + cx_foreach(void *, doc, i) { + UiContext *subctx = ui_document_context(doc); var = ctx_getvar(subctx, key); if(var) { break; @@ -168,11 +183,18 @@ static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) { } UiVar* uic_get_var(UiContext *ctx, const char *name) { - UcxKey key = ucx_key(name, strlen(name)); + CxHashKey key = cx_hash_key(name, strlen(name)); return ctx_getvar(ctx, key); } UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { + if(ctx->vars_unbound) { + UiVar *unbound = cxMapGet(ctx->vars_unbound, name); + if(unbound) { + return unbound; + } + } + UiVar *var = uic_get_var(ctx, name); if(var) { if(var->type == type) { @@ -188,14 +210,25 @@ UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { var->from = NULL; var->from_ctx = ctx; + cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); + if(!ctx->vars_unbound) { - ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16); + ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16); } - ucx_map_cstr_put(ctx->vars_unbound, name, var); + cxMapPut(ctx->vars_unbound, name, var); return var; } +UiVar* uic_create_value_var(UiContext* ctx, void* value) { + UiVar *var = (UiVar*)ui_malloc(ctx, sizeof(UiVar)); + var->from = NULL; + var->from_ctx = ctx; + var->value = value; + var->type = UI_VAR_SPECIAL; + return var; +} + void* uic_create_value(UiContext *ctx, UiVarType type) { void *val = NULL; switch(type) { @@ -224,10 +257,25 @@ void* uic_create_value(UiContext *ctx, UiVarType type) { val = ui_range_new(ctx, NULL); break; } + case UI_VAR_GENERIC: { + val = ui_generic_new(ctx, NULL); + } } return val; } + +UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) { + if (value) { + return uic_create_value_var(current, value); + } + if (varname) { + return uic_create_var(toplevel, varname, type); + } + return NULL; +} + + void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { // check type if(from->type != to->type) { @@ -283,11 +331,24 @@ void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { break; } case UI_VAR_LIST: { - UiList *f = fromvalue; + // TODO: not sure how correct this is + + UiList *f = from->value; UiList *t = to->value; - if(!f->obj) break; - uic_list_copy(f, t); - t->update(t, -1); + if (f->obj) { + t->obj = f->obj; + t->update = f->update; + t->getselection = f->getselection; + t->setselection = f->setselection; + } + + UiVar tmp = *from; + *from = *to; + *to = tmp; + + UiList* t2 = to->value; + ui_notify(t2->observers, NULL); + break; } case UI_VAR_RANGE: { @@ -300,6 +361,14 @@ void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { t->set(t, t->value); break; } + case UI_VAR_GENERIC: { + UiGeneric *f = fromvalue; + UiGeneric *t = to->value; + if(!f->obj) break; + uic_generic_copy(f, t); + t->set(t, t->value, t->type); + break; + } } } @@ -312,6 +381,7 @@ void uic_save_var2(UiVar *var) { case UI_VAR_TEXT: uic_text_save(var->value); break; case UI_VAR_LIST: break; case UI_VAR_RANGE: uic_range_save(var->value); break; + case UI_VAR_GENERIC: uic_generic_save(var->value); break; } } @@ -324,6 +394,7 @@ void uic_unbind_var(UiVar *var) { case UI_VAR_TEXT: uic_text_unbind(var->value); break; case UI_VAR_LIST: uic_list_unbind(var->value); break; case UI_VAR_RANGE: uic_range_unbind(var->value); break; + case UI_VAR_GENERIC: uic_generic_unbind(var->value); break; } } @@ -358,9 +429,9 @@ void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) { 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) { + size_t oldcount = cxMapSize(ctx->vars); + cxMapPut(ctx->vars, name, var); + if(cxMapSize(ctx->vars) != oldcount + 1) { fprintf(stderr, "UiError: var '%s' already exists\n", name); } @@ -375,8 +446,7 @@ void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) { } void uic_remove_bound_var(UiContext *ctx, UiVar *var) { - // TODO: implement - printf("TODO: implement uic_remove_bound_var\n"); + // TODO } @@ -395,10 +465,14 @@ void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) { ctx->close_data = udata; } +UIEXPORT void ui_context_destroy(UiContext *ctx) { + cxMempoolFree(ctx->mp); +} + 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); + if(cxListFind(ctx->groups, &group) == -1) { + cxListAdd(ctx->groups, &group); } // enable/disable group widgets @@ -406,10 +480,9 @@ void ui_set_group(UiContext *ctx, int group) { } void ui_unset_group(UiContext *ctx, int group) { - int i = ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL); + int i = cxListFind(ctx->groups, &group); if(i != -1) { - UcxList *elm = ucx_list_get(ctx->groups, i); - ctx->groups = ucx_list_remove_a(ctx->mempool->allocator, ctx->groups, elm); + cxListRemove(ctx->groups, i); } // enable/disable group widgets @@ -417,28 +490,16 @@ void ui_unset_group(UiContext *ctx, int group) { } 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; + *ngroups = cxListSize(ctx->groups); + return cxListAt(ctx->groups, 0); } void uic_check_group_widgets(UiContext *ctx) { int ngroups = 0; int *groups = ui_active_groups(ctx, &ngroups); - UCX_FOREACH(elm, ctx->group_widgets) { - UiGroupWidget *gw = elm->data; + CxIterator i = cxListIterator(ctx->group_widgets); + cx_foreach(UiGroupWidget *, gw, i) { char *check = calloc(1, gw->numgroups); for(int i=0;ienable(gw->widget, enable); } - - if(groups) { - free(groups); - } } void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { // get groups - UcxList *groups = NULL; + CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); 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); + cxListAdd(groups, &group); } va_end(ap); uic_add_group_widget(ctx, widget, enable, groups); - ucx_list_free(groups); + cxListFree(groups); +} + +size_t uic_group_array_size(const int *groups) { + int i; + for(i=0;groups[i] >= 0;i++) { } + return i; +} + +void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) { + uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups)); } -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups) { - UcxMempool *mp = ctx->mempool; - UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget)); +void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { + const CxAllocator *a = ctx->allocator; + UiGroupWidget gw; + + gw.widget = widget; + gw.enable = enable; + gw.numgroups = numgroups; + gw.groups = cxCalloc(a, numgroups, sizeof(int)); - 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; + // copy groups + if(groups) { + memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); } - ctx->group_widgets = ucx_list_append_a( - mp->allocator, - ctx->group_widgets, - gw); + cxListAdd(ctx->group_widgets, &gw); +} + +void uic_remove_group_widget(UiContext *ctx, void *widget) { + (void)cxListFindRemove(ctx->group_widgets, widget); +} + +UIEXPORT void *ui_allocator(UiContext *ctx) { + return (void*)ctx->allocator; +} + +void* ui_cx_mempool(UiContext *ctx) { + return ctx->mp; } void* ui_malloc(UiContext *ctx, size_t size) { - return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL; + return ctx ? cxMalloc(ctx->allocator, size) : NULL; } void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) { - return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL; + return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL; } void ui_free(UiContext *ctx, void *ptr) { - if(ctx) { - ucx_mempool_free(ctx->mempool, ptr); + if(ctx && ptr) { + cxFree(ctx->allocator, ptr); } } void* ui_realloc(UiContext *ctx, void *ptr, size_t size) { - return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL; + return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL; } +UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) { + if(!ctx) { + return NULL; + } + cxstring s = cx_str(str); + cxmutstr d = cx_strdup_a(ctx->allocator, s); + return d.ptr; +} diff --git a/ui/common/context.h b/ui/common/context.h index 14b2ed8..fdcd6a8 100644 --- a/ui/common/context.h +++ b/ui/common/context.h @@ -30,9 +30,11 @@ #define UIC_CONTEXT_H #include "../ui/toolkit.h" -#include -#include -#include +#include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { @@ -52,22 +54,24 @@ enum UiVarType { UI_VAR_STRING, UI_VAR_TEXT, UI_VAR_LIST, - UI_VAR_RANGE + UI_VAR_RANGE, + UI_VAR_GENERIC }; struct UiContext { UiContext *parent; UiObject *obj; - UcxMempool *mempool; + CxMempool *mp; + const CxAllocator *allocator; void *document; - UcxList *documents; + CxList *documents; - UcxMap *vars; // manually created context vars - UcxMap *vars_unbound; // unbound vars created by widgets + CxMap *vars; // manually created context vars + CxMap *vars_unbound; // unbound vars created by widgets - UcxList *groups; // int list - UcxList *group_widgets; // UiGroupWidget* list + CxList *groups; // int list + CxList *group_widgets; // UiGroupWidget list void (*attach_document)(UiContext *ctx, void *document); void (*detach_document2)(UiContext *ctx, void *document); @@ -75,8 +79,14 @@ struct UiContext { char *title; #ifdef UI_GTK +#if GTK_CHECK_VERSION(4, 0, 0) + GActionMap *action_map; +#elif UI_GTK2 || UI_GTK3 GtkAccelGroup *accel_group; +#endif #endif + + ui_callback close_callback; void *close_data; @@ -100,19 +110,24 @@ struct UiGroupWidget { void uic_init_global_context(void); -UiContext* uic_context(UiObject *toplevel, UcxMempool *mp); +UiContext* uic_context(UiObject *toplevel, CxMempool *mp); UiContext* uic_root_context(UiContext *ctx); void uic_context_set_document(UiContext *ctx, void *document); // deprecated void uic_context_detach_document(UiContext *ctx); // deprecated +void uic_context_prepare_close(UiContext *ctx); + void uic_context_attach_document(UiContext *ctx, void *document); void uic_context_detach_document2(UiContext *ctx, void *document); void uic_context_detach_all(UiContext *ctx); UiVar* uic_get_var(UiContext *ctx, const char *name); UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); +UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); +UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type); + void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc); void uic_save_var2(UiVar *var); void uic_unbind_var(UiVar *var); @@ -121,9 +136,11 @@ void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value); void uic_remove_bound_var(UiContext *ctx, UiVar *var); +size_t uic_group_array_size(const int *groups); void uic_check_group_widgets(UiContext *ctx); -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups); - +void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups); +void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups); +void uic_remove_group_widget(UiContext *ctx, void *widget); #ifdef __cplusplus } diff --git a/ui/common/document.c b/ui/common/document.c index f225949..fc3c11c 100644 --- a/ui/common/document.c +++ b/ui/common/document.c @@ -32,10 +32,16 @@ #include "document.h" -static UcxMap *documents; +#include +#include + + +static CxMap *documents; void uic_docmgr_init() { - documents = ucx_map_new(32); + if (!documents) { + documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); + } } void ui_set_document(UiObject *obj, void *document) { @@ -77,25 +83,40 @@ void* ui_get_subdocument(void *document) { } void* ui_document_new(size_t size) { - UcxMempool *mp = ucx_mempool_new(256); - UiContext *ctx = ucx_mempool_calloc(mp, 1, sizeof(UiContext)); + CxMempool *mp = cxMempoolCreate(256, NULL); + const CxAllocator *a = mp->allocator; + UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext)); + ctx->mp = mp; ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document2; - ctx->mempool = mp; - ctx->vars = ucx_map_new_a(mp->allocator, 16); + ctx->allocator = a; + ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16); - void *document = ucx_mempool_calloc(mp, 1, size); - ucx_map_put(documents, ucx_key(&document, sizeof(void*)), ctx); + void *document = cxCalloc(a, size, 1); + cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx); return document; } void ui_document_destroy(void *doc) { - // TODO + UiContext *ctx = ui_document_context(doc); + if(ctx) { + UiEvent ev; + ev.window = NULL; + ev.document = doc; + ev.obj = NULL; + ev.eventdata = NULL; + ev.intval = 0; + + if(ctx->close_callback) { + ctx->close_callback(&ev, ctx->close_data); + } + cxMempoolFree(ctx->mp); + } } UiContext* ui_document_context(void *doc) { if(doc) { - return ucx_map_get(documents, ucx_key(&doc, sizeof(void*))); + return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*))); } else { return NULL; } diff --git a/ui/common/menu.c b/ui/common/menu.c new file mode 100644 index 0000000..4b23418 --- /dev/null +++ b/ui/common/menu.c @@ -0,0 +1,331 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "menu.h" + +#include +#include +#include + +#include +#include + + +static UiMenuBuilder *current_builder; +static UiMenuBuilder global_builder; + +static int menu_item_counter = 0; + +void uic_menu_init(void) { + global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + current_builder = &global_builder; +} + +static void add_menu(UiMenu *menu) { + cx_linked_list_add( + (void**)¤t_builder->menus_begin, + (void**)¤t_builder->menus_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + menu); +} + +static void add_item(UiMenuItemI *item) { + UiMenu *menu = cxListAt(current_builder->current, 0); + cx_linked_list_add( + (void**)&menu->items_begin, + (void**)&menu->items_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + item); +} + +static void mitem_set_id(UiMenuItemI *item) { + snprintf(item->id, 8, "%x", menu_item_counter++); +} + +static char* nl_strdup(const char* s) { + return s ? strdup(s) : NULL; +} + +int* uic_copy_groups(const int* groups, size_t *ngroups) { + *ngroups = 0; + if (!groups) { + return NULL; + } + + size_t n; + for (n = 0; groups[n] > -1; n++) { } + + if (ngroups > 0) { + int* newarray = calloc(n+1, sizeof(int)); + memcpy(newarray, groups, n * sizeof(int)); + newarray[n] = -1; + *ngroups = n; + return newarray; + } + return NULL; +} + +void ui_menu_create(const char *label) { + // create menu + UiMenu *menu = malloc(sizeof(UiMenu)); + mitem_set_id(&menu->item); + menu->item.prev = NULL; + menu->item.next = NULL; + menu->item.type = UI_MENU; + + menu->label = label; + menu->items_begin = NULL; + menu->items_end = NULL; + menu->parent = NULL; + + menu->end = 0; + + if (cxListSize(current_builder->current) == 0) { + add_menu(menu); + } + else { + add_item((UiMenuItemI*)menu); + } + uic_add_menu_to_stack(menu); +} + +UIEXPORT void ui_menu_end(void) { + cxListRemove(current_builder->current, 0); + if(cxListSize(current_builder->current) == 0) { + current_builder = &global_builder; + } +} + + + +void ui_menuitem_create(UiMenuItemArgs args) { + UiMenuItem* item = malloc(sizeof(UiMenuItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->userdata = args.onclickdata; + item->callback = args.onclick; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menuseparator() { + UiMenuItemI *item = malloc(sizeof(UiMenuItemI)); + item->id[0] = 0; + item->prev = NULL; + item->next = NULL; + item->type = UI_MENU_SEPARATOR; + + add_item((UiMenuItemI*)item); +} + +void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) { + UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_CHECK_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->varname = nl_strdup(args.varname); + item->userdata = args.onchangedata; + item->callback = args.onchange; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitem_create(UiMenuToggleItemArgs args) { + UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIO_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->varname = nl_strdup(args.varname); + item->userdata = args.onchangedata; + item->callback = args.onchange; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_itemlist_create(UiMenuItemListArgs args) { + UiMenuItemList*item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_ITEM_LIST; + item->getvalue = args.getvalue; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + item->addseparator = args.addseparator; + + add_item((UiMenuItemI*)item); +} + +void ui_menu_checkitemlist_create(UiMenuItemListArgs args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_CHECKITEM_LIST; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitemlist_create(UiMenuItemListArgs args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIOITEM_LIST; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + + add_item((UiMenuItemI*)item); +} + + +void uic_add_menu_to_stack(UiMenu* menu) { + cxListInsert(current_builder->current, 0, menu); +} + +UiMenu* uic_get_menu_list(void) { + return current_builder->menus_begin; +} + +UIEXPORT void ui_menu_close(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + menu->end = 1; +} + +UIEXPORT int ui_menu_is_open(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + if (menu->end) { + ui_menu_end(); + return 0; + } + return 1; +} + + +void ui_contextmenu_builder(UiMenuBuilder **out_builder) { + UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder)); + builder->menus_begin = NULL; + builder->menus_end = NULL; + builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + current_builder = builder; + *out_builder = builder; + + ui_menu_create(NULL); +} + + + +static void free_menuitem(UiMenuItemI *item) { + switch(item->type) { + default: break; + case UI_MENU: { + UiMenu *menu = (UiMenu*)item; + UiMenuItemI *m = menu->items_begin; + while(m) { + UiMenuItemI *next = m->next; + free_menuitem(m); + m = next; + } + break; + } + case UI_MENU_ITEM: { + UiMenuItem *i = (UiMenuItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + break; + } + case UI_MENU_CHECK_ITEM: { + UiMenuCheckItem *i = (UiMenuCheckItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + free(i->varname); + break; + } + case UI_MENU_RADIO_ITEM: { + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + //free(i->varname); + break; + } + case UI_MENU_ITEM_LIST: { + break; + } + case UI_MENU_CHECKITEM_LIST: { + break; + } + case UI_MENU_RADIOITEM_LIST: { + break; + } + } + + free(item); +} + +void ui_menubuilder_free(UiMenuBuilder *builder) { + UiMenuItemI *m = &builder->menus_begin->item; + while(m) { + UiMenuItemI *next = m->next; + free_menuitem(m); + m = next; + } + cxListFree(builder->current); + free(builder); +} diff --git a/ui/motif/menu.h b/ui/common/menu.h similarity index 55% rename from ui/motif/menu.h rename to ui/common/menu.h index d8919cb..1ed01b0 100644 --- a/ui/motif/menu.h +++ b/ui/common/menu.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2023 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,102 +26,116 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MENU_H -#define MENU_H +#ifndef UIC_MENU_H +#define UIC_MENU_H #include "../ui/menu.h" -#include -#ifdef __cplusplus +#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 UiMenuCheckItem UiMenuCheckItem; +typedef struct UiMenuRadioItem UiMenuRadioItem; typedef struct UiMenuItemList UiMenuItemList; + +enum UiMenuItemType { + UI_MENU = 0, + UI_MENU_ITEM, + UI_MENU_CHECK_ITEM, + UI_MENU_RADIO_ITEM, + UI_MENU_ITEM_LIST, + UI_MENU_CHECKITEM_LIST, + UI_MENU_RADIOITEM_LIST, + UI_MENU_SEPARATOR +}; -typedef struct UiActiveMenuItemList UiActiveMenuItemList; - -typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*); +typedef enum UiMenuItemType UiMenuItemType; struct UiMenuItemI { - ui_menu_add_f add_to; + UiMenuItemI *prev; + UiMenuItemI *next; + UiMenuItemType type; + char id[8]; }; struct UiMenu { UiMenuItemI item; - char *label; - UcxList *items; + const char *label; + UiMenuItemI *items_begin; + UiMenuItemI *items_end; UiMenu *parent; + int end; }; struct UiMenuItem { UiMenuItemI item; ui_callback callback; char *label; - void *userdata; - UcxList *groups; -}; - -struct UiStMenuItem { - UiMenuItemI item; - ui_callback callback; char *stockid; + char *icon; void *userdata; - UcxList *groups; + int *groups; + size_t ngroups; }; -struct UiCheckItem { +struct UiMenuCheckItem { UiMenuItemI item; char *label; + char *stockid; + char *icon; + char *varname; ui_callback callback; void *userdata; + int *groups; + size_t ngroups; }; -struct UiCheckItemNV { +struct UiMenuRadioItem { UiMenuItemI item; char *label; + char *stockid; + char *icon; char *varname; + ui_callback callback; + void *userdata; + int *groups; + size_t ngroups; }; struct UiMenuItemList { - UiMenuItemI item; - ui_callback callback; - void *userdata; - UiList *list; + UiMenuItemI item; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; + char *varname; + UiBool addseparator; }; -struct UiActiveMenuItemList { - UiObject *object; - Widget menu; - int index; - int oldcount; - UiList *list; - ui_callback callback; - void *userdata; + + +struct UiMenuBuilder { + UiMenu *menus_begin; + UiMenu *menus_end; + CxList *current; }; -void ui_create_menubar(UiObject *obj); +void uic_menu_init(void); -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); +UiMenu* uic_get_menu_list(void); -void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list); -void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata); +void uic_add_menu_to_stack(UiMenu* menu); +int* uic_copy_groups(const int* groups, size_t *ngroups); -#ifdef __cplusplus +#ifdef __cplusplus } #endif -#endif /* MENU_H */ +#endif /* UIC_MENU_H */ diff --git a/ui/common/object.c b/ui/common/object.c index 42a31be..2c10597 100644 --- a/ui/common/object.c +++ b/ui/common/object.c @@ -32,6 +32,8 @@ #include "object.h" #include "context.h" +#include "../ui/container.h" + void ui_end(UiObject *obj) { if(!obj->next) { return; @@ -49,11 +51,51 @@ void ui_end(UiObject *obj) { } } +void ui_end_new(UiObject *obj) { + if(!obj->container_end) { + return; + } + UiContainerX *rm = obj->container_end; + uic_object_pop_container(obj); + ui_free(obj->ctx, rm); +} + +void ui_object_ref(UiObject *obj) { + obj->ref++; +} + +int ui_object_unref(UiObject *obj) { + // it is possible to have 0 references, in case + // a window was created but ui_show was never called + if(obj->ref == 0 || --obj->ref == 0) { + if(obj->destroy) { + obj->destroy(obj); + } + uic_object_destroy(obj); + return 0; + } + return 1; +} + +void uic_object_destroy(UiObject *obj) { + if(obj->ctx->close_callback) { + UiEvent ev; + ev.window = obj->window; + ev.document = obj->ctx->document; + ev.obj = obj; + ev.eventdata = NULL; + ev.intval = 0; + obj->ctx->close_callback(&ev, obj->ctx->close_data); + } + cxMempoolFree(obj->ctx->mp); +} UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) { - UiContext *ctx = toplevel->ctx; - - UiObject *newobj = ucx_mempool_calloc(ctx->mempool, 1, sizeof(UiObject)); + return uic_ctx_object_new(toplevel->ctx, widget); +} + +UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) { + UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject)); newobj->ctx = ctx; newobj->widget = widget; @@ -79,3 +121,23 @@ UiObject* uic_current_obj(UiObject *toplevel) { UiContainer* uic_get_current_container(UiObject *obj) { return uic_current_obj(obj)->container; } + +void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer) { + newcontainer->prev = toplevel->container_end; + if(toplevel->container_end) { + toplevel->container_end->next = newcontainer; + toplevel->container_end = newcontainer; + } else { + toplevel->container_begin = newcontainer; + toplevel->container_end = newcontainer; + } +} + +void uic_object_pop_container(UiObject *toplevel) { + toplevel->container_end = toplevel->container_end->prev; + if(toplevel->container_end) { + toplevel->container_end->next = NULL; + } else { + toplevel->container_begin = NULL; + } +} diff --git a/ui/common/object.h b/ui/common/object.h index cf0bd3c..a5011a0 100644 --- a/ui/common/object.h +++ b/ui/common/object.h @@ -35,11 +35,18 @@ extern "C" { #endif +void uic_object_destroy(UiObject *obj); + UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget); +UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget); void uic_obj_add(UiObject *toplevel, UiObject *ctobj); UiObject* uic_current_obj(UiObject *toplevel); -UiContainer* uic_get_current_container(UiObject *obj); +UiContainer* uic_get_current_container(UiObject *obj); // deprecated + +void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer); +void uic_object_pop_container(UiObject *toplevel); + #ifdef __cplusplus diff --git a/ui/common/objs.mk b/ui/common/objs.mk index 2bb35a7..b8980af 100644 --- a/ui/common/objs.mk +++ b/ui/common/objs.mk @@ -33,7 +33,13 @@ COMMON_OBJ = context.o COMMON_OBJ += document.o COMMON_OBJ += object.o COMMON_OBJ += types.o +COMMON_OBJ += menu.o COMMON_OBJ += properties.o +COMMON_OBJ += menu.o +COMMON_OBJ += toolbar.o +COMMON_OBJ += ucx_properties.o +COMMON_OBJ += threadpool.o +COMMON_OBJ += condvar.o TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%) TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c) diff --git a/ui/common/properties.c b/ui/common/properties.c index 177e98a..45a9b4e 100644 --- a/ui/common/properties.c +++ b/ui/common/properties.c @@ -32,13 +32,18 @@ #include #include +#include "../ui/toolkit.h" + + #include "properties.h" -#include -#include -#include +#include +#include + +#include +#include "ucx_properties.h" -static UiProperties *application_properties; -static UiProperties *language; +static CxMap *application_properties; +static CxMap *language; #ifndef UI_COCOA @@ -47,53 +52,66 @@ static char *pixmaps_dir; #endif -char* ui_getappdir() { + +char* ui_getappdir(void) { if(ui_appname() == NULL) { return NULL; } - + return ui_configfile(NULL); } +#ifndef _WIN32 +#define UI_PATH_SEPARATOR '/' +#define UI_ENV_HOME "HOME" +#else +#define UI_PATH_SEPARATOR '\\' +#define UI_ENV_HOME "USERPROFILE" +#endif + char* ui_configfile(char *name) { - char *appname = ui_appname(); + const char *appname = ui_appname(); if(!appname) { return NULL; } - UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); // add base dir - char *homeenv = getenv("HOME"); + char *homeenv = getenv(UI_ENV_HOME); if(homeenv == NULL) { - ucx_buffer_free(buf); + cxBufferDestroy(&buf); return NULL; } - sstr_t home = sstr(homeenv); + cxstring home = cx_str(homeenv); - ucx_buffer_write(home.ptr, 1, home.length, buf); - if(home.ptr[home.length-1] != '/') { - ucx_buffer_putc(buf, '/'); + cxBufferWrite(home.ptr, 1, home.length, &buf); + if(home.ptr[home.length-1] != UI_PATH_SEPARATOR) { + cxBufferPut(&buf, UI_PATH_SEPARATOR); } #ifdef UI_COCOA // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/ - ucx_buffer_puts(buf, "Library/Application Support/"); + cxBufferPutString(&buf, "Library/Application Support/"); +#elif defined(_WIN32) + // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/ + cxBufferPutString(&buf, "AppData\\Local\\"); #else // app dir is $HOME/.$APPNAME/ - ucx_buffer_putc(buf, '.'); + cxBufferPut(&buf, '.'); #endif - ucx_buffer_puts(buf, appname); - ucx_buffer_putc(buf, '/'); + cxBufferPutString(&buf, appname); + cxBufferPut(&buf, UI_PATH_SEPARATOR); // add file name if(name) { - ucx_buffer_puts(buf, name); + cxBufferPutString(&buf, name); } - char *path = buf->space; - free(buf); - return path; + cxBufferPut(&buf, 0); + + return buf.space; } static int ui_mkdir(char *path) { @@ -105,7 +123,7 @@ static int ui_mkdir(char *path) { } void uic_load_app_properties() { - application_properties = ucx_map_new(128); + application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); if(!ui_appname()) { // applications without name cannot load app properties @@ -144,58 +162,67 @@ void uic_load_app_properties() { free(path); } -void uic_store_app_properties() { +int uic_store_app_properties() { char *path = ui_configfile("application.properties"); if(!path) { - return; + return 1; } FILE *file = fopen(path, "w"); if(!file) { fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path); free(path); - return; + return 1; } + int ret = 0; if(ucx_properties_store(application_properties, file)) { fprintf(stderr, "Ui Error: Cannot store application properties.\n"); + ret = 1; } fclose(file); free(path); + + return ret; } -char* ui_get_property(char *name) { - return ucx_map_cstr_get(application_properties, name); +const char* ui_get_property(const char *name) { + return cxMapGet(application_properties, name); } -void ui_set_property(char *name, char *value) { - ucx_map_cstr_put(application_properties, name, value); +void ui_set_property(const char *name, const char *value) { + cxMapPut(application_properties, name, (char*)value); } -void ui_set_default_property(char *name, char *value) { - char *v = ucx_map_cstr_get(application_properties, name); +const char* ui_set_default_property(const char *name, const char *value) { + const char *v = cxMapGet(application_properties, name); if(!v) { - ucx_map_cstr_put(application_properties, name, value); + cxMapPut(application_properties, name, (char*)value); + v = value; } + return v; } +int ui_properties_store(void) { + return uic_store_app_properties(); +} static char* uic_concat_path(const char *base, const char *p, const char *ext) { size_t baselen = strlen(base); - UcxBuffer *buf = ucx_buffer_new(NULL, 32, UCX_BUFFER_AUTOEXTEND); + CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(baselen > 0) { - ucx_buffer_write(base, 1, baselen, buf); + cxBufferWrite(base, 1, baselen, buf); if(base[baselen - 1] != '/') { - ucx_buffer_putc(buf, '/'); + cxBufferPut(buf, '/'); } } - ucx_buffer_write(p, 1, strlen(p), buf); + cxBufferWrite(p, 1, strlen(p), buf); if(ext) { - ucx_buffer_write(ext, 1, strlen(ext), buf); + cxBufferWrite(ext, 1, strlen(ext), buf); } char *str = buf->space; @@ -263,7 +290,7 @@ void ui_load_lang_def(char *locale, char *default_locale) { #endif int uic_load_language_file(const char *path) { - UcxMap *lang = ucx_map_new(256); + CxMap *lang = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256); FILE *file = fopen(path, "r"); if(!file) { @@ -276,7 +303,7 @@ int uic_load_language_file(const char *path) { fclose(file); - ucx_map_rehash(lang); + cxMapRehash(lang); language = lang; @@ -292,6 +319,5 @@ char* uistr_n(char *name) { if(!language) { return NULL; } - return ucx_map_cstr_get(language, name); + return cxMapGet(language, name); } - diff --git a/ui/common/properties.h b/ui/common/properties.h index 26281d1..6e11345 100644 --- a/ui/common/properties.h +++ b/ui/common/properties.h @@ -30,8 +30,8 @@ #define UIC_PROPERTIES_H #include "../ui/properties.h" -#include -#include +#include +#include #ifdef __cplusplus extern "C" { @@ -44,7 +44,7 @@ extern "C" { #endif void uic_load_app_properties(); -void uic_store_app_properties(); +int uic_store_app_properties(); int uic_load_language_file(const char *path); char* uic_get_image_path(const char *imgfilename); diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c new file mode 100644 index 0000000..d3725c9 --- /dev/null +++ b/ui/common/threadpool.c @@ -0,0 +1,195 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "threadpool.h" +#include "context.h" + +#ifndef _WIN32 + +#include +#include +#include +#include + +static threadpool_job kill_job; + +UiThreadpool* threadpool_new(int min, int max) { + UiThreadpool *pool = malloc(sizeof(UiThreadpool)); + pool->queue = NULL; + pool->queue_len = 0; + pool->num_idle = 0; + pool->min_threads = min; + pool->max_threads = max; + + pthread_mutex_init(&pool->queue_lock, NULL); + pthread_mutex_init(&pool->avlbl_lock, NULL); + pthread_cond_init(&pool->available, NULL); + + return pool; +} + +int threadpool_start(UiThreadpool *pool) { + /* create pool threads */ + for(int i=0;imin_threads;i++) { + pthread_t t; + if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { + fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno)); + return 1; + } + } + return 0; +} + +void* threadpool_func(void *data) { + UiThreadpool *pool = (UiThreadpool*)data; + + for(;;) { + threadpool_job *job = threadpool_get_job(pool); + if(job == &kill_job) { + break; + } + + job->callback(job->data); + + free(job); + } + return NULL; +} + +threadpool_job* threadpool_get_job(UiThreadpool *pool) { + pthread_mutex_lock(&pool->queue_lock); + + threadpool_job *job = NULL; + pool->num_idle++; + while(job == NULL) { + if(pool->queue_len == 0) { + pthread_cond_wait(&pool->available, &pool->queue_lock); + continue; + } else { + pool_queue_t *q = pool->queue; + job = q->job; + pool->queue = q->next; + pool->queue_len--; + free(q); + } + } + pool->num_idle--; + + pthread_mutex_unlock(&pool->queue_lock); + return job; +} + +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { + // TODO: handle errors + + threadpool_job *job = malloc(sizeof(threadpool_job)); + job->callback = func; + job->data = data; + + pthread_mutex_lock(&pool->queue_lock); + threadpool_enqueue_job(pool, job); + + int create_thread = 0; + int destroy_thread = 0; + int diff = pool->queue_len - pool->num_idle; + + //if(pool->queue_len == 1) { + pthread_cond_signal(&pool->available); + //} + + pthread_mutex_unlock(&pool->queue_lock); +} + +void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) { + pool_queue_t *q = malloc(sizeof(pool_queue_t)); + q->job = job; + q->next = NULL; + + if(pool->queue == NULL) { + pool->queue = q; + } else { + pool_queue_t *last_elem = pool->queue; + while(last_elem->next != NULL) { + last_elem = last_elem->next; + } + last_elem->next = q; + } + pool->queue_len++; +} + + + + + + +UiThreadpool* ui_threadpool_create(int nthreads) { + UiThreadpool *pool = threadpool_new(nthreads, nthreads); + threadpool_start(pool); // TODO: check return value + return pool; +} + +void ui_threadpool_destroy(UiThreadpool* pool) { + +} + +static int ui_threadpool_job_finish(void *data) { + UiJob *job = data; + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + job->finish_callback(&event, job->finish_data); + free(job); + return 0; +} + +static void* ui_threadpool_job_func(void *data) { + UiJob *job = data; + if (!job->job_func(job->job_data) && job->finish_callback) { + ui_call_mainthread(ui_threadpool_job_finish, job); + } else { + free(job); + } + return NULL; +} + +void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) { + UiJob* job = malloc(sizeof(UiJob)); + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + threadpool_run(pool, ui_threadpool_job_func, job); +} + + +#endif + diff --git a/ui/motif/toolkit.h b/ui/common/threadpool.h similarity index 58% rename from ui/motif/toolkit.h rename to ui/common/threadpool.h index b14cbd9..d167062 100644 --- a/ui/motif/toolkit.h +++ b/ui/common/threadpool.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,27 +26,20 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TOOLKIT_H -#define TOOLKIT_H +#ifndef UIC_THREADPOOL_H +#define UIC_THREADPOOL_H -#include #include "../ui/toolkit.h" -#include "../common/context.h" -#include "../common/object.h" -#ifdef __cplusplus -extern "C" { +#ifndef _WIN32 +#include #endif -Display* ui_get_display(); - -typedef struct UiEventData { - UiObject *obj; - ui_callback callback; - void *userdata; - int value; -} UiEventData; - +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct UiJob { UiObject *obj; ui_threadfunc job_func; @@ -54,21 +47,43 @@ typedef struct UiJob { ui_callback finish_callback; void *finish_data; } UiJob; + + +typedef struct _threadpool_job threadpool_job; +typedef void*(*job_callback_f)(void *data); + +typedef struct _pool_queue pool_queue_t; +struct UiThreadpool { + pthread_mutex_t queue_lock; + pthread_mutex_t avlbl_lock; + pthread_cond_t available; + pool_queue_t *queue; + uint32_t queue_len; + uint32_t num_idle; + int min_threads; + int max_threads; +}; -typedef enum UiOrientation UiOrientation; -enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; - -void ui_exit_mainloop(); +struct _threadpool_job { + job_callback_f callback; + void *data; +}; -void ui_set_active_window(Widget w); -Widget ui_get_active_window(); +struct _pool_queue { + threadpool_job *job; + pool_queue_t *next; +}; -void ui_secondary_event_loop(int *loop); -void ui_window_dark_theme(Display *dp, Window window); +UiThreadpool* threadpool_new(int min, int max); +int threadpool_start(UiThreadpool *pool); +void* threadpool_func(void *data); +threadpool_job* threadpool_get_job(UiThreadpool *pool); +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data); +void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job); -#ifdef __cplusplus +#ifdef __cplusplus } #endif -#endif /* TOOLKIT_H */ +#endif /* UIC_THREADPOOL_H */ diff --git a/ui/common/toolbar.c b/ui/common/toolbar.c new file mode 100644 index 0000000..0d86f17 --- /dev/null +++ b/ui/common/toolbar.c @@ -0,0 +1,148 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "toolbar.h" +#include "menu.h" + +#include + +static CxMap* toolbar_items; + +static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right + +static UiToolbarMenuItem* ui_appmenu; + + +void uic_toolbar_init(void) { + toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS); +} + +static char* nl_strdup(const char* str) { + return str ? strdup(str) : NULL; +} + +static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args, size_t *ngroups) { + UiToolbarItemArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + newargs.onclick = args.onclick; + newargs.onclickdata = args.onclickdata; + newargs.groups = uic_copy_groups(args.groups, ngroups); + return newargs; +} + +void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args) { + UiToolbarItem* item = malloc(sizeof(UiToolbarItem)); + item->item.type = UI_TOOLBAR_ITEM; + item->args = itemargs_copy(args, &item->ngroups); + cxMapPut(toolbar_items, name, item); +} + + +static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args, size_t *ngroups) { + UiToolbarToggleItemArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + newargs.varname = nl_strdup(args.varname); + newargs.onchange = args.onchange; + newargs.onchangedata = args.onchangedata; + newargs.groups = uic_copy_groups(args.groups, ngroups); + return newargs; +} + +void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args) { + UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem)); + item->item.type = UI_TOOLBAR_TOGGLEITEM; + item->args = toggleitemargs_copy(args, &item->ngroups); + cxMapPut(toolbar_items, name, item); +} + +static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) { + UiToolbarMenuArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + return newargs; +} + +UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) { + UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem)); + item->item.type = UI_TOOLBAR_MENU; + memset(&item->menu, 0, sizeof(UiMenu)); + item->args = menuargs_copy(args); + + item->end = 0; + + if (!name) { + // special appmenu + ui_appmenu = item; + } else { + // toplevel menu + cxMapPut(toolbar_items, name, item); + } + + uic_add_menu_to_stack(&item->menu); +} + + +CxMap* uic_get_toolbar_items(void) { + return toolbar_items; +} + +CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) { + if (pos >= 0 && pos < 3) { + return toolbar_defaults[pos]; + } + return NULL; +} + +void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) { + char* cp = strdup(name); + if (pos >= 0 && pos < 3) { + cxListAdd(toolbar_defaults[pos], cp); + } else { + // TODO: error + } +} + +UiBool uic_toolbar_isenabled(void) { + return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0; +} + +UiToolbarItemI* uic_toolbar_get_item(const char* name) { + return cxMapGet(toolbar_items, name); +} + +UiToolbarMenuItem* uic_get_appmenu(void) { + return ui_appmenu; +} diff --git a/ui/motif/graphics.h b/ui/common/toolbar.h similarity index 51% rename from ui/motif/graphics.h rename to ui/common/toolbar.h index fa248b7..a13828e 100644 --- a/ui/motif/graphics.h +++ b/ui/common/toolbar.h @@ -1,78 +1,98 @@ -/* - * 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 */ - +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_TOOLBAR_H +#define UIC_TOOLBAR_H + +#include "../ui/toolbar.h" + +#include +#include + +#include "menu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolbarItemI UiToolbarItemI; + +typedef struct UiToolbarItem UiToolbarItem; +typedef struct UiToolbarToggleItem UiToolbarToggleItem; + +typedef struct UiToolbarMenuItem UiToolbarMenuItem; + +enum UiToolbarItemType { + UI_TOOLBAR_ITEM = 0, + UI_TOOLBAR_TOGGLEITEM, + UI_TOOLBAR_MENU +}; + +typedef enum UiToolbarItemType UiToolbarItemType; + +struct UiToolbarItemI { + UiToolbarItemType type; +}; + +struct UiToolbarItem { + UiToolbarItemI item; + UiToolbarItemArgs args; + size_t ngroups; +}; + +struct UiToolbarToggleItem { + UiToolbarItemI item; + UiToolbarToggleItemArgs args; + size_t ngroups; +}; + +struct UiToolbarMenuItem { + UiToolbarItemI item; + UiMenu menu; + UiToolbarMenuArgs args; + int end; +}; + + +void uic_toolbar_init(void); + +CxMap* uic_get_toolbar_items(void); +CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos); + +UiBool uic_toolbar_isenabled(void); + +UiToolbarItemI* uic_toolbar_get_item(const char* name); + +UiToolbarMenuItem* uic_get_appmenu(void); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_TOOLBAR_H */ + diff --git a/ui/common/types.c b/ui/common/types.c index 18933f7..7a7a722 100644 --- a/ui/common/types.c +++ b/ui/common/types.c @@ -31,11 +31,14 @@ #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; @@ -99,10 +102,11 @@ UiList* ui_list_new(UiContext *ctx, char *name) { list->count = ui_list_count; list->observers = NULL; - list->data = NULL; + list->data = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 32); list->iter = NULL; list->update = NULL; + list->getselection = NULL; list->obj = NULL; if(name) { @@ -113,54 +117,53 @@ UiList* ui_list_new(UiContext *ctx, char *name) { } void ui_list_free(UiList *list) { - ucx_list_free(list->data); + cxListFree(list->data); free(list); } void* ui_list_first(UiList *list) { - UcxList *elm = list->data; - list->iter = elm; - return elm ? elm->data : NULL; + list->iter = (void*)(intptr_t)0; + return cxListAt(list->data, 0); } void* ui_list_next(UiList *list) { - UcxList *elm = list->iter; + intptr_t iter = (intptr_t)list->iter; + iter++; + void *elm = cxListAt(list->data, iter); if(elm) { - elm = elm->next; - if(elm) { - list->iter = elm; - return elm->data; - } + list->iter = (void*)iter; } - return NULL; + return elm; } 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; - } + return cxListAt(list->data, i); } int ui_list_count(UiList *list) { - UcxList *elm = list->data; - return (int)ucx_list_size(elm); + return cxListSize(list->data); } void ui_list_append(UiList *list, void *data) { - list->data = ucx_list_append(list->data, data); + cxListAdd(list->data, data); } void ui_list_prepend(UiList *list, void *data) { - list->data = ucx_list_prepend(list->data, data); + cxListInsert(list->data, 0, data); +} + +void ui_list_remove(UiList *list, int i) { + cxListRemove(list->data, i); } void ui_list_clear(UiList *list) { - ucx_list_free(list->data); - list->data = NULL; + cxListClear(list->data); +} + +UIEXPORT void ui_list_update(UiList *list) { + if(list->update) { + list->update(list, 0); + } } void ui_list_addobsv(UiList *list, ui_callback f, void *data) { @@ -183,42 +186,60 @@ UiModel* ui_model(UiContext *ctx, ...) { va_list ap; va_start(ap, ctx); - UcxList *cols = NULL; + CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32); int type; while((type = va_arg(ap, int)) != -1) { char *name = va_arg(ap, char*); - UiColumn *column = malloc(sizeof(UiColumn)); - column->type = type; - column->name = name; + UiColumn column; + column.type = type; + column.name = name; - cols = ucx_list_append(cols, column); + cxListAdd(cols, &column); } va_end(ap); - size_t len = ucx_list_size(cols); + size_t len = cxListSize(cols); info->columns = len; info->types = ui_calloc(ctx, len, sizeof(UiModelType)); info->titles = ui_calloc(ctx, len, sizeof(char*)); + info->columnsize = ui_calloc(ctx, len, sizeof(int)); int i = 0; - UCX_FOREACH(elm, cols) { - UiColumn *c = elm->data; + CxIterator iter = cxListIterator(cols); + cx_foreach(UiColumn*, c, iter) { info->types[i] = c->type; info->titles[i] = c->name; - free(c); i++; } - ucx_list_free(cols); + cxListFree(cols); return info; } +UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { + const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; + + UiModel* newmodel = cxMalloc(a, sizeof(UiModel)); + *newmodel = *model; + + newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType)); + memcpy(newmodel->types, model->types, model->columns); + + newmodel->titles = cxCalloc(a, model->columns, sizeof(char*)); + for (int i = 0; i < model->columns; i++) { + newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL; + } + + return newmodel; +} + void ui_model_free(UiContext *ctx, UiModel *mi) { - ucx_mempool_free(ctx->mempool, mi->types); - ucx_mempool_free(ctx->mempool, mi->titles); - ucx_mempool_free(ctx->mempool, mi); + const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; + cxFree(a, mi->types); + cxFree(a, mi->titles); + cxFree(a, mi); } // types @@ -269,6 +290,75 @@ UiRange* ui_range_new(UiContext *ctx, char *name) { return r; } +UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name) { + UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric)); + memset(g, 0, sizeof(UiGeneric)); + if(name) { + uic_reg_var(ctx, name, UI_VAR_GENERIC, g); + } + return g; +} + + +void ui_int_set(UiInteger* i, int64_t value) { + if (i && i->set) { + i->set(i, value); + } +} + +int64_t ui_int_get(UiInteger* i) { + if (i) { + return i->get ? i->get(i) : i->value; + } else { + return 0; + } +} + +void ui_double_set(UiDouble* d, double value) { + if (d && d->set) { + d->set(d, value); + } +} + +double ui_double_get(UiDouble* d) { + if (d) { + return d->get ? d->get(d) : d->value; + } + else { + return 0; + } +} + +void ui_string_set(UiString* s, const char* value) { + if (s && s->set) { + s->set(s, value); + } +} + +char* ui_string_get(UiString* s) { + if (s) { + return s->get ? s->get(s) : s->value.ptr; + } + else { + return 0; + } +} + +void ui_text_set(UiText* s, const char* value) { + if (s && s->set) { + s->set(s, value); + } +} + +char* ui_text_get(UiText* s) { + if (s) { + return s->get ? s->get(s) : s->value.ptr; + } + else { + return 0; + } +} + // private functions void uic_int_copy(UiInteger *from, UiInteger *to) { @@ -317,6 +407,12 @@ void uic_list_copy(UiList *from, UiList *to) { to->obj = from->obj; } +void uic_generic_copy(UiGeneric *from, UiGeneric *to) { + to->get = from->get; + to->get_type = from->get_type; + to->set = from->set; + to->obj = from->obj; +} void uic_int_save(UiInteger *i) { if(!i->obj) return; @@ -344,6 +440,11 @@ void uic_range_save(UiRange *r) { r->get(r); } +void uic_generic_save(UiGeneric *g) { + if(!g->obj) return; + g->get(g); +} + void uic_int_unbind(UiInteger *i) { i->get = NULL; @@ -389,3 +490,90 @@ void uic_list_unbind(UiList *l) { l->update = NULL; l->obj = NULL; } + +void uic_generic_unbind(UiGeneric *g) { + g->get = NULL; + g->get_type = NULL; + g->set = NULL; + g->obj = NULL; +} + + +UIEXPORT UiListSelection ui_list_getselection(UiList *list) { + if (list->getselection) { + return list->getselection(list); + } + return (UiListSelection){ 0, NULL }; +} + +UIEXPORT void ui_list_setselection(UiList *list, int index) { + if (list->setselection && index >= 0) { + UiListSelection sel; + sel.count = 1; + sel.rows = &index; + list->setselection(list, sel); + } +} + +UIEXPORT void ui_listselection_free(UiListSelection selection) { + if (selection.rows) { + free(selection.rows); + } +} + +UIEXPORT UiStr ui_str(char *cstr) { + return (UiStr) { cstr, NULL }; +} + +UIEXPORT UiStr ui_str_free(char *str, void (*freefunc)(void *v)) { + return (UiStr) { str, freefunc }; +} + + +UIEXPORT UiFileList ui_filelist_copy(UiFileList list) { + char **newlist = calloc(sizeof(char*), list.nfiles); + for (int i = 0; i < list.nfiles; i++) { + newlist[i] = strdup(list.files[i]); + } + return (UiFileList) { newlist, list.nfiles }; +} + +UIEXPORT void ui_filelist_free(UiFileList list) { + for (int i = 0; i < list.nfiles; i++) { + free(list.files[i]); + } + free(list.files); +} + + +typedef struct UiObserverDestructor { + UiList *list; + UiObserver *observer; +} UiObserverDestructor; + +static void observer_destructor(UiObserverDestructor *destr) { + UiObserver *remove_obs = destr->observer; + UiObserver *obs = destr->list->observers; + UiObserver *prev = NULL; + while(obs) { + if(obs == remove_obs) { + if(prev) { + prev->next = obs->next; + } else { + destr->list->observers = obs->next; + } + break; + } + prev = obs; + obs = obs->next; + } + free(remove_obs); +} + +void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer) { + CxMempool *mp = ctx->mp; + UiObserverDestructor *destr = cxMalloc(mp->allocator, sizeof(UiObserverDestructor)); + destr->list = list; + destr->observer = observer; + cxMempoolSetDestructor(destr, (cx_destructor_func)observer_destructor); +} diff --git a/ui/common/types.h b/ui/common/types.h index b1d5a2c..677199f 100644 --- a/ui/common/types.h +++ b/ui/common/types.h @@ -42,12 +42,14 @@ void uic_string_copy(UiString *from, UiString *to); void uic_text_copy(UiText *from, UiText *to); void uic_range_copy(UiRange *from, UiRange *to); void uic_list_copy(UiList *from, UiList *to); +void uic_generic_copy(UiGeneric *from, UiGeneric *to); void uic_int_save(UiInteger *i); void uic_double_save(UiDouble *d); void uic_string_save(UiString *s); void uic_text_save(UiText *t); void uic_range_save(UiRange *r); +void uic_generic_save(UiGeneric *g); void uic_int_unbind(UiInteger *i); void uic_double_unbind(UiDouble *d); @@ -55,7 +57,10 @@ void uic_string_unbind(UiString *s); void uic_text_unbind(UiText *t); void uic_range_unbind(UiRange *r); void uic_list_unbind(UiList *l); - +void uic_generic_unbind(UiGeneric *g); + +void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer); + #ifdef __cplusplus } #endif diff --git a/ui/common/ucx_properties.c b/ui/common/ucx_properties.c new file mode 100644 index 0000000..b9374aa --- /dev/null +++ b/ui/common/ucx_properties.c @@ -0,0 +1,263 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx_properties.h" + +#include +#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, cxstring *name, cxstring *value) { + if(parser->tmplen > 0) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + cxstring str = cx_strn(buf, len); + cxstring nl = cx_strchr(str, '\n'); + if(nl.ptr) { + size_t newlen = (size_t)(nl.ptr - buf) + 1; + parser_tmp_append(parser, buf, newlen); + // the tmp buffer contains exactly one line now + + char *orig_buf = parser->buffer; + size_t orig_len = parser->buflen; + + parser->buffer = parser->tmp; + parser->buflen = parser->tmplen; + parser->pos = 0; + parser->tmp = NULL; + parser->tmpcap = 0; + parser->tmplen = 0; + // run ucx_properties_next with the tmp buffer as main buffer + int ret = ucx_properties_next(parser, name, value); + + // restore original buffer + parser->tmp = parser->buffer; + parser->buffer = orig_buf; + parser->buflen = orig_len; + parser->pos = newlen; + + /* + * if ret == 0 the tmp buffer contained just space or a comment + * we parse again with the original buffer to get a name/value + * or a new tmp buffer + */ + return ret ? ret : ucx_properties_next(parser, name, value); + } else { + parser_tmp_append(parser, buf, len); + return 0; + } + } else if(parser->tmp) { + free(parser->tmp); + parser->tmp = NULL; + } + + char comment1 = parser->comment1; + char comment2 = parser->comment2; + char comment3 = parser->comment3; + char delimiter = parser->delimiter; + + // get one line and parse it + while(parser->pos < parser->buflen) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + + /* + * First we check if we have at least one line. We also get indices of + * delimiter and comment chars + */ + size_t delimiter_index = 0; + size_t comment_index = 0; + int has_comment = 0; + + size_t i = 0; + char c = 0; + for(;itmpcap = len + 128; + parser->tmp = (char*)malloc(parser->tmpcap); + parser->tmplen = len; + memcpy(parser->tmp, buf, len); + return 0; + } + + cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i); + // check line + if(delimiter_index == 0) { + line = cx_strtrim(line); + if(line.length != 0) { + parser->error = 1; + } + } else { + cxstring n = cx_strn(buf, delimiter_index); + cxstring v = cx_strn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + n = cx_strtrim(n); + v = cx_strtrim(v); + if(n.length != 0 || v.length != 0) { + *name = n; + *value = v; + parser->pos += i + 1; + return 1; + } else { + parser->error = 1; + } + } + + parser->pos += i + 1; + } + + return 0; +} + +int ucx_properties2map(UcxProperties *parser, CxMap *map) { + cxstring name; + cxstring value; + while(ucx_properties_next(parser, &name, &value)) { + cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value); + if(!mutvalue.ptr) { + return 1; + } + if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) { + cxFree(map->collection.allocator, mutvalue.ptr); + return 1; + } + } + if (parser->error) { + return parser->error; + } else { + return 0; + } +} + +// buffer size is documented - change doc, when you change bufsize! +#define UCX_PROPLOAD_BUFSIZE 1024 +int ucx_properties_load(CxMap *map, FILE *file) { + UcxProperties *parser = ucx_properties_new(); + if(!(parser && map && file)) { + return 1; + } + + int error = 0; + size_t r; + char buf[UCX_PROPLOAD_BUFSIZE]; + while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) { + ucx_properties_fill(parser, buf, r); + error = ucx_properties2map(parser, map); + if (error) { + break; + } + } + ucx_properties_free(parser); + return error; +} + +int ucx_properties_store(CxMap *map, FILE *file) { + CxIterator iter = cxMapIterator(map); + cxstring value; + size_t written; + + cx_foreach(CxMapEntry *, v, iter) { + value = cx_str(v->value); + + written = 0; + written += fwrite(v->key->data, 1, v->key->len, file); + written += fwrite(" = ", 1, 3, file); + written += fwrite(value.ptr, 1, value.length, file); + written += fwrite("\n", 1, 1, file); + + if (written != v->key->len + value.length + 4) { + return 1; + } + } + + return 0; +} + diff --git a/ucx/ucx/properties.h b/ui/common/ucx_properties.h similarity index 91% rename from ucx/ucx/properties.h rename to ui/common/ucx_properties.h index 819d1e5..cfb4359 100644 --- a/ucx/ucx/properties.h +++ b/ui/common/ucx_properties.h @@ -37,8 +37,10 @@ #ifndef UCX_PROPERTIES_H #define UCX_PROPERTIES_H -#include "ucx.h" -#include "map.h" +#include +#include + +#include #ifdef __cplusplus extern "C" { @@ -156,18 +158,18 @@ void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); * 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 + * Attention: the cxstring.ptr pointers of the output parameters point to * memory within the input buffer of the parser and will get invalid some time. * If you want long term copies of the key/value-pairs, use sstrdup() after * calling this function. * * @param prop the UcxProperties object - * @param name a pointer to the sstr_t that shall contain the property name - * @param value a pointer to the sstr_t that shall contain the property value + * @param name a pointer to the cxstring that shall contain the property name + * @param value a pointer to the cxstring that shall contain the property value * @return Nonzero, if a key/value-pair was successfully retrieved * @see ucx_properties_fill() */ -int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value); +int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value); /** * Retrieves all available key/value-pairs and puts them into a UcxMap. @@ -183,7 +185,7 @@ int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value); * @see ucx_properties_fill() * @see UcxMap.allocator */ -int ucx_properties2map(UcxProperties *prop, UcxMap *map); +int ucx_properties2map(UcxProperties *prop, CxMap *map); /** * Loads a properties file to a UcxMap. @@ -198,7 +200,7 @@ int ucx_properties2map(UcxProperties *prop, UcxMap *map); * @see ucx_properties_fill() * @see ucx_properties2map() */ -int ucx_properties_load(UcxMap *map, FILE *file); +int ucx_properties_load(CxMap *map, FILE *file); /** * Stores a UcxMap to a file. @@ -211,7 +213,7 @@ int ucx_properties_load(UcxMap *map, FILE *file); * @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); +int ucx_properties_store(CxMap *map, FILE *file); #ifdef __cplusplus } diff --git a/ui/gtk/button.c b/ui/gtk/button.c index 0add84f..5123e27 100644 --- a/ui/gtk/button.c +++ b/ui/gtk/button.c @@ -31,19 +31,48 @@ #include "button.h" #include "container.h" -#include +#include #include "../common/context.h" #include "../common/object.h" -UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { +void ui_button_set_icon_name(GtkWidget *button, const char *icon) { + if(!icon) { + return; + } + +#ifdef UI_GTK4 + gtk_button_set_icon_name(GTK_BUTTON(button), icon); +#else +#if GTK_CHECK_VERSION(2, 6, 0) + GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON); + if(image) { + gtk_button_set_image(GTK_BUTTON(button), image); + } +#else + // TODO +#endif +#endif +} + +GtkWidget* ui_create_button( + UiObject *obj, + const char *label, + const char *icon, + ui_callback onclick, + void *userdata, + int event_value, + bool activate_event) +{ GtkWidget *button = gtk_button_new_with_label(label); + ui_button_set_icon_name(button, icon); - if(f) { + if(onclick) { UiEventData *event = malloc(sizeof(UiEventData)); event->obj = obj; - event->userdata = data; - event->callback = f; - event->value = 0; + event->userdata = userdata; + event->callback = onclick; + event->value = event_value; + event->customdata = NULL; g_signal_connect( button, @@ -55,11 +84,25 @@ UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { "destroy", G_CALLBACK(ui_destroy_userdata), event); + if(activate_event) { + g_signal_connect( + button, + "activate", + G_CALLBACK(ui_button_clicked), + event); + } } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, button, FALSE); - + return button; +} + +UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) { + UiObject* current = uic_current_obj(obj); + GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata, 0, FALSE); + ui_set_name_and_style(button, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, button, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, button, FALSE); return button; } @@ -86,81 +129,318 @@ void ui_toggle_button_set(UiInteger *integer, int64_t value) { gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE); } -void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) { +void ui_toggled_obs(void *widget, UiVarEventData *event) { + UiInteger *i = event->var->value; UiEvent e; e.obj = event->obj; e.window = event->obj->window; e.document = event->obj->ctx->document; e.eventdata = event->var->value; - e.intval = gtk_toggle_tool_button_get_active(widget); + e.intval = i->get(i); - 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); +static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = gtk_toggle_button_get_active(widget); + event->callback(&e, event->userdata); +} + +static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) { + if(gtk_toggle_button_get_active(widget)) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); + } +} + +void ui_setup_togglebutton( + UiObject *obj, + GtkWidget *togglebutton, + const char *label, + const char *icon, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state) +{ + if(label) { + gtk_button_set_label(GTK_BUTTON(togglebutton), label); + } + ui_button_set_icon_name(togglebutton, icon); - // 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); + ui_bind_togglebutton( + obj, + togglebutton, + ui_toggle_button_get, + ui_toggle_button_set, + varname, + value, + (ui_toggled_func)ui_toggled_callback, + onchange, + onchangedata, + (ui_toggled_func)ui_togglebutton_enable_state_callback, + enable_state + ); +} + +void ui_bind_togglebutton( + UiObject *obj, + GtkWidget *widget, + int64_t (*getfunc)(UiInteger*), + void (*setfunc)(UiInteger*, int64_t), + const char *varname, + UiInteger *value, + void (*toggled_callback)(void*, void*), + ui_callback onchange, + void *onchangedata, + void (*enable_state_func)(void*, void*), + int enable_state) +{ + UiObject* current = uic_current_obj(obj); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER); + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = getfunc; + value->set = setfunc; UiVarEventData *event = malloc(sizeof(UiVarEventData)); event->obj = obj; event->var = var; event->observers = NULL; + event->callback = NULL; + event->userdata = NULL; g_signal_connect( - button, - "clicked", + widget, + "toggled", G_CALLBACK(ui_toggled_obs), event); g_signal_connect( - button, + widget, "destroy", G_CALLBACK(ui_destroy_vardata), event); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, button, FALSE); + if(onchange) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = onchangedata; + event->callback = onchange; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + widget, + "toggled", + G_CALLBACK(toggled_callback), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + if(enable_state > 0) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = NULL; + event->callback = NULL; + event->value = enable_state; + event->customdata = NULL; + + g_signal_connect( + widget, + "toggled", + G_CALLBACK(enable_state_func), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } +} + +static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + + ui_setup_togglebutton( + current, + widget, + args.label, + args.icon, + args.varname, + args.value, + args.onchange, + args.onchangedata, + args.enable_group); + ui_set_name_and_style(widget, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, widget, args.groups); - return button; + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, gtk_toggle_button_new(), args); +} + +#if GTK_MAJOR_VERSION >= 4 + +int64_t ui_check_button_get(UiInteger *integer) { + GtkCheckButton *button = integer->obj; + integer->value = (int)gtk_check_button_get_active(button); + return integer->value; +} + +void ui_check_button_set(UiInteger *integer, int64_t value) { + GtkCheckButton *button = integer->obj; + integer->value = value; + gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE); } -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; +static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = gtk_check_button_get_active(widget); + event->callback(&e, event->userdata); +} + +static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) { + if(gtk_check_button_get_active(widget)) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); } - 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_checkbox_create(UiObject* obj, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *widget = gtk_check_button_new_with_label(args.label); + ui_bind_togglebutton( + obj, + widget, + ui_check_button_get, + ui_check_button_set, + args.varname, + args.value, + (ui_toggled_func)ui_checkbox_callback, + args.onchange, + args.onchangedata, + (ui_toggled_func)ui_checkbox_enable_state, + args.enable_group); + + ui_set_name_and_style(widget, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, widget, args.groups); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; +} + +#else +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, gtk_check_button_new(), args); +} +#endif + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { +#ifdef UI_GTK3 + return NULL; // TODO +#else + return ui_checkbox_create(obj, args); +#endif +} + +#if GTK_MAJOR_VERSION >= 4 +#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label) +#define RADIOBUTTON_SET_GROUP(button, group) +#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button) +#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button)) +#else +#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label) +#define RADIOBUTTON_SET_GROUP(button, group) /* noop */ +#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)) +#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) +#endif + +static void radiobutton_toggled(void *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = RADIOBUTTON_GET_ACTIVE(widget); + event->callback(&e, event->userdata); } +typedef struct UiRadioButtonData { + UiInteger *value; + UiVarEventData *eventdata; + UiBool first; +} UiRadioButtonData; + +static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) { + if(data->first) { + ui_destroy_vardata(w, data->eventdata); + g_slist_free(data->value->obj); + data->value->obj = NULL; + data->value->get = NULL; + data->value->set = NULL; + } else { + free(data->eventdata); + } + free(data); +} -UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) { +UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + GSList *rg = NULL; UiInteger *rgroup; + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + + UiBool first = FALSE; if(var) { rgroup = var->value; rg = rgroup->obj; + if(!rg) { + first = TRUE; + } } - GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label); - rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton)); - + GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label); + ui_set_name_and_style(rbutton, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, rbutton, args.groups); if(rgroup) { +#if GTK_MAJOR_VERSION >= 4 + if(rg) { + gtk_check_button_set_group(GTK_CHECK_BUTTON(rbutton), rg->data); + } + rg = g_slist_prepend(rg, rbutton); +#else + gtk_radio_button_set_group(GTK_RADIO_BUTTON(rbutton), rg); + rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton)); +#endif + rgroup->obj = rg; rgroup->get = ui_radiobutton_get; rgroup->set = ui_radiobutton_set; @@ -171,41 +451,53 @@ UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) { event->obj = obj; event->var = var; event->observers = NULL; + event->callback = NULL; + event->userdata = NULL; + + UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData)); + rbdata->value = rgroup; + rbdata->eventdata = event; + rbdata->first = first; g_signal_connect( rbutton, - "clicked", + "toggled", G_CALLBACK(ui_radio_obs), event); g_signal_connect( rbutton, "destroy", - G_CALLBACK(ui_destroy_vardata), + G_CALLBACK(destroy_radiobutton), + rbdata); + } + + if(args.onchange) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = args.onchangedata; + event->callback = args.onchange; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + rbutton, + "toggled", + G_CALLBACK(radiobutton_toggled), + event); + g_signal_connect( + rbutton, + "destroy", + G_CALLBACK(ui_destroy_userdata), event); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, rbutton, FALSE); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, 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) { +void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) { UiInteger *i = event->var->value; UiEvent e; @@ -218,6 +510,41 @@ void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) { ui_notify_evt(i->observers, &e); } +#if GTK_MAJOR_VERSION >= 4 +int64_t ui_radiobutton_get(UiInteger *value) { + int selection = 0; + GSList *ls = value->obj; + int i = 0; + guint len = g_slist_length(ls); + while(ls) { + if(gtk_check_button_get_active(GTK_CHECK_BUTTON(ls->data))) { + selection = len - i - 1; + break; + } + ls = ls->next; + i++; + } + + value->value = selection; + return selection; +} + +void ui_radiobutton_set(UiInteger *value, int64_t i) { + GSList *ls = value->obj; + int s = g_slist_length(ls) - 1 - i; + int j = 0; + while(ls) { + if(j == s) { + gtk_check_button_set_active(GTK_CHECK_BUTTON(ls->data), TRUE); + break; + } + ls = ls->next; + j++; + } + + value->value = i; +} +#else int64_t ui_radiobutton_get(UiInteger *value) { int selection = 0; GSList *ls = value->obj; @@ -251,4 +578,4 @@ void ui_radiobutton_set(UiInteger *value, int64_t i) { value->value = i; } - +#endif diff --git a/ui/gtk/button.h b/ui/gtk/button.h index eac2337..c855b35 100644 --- a/ui/gtk/button.h +++ b/ui/gtk/button.h @@ -36,18 +36,55 @@ #ifdef __cplusplus extern "C" { #endif + +void ui_button_set_icon_name(GtkWidget *button, const char *icon_name); +typedef void (*ui_toggled_func)(void*, void*); + +GtkWidget* ui_create_button( + UiObject *obj, + const char *label, + const char *icon, + ui_callback onclick, + void *userdata, + int event_value, + bool activate_event); + +void ui_setup_togglebutton( + UiObject *obj, + GtkWidget *togglebutton, + const char *label, + const char *icon, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state); + +void ui_bind_togglebutton( + UiObject *obj, + GtkWidget *widget, + int64_t (*getfunc)(UiInteger*), + void (*setfunc)(UiInteger*, int64_t), + const char *varname, + UiInteger *value, + void (*toggled_callback)(void*, void*), + ui_callback onchange, + void *onchangedata, + void (*enable_state_func)(void*, void*), + int enable_state); + // event wrapper void ui_button_clicked(GtkWidget *widget, UiEventData *event); -void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event); +void ui_toggled_obs(void *widget, UiVarEventData *event); UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var); UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var); -void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event); +void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event); int64_t ui_radiobutton_get(UiInteger *value); void ui_radiobutton_set(UiInteger *value, int64_t i); diff --git a/ui/gtk/container.c b/ui/gtk/container.c index 551eb85..863f4a7 100644 --- a/ui/gtk/container.c +++ b/ui/gtk/container.c @@ -32,6 +32,7 @@ #include "container.h" #include "toolkit.h" +#include "headerbar.h" #include "../common/context.h" #include "../common/object.h" @@ -52,7 +53,7 @@ int ui_container_finish(UiObject *obj) { } GtkWidget* ui_gtk_vbox_new(int spacing) { -#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION >= 3 return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing); #else return gtk_vbox_new(FALSE, spacing); @@ -60,39 +61,62 @@ GtkWidget* ui_gtk_vbox_new(int spacing) { } GtkWidget* ui_gtk_hbox_new(int spacing) { -#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION >= 3 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; +GtkWidget* ui_subcontainer_create( + UiSubContainerType type, + UiObject *newobj, + int spacing, + int columnspacing, + int rowspacing, + int margin) +{ + GtkWidget *sub = NULL; + GtkWidget *add = NULL; + switch(type) { + default: { + sub = ui_gtk_vbox_new(spacing); + add = ui_box_set_margin(sub, margin); + newobj->container = ui_box_container(newobj, sub, type); + newobj->widget = sub; + break; + } + case UI_CONTAINER_HBOX: { + sub = ui_gtk_hbox_new(spacing); + add = ui_box_set_margin(sub, margin); + newobj->container = ui_box_container(newobj, sub, type); + newobj->widget = sub; + break; + } + case UI_CONTAINER_GRID: { + sub = ui_create_grid_widget(columnspacing, rowspacing); + add = ui_box_set_margin(sub, margin); + newobj->container = ui_grid_container(newobj, sub); + newobj->widget = sub; + break; + } + case UI_CONTAINER_NO_SUB: { + break; + } + } + return add; } /* -------------------- Box Container -------------------- */ -UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) { - UiBoxContainer *ct = ucx_mempool_calloc( - obj->ctx->mempool, +UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { + UiBoxContainer *ct = cxCalloc( + obj->ctx->allocator, 1, sizeof(UiBoxContainer)); ct->container.widget = box; ct->container.add = ui_box_container_add; + ct->type = type; return (UiContainer*)ct; } @@ -111,27 +135,40 @@ void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { } UiBool expand = fill; +#if GTK_MAJOR_VERSION >= 4 + gtk_box_append(GTK_BOX(ct->widget), widget); + GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START; + if(bc->type == UI_CONTAINER_VBOX) { + gtk_widget_set_valign(widget, align); + gtk_widget_set_vexpand(widget, expand); + gtk_widget_set_hexpand(widget, TRUE); + } else if(bc->type == UI_CONTAINER_HBOX) { + gtk_widget_set_halign(widget, align); + gtk_widget_set_hexpand(widget, expand); + gtk_widget_set_vexpand(widget, TRUE); + } + +#else gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0); +#endif ui_reset_layout(ct->layout); ct->current = widget; } UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) { - UiGridContainer *ct = ucx_mempool_calloc( - obj->ctx->mempool, + UiGridContainer *ct = cxCalloc( + obj->ctx->allocator, 1, sizeof(UiGridContainer)); ct->container.widget = grid; ct->container.add = ui_grid_container_add; -#ifdef UI_GTK2 - ct->width = 0; - ct->height = 1; -#endif + UI_GTK_V2(ct->width = 0); + UI_GTK_V2(ct->height = 1); return (UiContainer*)ct; } -#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION >= 3 void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { UiGridContainer *grid = (UiGridContainer*)ct; @@ -143,24 +180,39 @@ void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { int hexpand = FALSE; int vexpand = FALSE; + int hfill = FALSE; + int vfill = FALSE; + if(ct->layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(ct->layout.fill); + } if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) { hexpand = ct->layout.hexpand; + hfill = TRUE; } if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) { vexpand = ct->layout.vexpand; + vfill = TRUE; + } + if(fill) { + hfill = TRUE; + vfill = TRUE; } - if(hexpand) { - gtk_widget_set_hexpand(widget, TRUE); + if(!hfill) { + gtk_widget_set_halign(widget, GTK_ALIGN_START); } - if(vexpand) { - gtk_widget_set_vexpand(widget, TRUE); + if(!vfill) { + gtk_widget_set_valign(widget, GTK_ALIGN_START); } - int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1; + gtk_widget_set_hexpand(widget, hexpand); + gtk_widget_set_vexpand(widget, vexpand); - gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1); - grid->x += gwidth; + int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1; + int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1; + + gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan); + grid->x += colspan; ui_reset_layout(ct->layout); ct->current = widget; @@ -187,6 +239,10 @@ void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; + int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1; + int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1; + // TODO: use colspan/rowspan + gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0); grid->x++; int nw = grid->x > grid->width ? grid->x : grid->width; @@ -201,30 +257,54 @@ void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { } #endif -UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { - UiContainer *ct = ucx_mempool_calloc( - obj->ctx->mempool, +UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) { + UiContainer *ct = cxCalloc( + obj->ctx->allocator, 1, sizeof(UiContainer)); - ct->widget = scrolledwindow; - ct->add = ui_scrolledwindow_container_add; + ct->widget = frame; + ct->add = ui_frame_container_add; return ct; } +void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + FRAME_SET_CHILD(ct->widget, widget); +} + +UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) { + UiContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainer)); + ct->widget = expander; + ct->add = ui_expander_container_add; + return ct; +} + +void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + EXPANDER_SET_CHILD(ct->widget, widget); +} + void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { // TODO: check if the widget implements GtkScrollable -#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 + SCROLLEDWINDOW_SET_CHILD(ct->widget, widget); ui_reset_layout(ct->layout); ct->current = widget; } +UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { + UiContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainer)); + ct->widget = scrolledwindow; + ct->add = ui_scrolledwindow_container_add; + return ct; +} + UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) { - UiTabViewContainer *ct = ucx_mempool_calloc( - obj->ctx->mempool, + UiTabViewContainer *ct = cxCalloc( + obj->ctx->allocator, 1, sizeof(UiTabViewContainer)); ct->container.widget = tabview; @@ -233,28 +313,23 @@ UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) { } 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)); + UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview"); + return; + } + data->add_tab(ct->widget, -1, ct->layout.label, widget); ui_reset_layout(ct->layout); ct->current = widget; } -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* ui_box_set_margin(GtkWidget *box, int margin) { GtkWidget *ret = box; -#ifdef UI_GTK3 -#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12 +#if GTK_MAJOR_VERSION >= 3 +#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012 gtk_widget_set_margin_start(box, margin); gtk_widget_set_margin_end(box, margin); #else @@ -272,73 +347,53 @@ static GtkWidget* box_set_margin(GtkWidget *box, int margin) { return ret; } -UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) { - UiContainer *ct = uic_get_current_container(obj); +UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type) { + UiObject *current = uic_current_obj(obj); + UiContainer *ct = current->container; + UI_APPLY_LAYOUT1(current, args); - GtkWidget *vbox = ui_gtk_vbox_new(spacing); - GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox; + GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing); + ui_set_name_and_style(box, args.name, args.style_class); + GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box; ct->add(ct, widget, TRUE); - UiObject *newobj = uic_object_new(obj, vbox); - newobj->container = ui_box_container(obj, vbox); + UiObject *newobj = uic_object_new(obj, box); + newobj->container = ui_box_container(obj, box, type); 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; +UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) { + return ui_box_create(obj, args, UI_CONTAINER_VBOX); } -UIWIDGET ui_grid(UiObject *obj) { - return ui_grid_sp(obj, 0, 0, 0); +UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) { + return ui_box_create(obj, args, UI_CONTAINER_HBOX); } -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* ui_create_grid_widget(int colspacing, int rowspacing) { +#if GTK_MAJOR_VERSION >= 3 GtkWidget *grid = gtk_grid_new(); - gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing); + gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing); 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_col_spacings(GTK_TABLE(grid), colspacing); 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); + return grid; +} + +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + GtkWidget *widget; + + GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing); + ui_set_name_and_style(grid, args.name, args.style_class); + widget = ui_box_set_margin(grid, args.margin); + current->container->add(current->container, widget, TRUE); UiObject *newobj = uic_object_new(obj, grid); newobj->container = ui_grid_container(obj, grid); @@ -347,247 +402,674 @@ UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing 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); +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); - UiObject *newobj = uic_object_new(obj, sw); - newobj->container = ui_scrolledwindow_container(obj, sw); + GtkWidget *frame = gtk_frame_new(args.label); + UiObject *newobj = uic_object_new(obj, frame); + GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin); + if(sub) { + FRAME_SET_CHILD(frame, sub); + } else { + newobj->widget = frame; + newobj->container = ui_frame_container(obj, frame); + } + current->container->add(current->container, frame, FALSE); uic_obj_add(obj, newobj); - return sw; + return frame; } -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); +UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); - 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); + GtkWidget *expander = gtk_expander_new(args.label); + gtk_expander_set_expanded(GTK_EXPANDER(expander), args.isexpanded); + UiObject *newobj = uic_object_new(obj, expander); + GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin); + if(sub) { + EXPANDER_SET_CHILD(expander, sub); + } else { + newobj->widget = expander; + newobj->container = ui_expander_container(obj, expander); + } + current->container->add(current->container, expander, FALSE); + uic_obj_add(obj, newobj); - return tabview; + return expander; } -void ui_tab(UiObject *obj, char *title) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.label = title; - ui_vbox(obj); + +UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + + GtkWidget *sw = SCROLLEDWINDOW_NEW(); + ui_set_name_and_style(sw, args.name, args.style_class); + GtkWidget *widget = ui_box_set_margin(sw, args.margin); + current->container->add(current->container, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, sw); + GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin); + if(sub) { + SCROLLEDWINDOW_SET_CHILD(sw, sub); + } else { + newobj->widget = sw; + newobj->container = ui_scrolledwindow_container(obj, sw); + } + + uic_obj_add(obj, newobj); + + return sw; } -void ui_select_tab(UIWIDGET tabview, int tab) { + +void ui_notebook_tab_select(UIWIDGET tabview, int tab) { gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab); } -/* -------------------- Splitpane -------------------- */ +void ui_notebook_tab_remove(UIWIDGET tabview, int tab) { + gtk_notebook_remove_page(GTK_NOTEBOOK(tabview), tab); +} -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); +void ui_notebook_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) { + gtk_notebook_insert_page( + GTK_NOTEBOOK(widget), + child, + gtk_label_new(name), + index); +} + +int64_t ui_notebook_get(UiInteger *i) { + GtkNotebook *nb = i->obj; + i->value = gtk_notebook_get_current_page(nb); + return i->value; +} + +void ui_notebook_set(UiInteger *i, int64_t value) { + GtkNotebook *nb = i->obj; + gtk_notebook_set_current_page(nb, value); + i->value = gtk_notebook_get_current_page(nb); +} + + +#if GTK_MAJOR_VERSION >= 4 +static int stack_set_page(GtkWidget *stack, int index) { + GtkSelectionModel *pages = gtk_stack_get_pages(GTK_STACK(stack)); + GListModel *list = G_LIST_MODEL(pages); + GtkStackPage *page = g_list_model_get_item(list, index); + if(page) { + gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_page_get_child(page)); + } else { + fprintf(stderr, "UI Error: ui_stack_set value out of bounds\n"); + return -1; } -#else - switch(orientation) { - case UI_HORIZONTAL: return gtk_hpaned_new(); - case UI_VERTICAL: return gtk_vpaned_new(); + return index; +} + +void ui_stack_tab_select(UIWIDGET tabview, int tab) { + stack_set_page(tabview, tab); +} + +void ui_stack_tab_remove(UIWIDGET tabview, int tab) { + GtkStack *stack = GTK_STACK(tabview); + GtkWidget *current = gtk_stack_get_visible_child(stack); + GtkSelectionModel *pages = gtk_stack_get_pages(stack); + GListModel *list = G_LIST_MODEL(pages); + GtkStackPage *page = g_list_model_get_item(list, tab); + if(page) { + gtk_stack_remove(stack, gtk_stack_page_get_child(page)); + } +} + +void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) { + (void)gtk_stack_add_titled(GTK_STACK(widget), child, name, name); +} + +int64_t ui_stack_get(UiInteger *i) { + GtkStack *stack = GTK_STACK(i->obj); + GtkWidget *current = gtk_stack_get_visible_child(stack); + GtkSelectionModel *pages = gtk_stack_get_pages(stack); + GListModel *list = G_LIST_MODEL(pages); + int nitems = g_list_model_get_n_items(list); + for(int p=0;pvalue = p; + break; + } + } + return i->value; +} + +void ui_stack_set(UiInteger *i, int64_t value) { + GtkWidget *widget = i->obj; + if(stack_set_page(widget, value) >= 0) { + i->value = value; + } +} +#elif GTK_MAJOR_VERSION >= 3 +static GtkWidget* stack_get_child(GtkWidget *stack, int index) { + GList *children = gtk_container_get_children(GTK_CONTAINER(stack)); + if(children) { + return g_list_nth_data(children, index); } -#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); +void ui_stack_tab_select(UIWIDGET tabview, int tab) { + GtkWidget *child = stack_get_child(tabview, tab); + if(child) { + gtk_stack_set_visible_child(GTK_STACK(tabview), child); + } +} + +void ui_stack_tab_remove(UIWIDGET tabview, int tab) { + GtkWidget *child = stack_get_child(tabview, tab); + if(child) { + gtk_container_remove(GTK_CONTAINER(tabview), child); + } +} + +void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) { + gtk_stack_add_titled(GTK_STACK(widget), child, name, name); +} + +int64_t ui_stack_get(UiInteger *i) { + GtkWidget *visible = gtk_stack_get_visible_child(GTK_STACK(i->obj)); + GList *children = gtk_container_get_children(GTK_CONTAINER(i->obj)); + GList *elm = children; + int n = 0; + int64_t v = -1; + while(elm) { + GtkWidget *child = elm->data; + if(child == visible) { + v = n; + break; + } + + elm = elm->next; + n++; + } + g_list_free(children); + i->value = v; + return v; +} + +void ui_stack_set(UiInteger *i, int64_t value) { + GtkWidget *child = stack_get_child(i->obj, value); + if(child) { + gtk_stack_set_visible_child(GTK_STACK(i->obj), child); + i->value = value; + } +} + +#endif + + + + +UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview) { + return g_object_get_data(G_OBJECT(tabview), "ui_tabview"); +} + +typedef int64_t(*ui_tabview_get_func)(UiInteger*); +typedef void (*ui_tabview_set_func)(UiInteger*, int64_t); + +UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) { + UiGtkTabView *data = malloc(sizeof(UiGtkTabView)); + data->margin = args.margin; + data->spacing = args.spacing; + data->columnspacing = args.columnspacing; + data->rowspacing = args.rowspacing; - if(max <= 0) max = INT_MAX; + ui_tabview_get_func getfunc = NULL; + ui_tabview_set_func setfunc = NULL; - 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; + GtkWidget *widget = NULL; + GtkWidget *data_widget = NULL; + switch(args.tabview) { + case UI_TABVIEW_DOC: { + // TODO + break; + } + case UI_TABVIEW_NAVIGATION_SIDE: { +#if GTK_CHECK_VERSION(3, 10, 0) + widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *sidebar = gtk_stack_sidebar_new(); + BOX_ADD(widget, sidebar); + GtkWidget *stack = gtk_stack_new(); + gtk_stack_set_transition_type (GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN); + gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(sidebar), GTK_STACK(stack)); + BOX_ADD_EXPAND(widget, stack); + data->select_tab = ui_stack_tab_select; + data->remove_tab = ui_stack_tab_remove; + data->add_tab = ui_stack_tab_add; + getfunc = ui_stack_get; + setfunc = ui_stack_set; + data_widget = stack; +#else + // TODO +#endif + break; + } + case UI_TABVIEW_DEFAULT: /* fall through */ + case UI_TABVIEW_NAVIGATION_TOP: /* fall through */ + case UI_TABVIEW_INVISIBLE: /* fall through */ + case UI_TABVIEW_NAVIGATION_TOP2: { + widget = gtk_notebook_new(); + data_widget = widget; + data->select_tab = ui_notebook_tab_select; + data->remove_tab = ui_notebook_tab_remove; + data->add_tab = ui_notebook_tab_add; + getfunc = ui_notebook_get; + setfunc = ui_notebook_set; + if(args.tabview == UI_TABVIEW_INVISIBLE) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE); + } + break; + } + } - UiObject *pobj = uic_object_new(obj, paned); - pobj->container = (UiContainer*)pctn; + UiObject* current = uic_current_obj(obj); + if(args.value || args.varname) { + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + UiInteger *i = var->value; + i->get = getfunc; + i->set = setfunc; + i->obj = data_widget; + } - uic_obj_add(obj, pobj); + g_object_set_data(G_OBJECT(widget), "ui_tabview", data); + data->widget = data_widget; + data->subcontainer = args.subcontainer; + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, widget); + newobj->container = ui_tabview_container(obj, widget); + uic_obj_add(obj, newobj); + data->obj = newobj; + + return widget; +} + +void ui_tab_create(UiObject* obj, const char* title) { + UiObject* current = uic_current_obj(obj); + UiGtkTabView *data = ui_widget_get_tabview_data(current->widget); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview\n"); + return; + } - return paned; + UiObject *newobj = ui_tabview_add(current->widget, title, -1); + current->next = newobj; } -UIWIDGET ui_hsplitpane(UiObject *obj, int max) { - return ui_splitpane(obj, max, UI_HORIZONTAL); + + +void ui_tabview_select(UIWIDGET tabview, int tab) { + UiGtkTabView *data = ui_widget_get_tabview_data(tabview); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview\n"); + return; + } + data->select_tab(tabview, tab); } -UIWIDGET ui_vsplitpane(UiObject *obj, int max) { - return ui_splitpane(obj, max, UI_VERTICAL); +void ui_tabview_remove(UIWIDGET tabview, int tab) { + UiGtkTabView *data = ui_widget_get_tabview_data(tabview); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview\n"); + return; + } + data->remove_tab(tabview, tab); } -void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { - UiPanedContainer *pctn = (UiPanedContainer*)ct; +UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + UiGtkTabView *data = ui_widget_get_tabview_data(tabview); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview\n"); + return NULL; + } - gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE; - int width = ct->layout.width; - ui_reset_layout(ct->layout); + UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject)); + newobj->ctx = data->obj->ctx; - 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; + GtkWidget *sub; + switch(data->subcontainer) { + default: { + sub = ui_gtk_vbox_new(data->spacing); + newobj->container = ui_box_container(newobj, sub, data->subcontainer); + break; + } + case UI_CONTAINER_HBOX: { + sub = ui_gtk_hbox_new(data->spacing); + newobj->container = ui_box_container(newobj, sub, data->subcontainer); + break; + } + case UI_CONTAINER_GRID: { + sub = ui_create_grid_widget(data->columnspacing, data->rowspacing); + newobj->container = ui_grid_container(newobj, sub); + break; + } } + newobj->widget = sub; + GtkWidget *widget = ui_box_set_margin(sub, data->margin); - if(width > 0) { - gtk_paned_set_position(GTK_PANED(pctn->current_pane), width); - } + data->add_tab(data->widget, tab_index, name, widget); - pctn->cur++; + return newobj; } -/* -------------------- 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); +/* -------------------- Headerbar -------------------- */ + +static void hb_set_part(UiObject *obj, int part) { + UiObject* current = uic_current_obj(obj); + GtkWidget *headerbar = current->widget; - GtkWidget *sidebar = ui_gtk_vbox_new(0); - gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE); + UiHeaderbarContainer *hb = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiHeaderbarContainer)); + memcpy(hb, current->container, sizeof(UiHeaderbarContainer)); - UiObject *left = uic_object_new(obj, sidebar); - UiContainer *ct1 = ui_box_container(obj, sidebar); - left->container = ct1; + UiObject *newobj = uic_object_new(obj, headerbar); + newobj->container = (UiContainer*)hb; + uic_obj_add(obj, newobj); - 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; + hb->part = part; +} + +void ui_headerbar_start_create(UiObject *obj) { + hb_set_part(obj, 0); +} + +void ui_headerbar_center_create(UiObject *obj) { + hb_set_part(obj, 2); +} + +void ui_headerbar_end_create(UiObject *obj) { + hb_set_part(obj, 1); +} + +UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs args) { + UiObject *current = uic_current_obj(obj); + UiContainer *ct = current->container; + UI_APPLY_LAYOUT1(current, args); - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, paned, TRUE); + GtkWidget *box = ui_gtk_hbox_new(args.alt_spacing); + ui_set_name_and_style(box, args.name, args.style_class); + ct->add(ct, box, FALSE); - uic_obj_add(obj, right); - uic_obj_add(obj, left); + UiObject *newobj = uic_object_new(obj, box); + newobj->container = ui_headerbar_fallback_container(obj, box); + uic_obj_add(obj, newobj); - return sidebar; + return box; } -void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) { - // TODO: remove - gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE); +static void hb_fallback_set_part(UiObject *obj, int part) { + UiObject* current = uic_current_obj(obj); + GtkWidget *headerbar = current->widget; - ui_reset_layout(ct->layout); - ct->current = widget; + UiObject *newobj = uic_object_new(obj, headerbar); + newobj->container = ui_headerbar_container(obj, headerbar); + uic_obj_add(obj, newobj); + + UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container; + hb->part = part; } -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; +UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) { + UiHeaderbarContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiHeaderbarContainer)); + ct->container.widget = headerbar; + ct->container.add = ui_headerbar_fallback_container_add; + return (UiContainer*)ct; } +void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct; + BOX_ADD(ct->widget, widget); +} -/* -------------------- 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; +#if GTK_CHECK_VERSION(3, 10, 0) + +UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) { + GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar"); + if(!headerbar) { + return ui_headerbar_fallback_create(obj, args); } - //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); + UiObject *newobj = uic_object_new(obj, headerbar); + newobj->container = ui_headerbar_container(obj, headerbar); + uic_obj_add(obj, newobj); + + return headerbar; +} + +UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) { + UiHeaderbarContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiHeaderbarContainer)); + ct->container.widget = headerbar; + ct->container.add = ui_headerbar_container_add; + return (UiContainer*)ct; +} + +void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct; + if(hb->part == 0) { + UI_HEADERBAR_PACK_START(ct->widget, widget); + } else if(hb->part == 1) { + UI_HEADERBAR_PACK_END(ct->widget, widget); + } else if(hb->part == 2) { + if(!hb->centerbox) { + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + hb->centerbox = box; + UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box); + } + BOX_ADD(hb->centerbox, widget); + } +} + +#else + +UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) { + return ui_headerbar_fallback_create(obj, args); } -UiTabbedPane* ui_tabbed_document_view(UiObject *obj) { - GtkWidget *tabview = gtk_notebook_new(); - gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE); +#endif + +/* -------------------- Sidebar -------------------- */ + +#ifdef UI_LIBADWAITA +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) { + GtkWidget *sidebar_toolbar_view = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar"); + if(!sidebar_toolbar_view) { + fprintf(stderr, "Error: window is not configured for sidebar\n"); + return NULL; + } - g_signal_connect( - tabview, - "switch-page", - G_CALLBACK(page_change), - NULL); + GtkWidget *box = ui_gtk_vbox_new(args.spacing); + ui_box_set_margin(box, args.margin); + adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box); - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, tabview, TRUE); + UiObject *newobj = uic_object_new(obj, box); + newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_obj_add(obj, newobj); - UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane)); - tabbedpane->ctx = uic_current_obj(obj)->ctx; - tabbedpane->widget = tabview; - tabbedpane->document = NULL; + return box; +} +#else +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) { + GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar"); - return tabbedpane; + GtkWidget *box = ui_gtk_vbox_new(args.spacing); + ui_box_set_margin(box, args.margin); + BOX_ADD_EXPAND(sidebar_vbox, box); + + UiObject *newobj = uic_object_new(obj, box); + newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_obj_add(obj, newobj); + + return box; } +#endif -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); +/* -------------------- Splitpane -------------------- */ + +static GtkWidget* create_paned(UiOrientation orientation) { +#if GTK_MAJOR_VERSION >= 3 + switch(orientation) { + case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL); + } +#else + switch(orientation) { + case UI_HORIZONTAL: return gtk_hpaned_new(); + case UI_VERTICAL: return gtk_vpaned_new(); + } +#endif + return NULL; +} + + + +/* -------------------- ItemList Container -------------------- */ + +static void remove_item(void *data, void *item) { + UiGtkItemListContainer *ct = data; + UiObject *obj = item; + if(ct->remove_items) { + BOX_REMOVE(ct->widget, obj->widget); + } + uic_object_destroy(obj); +} + +static void update_itemlist(UiList *list, int c) { + UiGtkItemListContainer *ct = list->obj; + + CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + new_items->collection.advanced_destructor = remove_item; + new_items->collection.destructor_data = ct; - 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; + // only create new widgets for new elements, so at first we have + // to find which elements are new + // check which elements in the list are already in the container + void *elm = list->first(list); + int j = 0; + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = NULL; + cxMapRemoveAndGet(ct->current_items, key, &item_obj); + if(item_obj) { + g_object_ref(G_OBJECT(item_obj->widget)); + BOX_REMOVE(ct->widget, item_obj->widget); + cxMapPut(new_items, key, item_obj); + } + elm = list->next(list); + j++; + } - GQuark q = g_quark_from_static_string("ui.tab.object"); - g_object_set_qdata(G_OBJECT(frame), q, tab); + // ct->current_items only contains elements, that are not in the list + cxMapFree(ct->current_items); // calls destructor remove_item + ct->current_items = new_items; - return tab; + // add all items + int index = 0; + elm = list->first(list); + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = cxMapGet(ct->current_items, key); + if(item_obj) { + // re-add previously created widget + ui_box_container_add(ct->container, item_obj->widget, FALSE); + } else { + // create new widget and object for this list element + CxMempool *mp = cxBasicMempoolCreate(256); + const CxAllocator *a = mp->allocator; + UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->window = NULL; + obj->widget = ui_subcontainer_create( + ct->subcontainer, + obj, + ct->spacing, + ct->columnspacing, + ct->rowspacing, + ct->margin); + ui_box_container_add(ct->container, obj->widget, FALSE); + if(ct->create_ui) { + ct->create_ui(obj, index, elm, ct->userdata); + } + cxMapPut(new_items, key, obj); + } + elm = list->next(list); + index++; + } } -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; +static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) { + container->remove_items = FALSE; + cxMapFree(container->current_items); + free(container); } -void ui_tab_detach_document(UiContext *ctx) { - // TODO: remove? - //uic_context_detach_document(ctx->parent); +UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args) { + UiObject *current = uic_current_obj(obj); + UiContainer *ct = current->container; + UI_APPLY_LAYOUT1(current, args); + + GtkWidget *box = args.container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing); + ui_set_name_and_style(box, args.name, args.style_class); + GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box; + ct->add(ct, widget, TRUE); + + UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer)); + container->parent = obj; + container->widget = box; + container->container = ui_box_container(current, box, args.container); + container->create_ui = args.create_ui; + container->userdata = args.userdata; + container->subcontainer = args.subcontainer; + container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + container->current_items->collection.advanced_destructor = remove_item; + container->current_items->collection.destructor_data = container; + container->margin = args.sub_margin; + container->spacing = args.sub_spacing; + container->columnspacing = args.sub_columnspacing; + container->rowspacing = args.sub_rowspacing; + container->remove_items = TRUE; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = container; + list->update = update_itemlist; + update_itemlist(list, 0); + } + g_signal_connect( + box, + "destroy", + G_CALLBACK(destroy_itemlist_container), + container); + + return box; } + /* * -------------------- Layout Functions -------------------- * @@ -610,14 +1092,24 @@ void ui_layout_vexpand(UiObject *obj, UiBool expand) { ct->layout.vexpand = expand; } -void ui_layout_width(UiObject *obj, int width) { +void ui_layout_hfill(UiObject *obj, UiBool fill) { UiContainer *ct = uic_get_current_container(obj); - ct->layout.width = width; + ct->layout.hfill = fill; } -void ui_layout_gridwidth(UiObject *obj, int width) { +void ui_layout_vfill(UiObject *obj, UiBool fill) { UiContainer *ct = uic_get_current_container(obj); - ct->layout.gridwidth = width; + ct->layout.vfill = fill; +} + +void ui_layout_colspan(UiObject* obj, int cols) { + UiContainer* ct = uic_get_current_container(obj); + ct->layout.colspan = cols; +} + +void ui_layout_rowspan(UiObject* obj, int rows) { + UiContainer* ct = uic_get_current_container(obj); + ct->layout.rowspan = rows; } void ui_newline(UiObject *obj) { diff --git a/ui/gtk/container.h b/ui/gtk/container.h index f2c0740..3ea8fbb 100644 --- a/ui/gtk/container.h +++ b/ui/gtk/container.h @@ -33,6 +33,9 @@ #include "../ui/container.h" #include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -60,13 +63,16 @@ struct UiLayout { char *label; UiBool hexpand; UiBool vexpand; + UiBool hfill; + UiBool vfill; int width; - int gridwidth; + int colspan; + int rowspan; }; struct UiContainer { GtkWidget *widget; - GtkMenu *menu; + UIMENU menu; GtkWidget *current; void (*add)(UiContainer*, GtkWidget*, UiBool); @@ -77,6 +83,7 @@ struct UiContainer { typedef struct UiBoxContainer { UiContainer container; + UiSubContainerType type; UiBool has_fill; } UiBoxContainer; @@ -90,6 +97,7 @@ typedef struct UiGridContainer { #endif } UiGridContainer; +/* typedef struct UiPanedContainer { UiContainer container; GtkWidget *current_pane; @@ -97,23 +105,80 @@ typedef struct UiPanedContainer { int max; int cur; } UiPanedContainer; +*/ typedef struct UiTabViewContainer { UiContainer container; } UiTabViewContainer; +typedef void (*ui_select_tab_func)(UIWIDGET widget, int tab); +typedef void (*ui_add_tab_func)(UIWIDGET widget, int index, const char *name, UIWIDGET child); + +typedef struct UiGtkTabView { + UiObject *obj; + GtkWidget *widget; + ui_select_tab_func select_tab; + ui_select_tab_func remove_tab; + ui_add_tab_func add_tab; + UiSubContainerType subcontainer; + int margin; + int spacing; + int columnspacing; + int rowspacing; +} UiGtkTabView; + +typedef struct UiHeaderbarContainer { + UiContainer container; + GtkWidget *centerbox; + int part; + UiHeaderbarAlternative alternative; /* only used by fallback headerbar */ +} UiHeaderbarContainer; + +typedef struct UiGtkItemListContainer { + UiObject *parent; + GtkWidget *widget; + UiContainer *container; + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + UiSubContainerType subcontainer; + CxMap *current_items; + int margin; + int spacing; + int columnspacing; + int rowspacing; + bool remove_items; +} UiGtkItemListContainer; + GtkWidget* ui_gtk_vbox_new(int spacing); GtkWidget* ui_gtk_hbox_new(int spacing); +GtkWidget* ui_subcontainer_create( + UiSubContainerType type, + UiObject *newobj, + int spacing, + int columnspacing, + int rowspacing, + int margin); + UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); -UiContainer* ui_box_container(UiObject *obj, GtkWidget *box); +GtkWidget* ui_box_set_margin(GtkWidget *box, int margin); +UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type); + +UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type); void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); +GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing); UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid); void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); +UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); +void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + +UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander); +void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); + UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow); void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); @@ -125,10 +190,17 @@ void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill); void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill); +UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview); + +void ui_gtk_notebook_select_tab(GtkWidget *widget, int tab); + +#if GTK_CHECK_VERSION(3, 10, 0) +UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); +#endif -UiObject* ui_add_document_tab(UiDocumentView *view); -void ui_tab_set_document(UiContext *ctx, void *document); -void ui_tab_detach_document(UiContext *ctx); +UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); #ifdef __cplusplus } diff --git a/ui/gtk/display.c b/ui/gtk/display.c index 83ce3d4..78ff754 100644 --- a/ui/gtk/display.c +++ b/ui/gtk/display.c @@ -31,12 +31,14 @@ #include "display.h" #include "container.h" -#include #include "../common/context.h" #include "../common/object.h" +#include "../ui/display.h" + +#include static void set_alignment(GtkWidget *widget, float xalign, float yalign) { -#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16 +#if GTK_MAJOR_VERSION >= 4 || (GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16) gtk_label_set_xalign(GTK_LABEL(widget), xalign); gtk_label_set_yalign(GTK_LABEL(widget), yalign); #else @@ -44,30 +46,108 @@ static void set_alignment(GtkWidget *widget, float xalign, float yalign) { #endif } -UIWIDGET ui_label(UiObject *obj, char *label) { - GtkWidget *widget = gtk_label_new(label); +UIWIDGET ui_label_create(UiObject *obj, UiLabelArgs args) { + UiObject* current = uic_current_obj(obj); - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, widget, FALSE); + const char *css_class = NULL; + char *markup = NULL; + if(args.label) { + #if GTK_MAJOR_VERSION < 3 + switch(args.style) { + case UI_LABEL_STYLE_DEFAULT: break; + case UI_LABEL_STYLE_TITLE: { + cxmutstr m = cx_asprintf("%s", args.label); + markup = m.ptr; + args.label = NULL; + } + case UI_LABEL_STYLE_SUBTITLE: { + break; + } + case UI_LABEL_STYLE_DIM: { + break; + } + } +# else + switch(args.style) { + case UI_LABEL_STYLE_DEFAULT: break; + case UI_LABEL_STYLE_TITLE: { + css_class = "ui_label_title"; + break; + } + case UI_LABEL_STYLE_SUBTITLE: { + css_class = "subtitle"; + break; + } + case UI_LABEL_STYLE_DIM: { + css_class = "dim-label"; + break; + } + } +# endif + } + + + GtkWidget *widget = gtk_label_new(args.label); + if(markup) { + gtk_label_set_markup(GTK_LABEL(widget), markup); + free(markup); + } + + if(css_class) { + WIDGET_ADD_CSS_CLASS(widget, css_class); + } + + switch(args.align) { + case UI_ALIGN_DEFAULT: break; + case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break; + case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break; + case UI_ALIGN_CENTER: break; // TODO + } + + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); + if(var) { + UiString* value = (UiString*)var->value; + value->obj = widget; + value->get = ui_label_get; + value->set = ui_label_set; + } + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); return widget; } -UIWIDGET ui_llabel(UiObject *obj, char *label) { - UIWIDGET widget = ui_label(obj, label); - set_alignment(widget, 0, .5); - return widget; +UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) { + args.align = UI_ALIGN_LEFT; + return ui_label_create(obj, args); } -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_rlabel_create(UiObject* obj, UiLabelArgs args) { + args.align = UI_ALIGN_RIGHT; + return ui_label_create(obj, args); } -UIWIDGET ui_space(UiObject *obj) { +char* ui_label_get(UiString *s) { + if(s->value.ptr) { + s->value.free(s->value.ptr); + } + s->value.ptr = g_strdup(gtk_label_get_text(GTK_LABEL(s->obj))); + s->value.free = (ui_freefunc)g_free; + return s->value.ptr; +} + +void ui_label_set(UiString *s, const char *value) { + gtk_label_set_text(GTK_LABEL(s->obj), value); + if(s->value.ptr) { + s->value.free(s->value.ptr); + s->value.ptr = NULL; + s->value.free = NULL; + } +} + +UIWIDGET ui_space_deprecated(UiObject *obj) { GtkWidget *widget = gtk_label_new(""); UiContainer *ct = uic_get_current_container(obj); ct->add(ct, widget, TRUE); @@ -75,8 +155,8 @@ UIWIDGET ui_space(UiObject *obj) { return widget; } -UIWIDGET ui_separator(UiObject *obj) { -#if UI_GTK3 +UIWIDGET ui_separator_deprecated(UiObject *obj) { +#if GTK_MAJOR_VERSION >= 3 GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); #else GtkWidget *widget = gtk_hseparator_new(); @@ -89,40 +169,97 @@ UIWIDGET ui_separator(UiObject *obj) { /* ------------------------- 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); -} +typedef struct UiProgressBarRange { + double min; + double max; +} UiProgressBarRange; -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) { +UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) { + UiObject* current = uic_current_obj(obj); + GtkWidget *progressbar = gtk_progress_bar_new(); + if(args.max > args.min) { + UiProgressBarRange *range = malloc(sizeof(UiProgressBarRange)); + range->min = args.min; + range->max = args.max; + g_signal_connect( + progressbar, + "destroy", + G_CALLBACK(ui_destroy_userdata), + range); + g_object_set_data(G_OBJECT(progressbar), "ui_range", range); + } + + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE); if(var && var->value) { UiDouble *value = var->value; value->get = ui_progressbar_get; value->set = ui_progressbar_set; value->obj = progressbar; - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 0.5); + ui_progressbar_set(value, value->value); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, progressbar, FALSE); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, progressbar, FALSE); return progressbar; } double ui_progressbar_get(UiDouble *d) { - d->value = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj)); + UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range"); + double fraction = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj)); + if(range) { + fraction = range->min + (range->max - range->min) * fraction; + } + d->value = fraction; return d->value; } void ui_progressbar_set(UiDouble *d, double value) { - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value); d->value = value; + UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range"); + if(range) { + value = (value - range->min) / (range->max - range->min); + } + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value); +} + + +/* ------------------------- progress spinner ------------------------- */ + +UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *spinner = gtk_spinner_new(); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var && var->value) { + UiInteger *value = var->value; + value->get = ui_spinner_get; + value->set = ui_spinner_set; + value->obj = spinner; + ui_spinner_set(value, value->value); + } + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, spinner, FALSE); + + return spinner; +} + +int64_t ui_spinner_get(UiInteger *i) { + return i->value; +} + +void ui_spinner_set(UiInteger *i, int64_t value) { + i->value = value; + if(i->obj) { + GtkSpinner *spinner = GTK_SPINNER(i->obj); + if(value != 0) { + gtk_spinner_start(spinner); + } else { + gtk_spinner_stop(spinner); + } + } } diff --git a/ui/gtk/display.h b/ui/gtk/display.h index f9d94e8..504c7ad 100644 --- a/ui/gtk/display.h +++ b/ui/gtk/display.h @@ -36,9 +36,14 @@ extern "C" { #endif +char* ui_label_get(UiString *s); +void ui_label_set(UiString *s, const char *value); + UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var); double ui_progressbar_get(UiDouble *d); void ui_progressbar_set(UiDouble *d, double value); +int64_t ui_spinner_get(UiInteger *i); +void ui_spinner_set(UiInteger *i, int64_t value); #ifdef __cplusplus } diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c index d110ad4..8cb03b0 100644 --- a/ui/gtk/dnd.c +++ b/ui/gtk/dnd.c @@ -31,20 +31,21 @@ #include #include "dnd.h" -#include +#include +#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); + CxBuffer *buf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); char *uri; int i = 0; while((uri = uris[i]) != NULL) { - ucx_buffer_puts(buf, uri); - ucx_buffer_puts(buf, "\r\n"); + cxBufferPutString(buf, uri); + cxBufferPutString(buf, "\r\n"); } GdkAtom type = gdk_atom_intern("text/uri-list", FALSE); gtk_selection_data_set(selection_data, type, 8, (guchar*)buf->space, buf->pos); - ucx_buffer_free(buf); + cxBufferFree(buf); return TRUE; } static char** selection_data_get_uris(GtkSelectionData *selection_data) { @@ -55,6 +56,7 @@ static char** selection_data_get_uris(GtkSelectionData *selection_data) { #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); @@ -99,3 +101,195 @@ char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { } return NULL; } +*/ + +#if GTK_MAJOR_VERSION >= 4 + +void ui_selection_settext(UiDnD *sel, char *str, int len) { + if(!sel->providers) { + return; + } + + if(len == -1) { + len = strlen(str); + } + GBytes *bytes = g_bytes_new(str, len); + GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes); + g_bytes_unref(bytes); + + cxListAdd(sel->providers, &provider); +} + +void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) { + if(!sel->providers) { + return; + } + + GFile **files = calloc(nelm, sizeof(GFile*)); + for(int i=0;iproviders, &provider); + + g_slist_free_full ((GSList*)list, g_object_unref); + free(files); +} + +char* ui_selection_gettext(UiDnD *sel) { + if(!sel->value) { + return NULL; + } + + if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) { + const char *str = g_value_get_string(sel->value); + return str ? strdup(str) : NULL; + } + + return NULL; +} + +UiFileList ui_selection_geturis(UiDnD *sel) { + if(!sel->value) { + return (UiFileList){NULL,0}; + } + + if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) { + GSList *list = g_value_get_boxed(sel->value); + if(!list) { + return (UiFileList){NULL,0}; + } + guint size = g_slist_length(list); + + UiFileList flist; + flist.nfiles = size; + flist.files = calloc(size, sizeof(char*)); + int i=0; + while(list) { + GFile *file = list->data; + char *uri = g_file_get_uri(file); + flist.files[i++] = strdup(uri); + g_free(uri); + list = list->next; + } + return flist; + } + return (UiFileList){NULL,0}; +} + + +UiDnD* ui_create_dnd(void) { + UiDnD *dnd = malloc(sizeof(UiDnD)); + memset(dnd, 0, sizeof(UiDnD)); + dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16); + dnd->selected_action = 0; + dnd->delete = FALSE; + return dnd; +} + +void ui_dnd_free(UiDnD *dnd) { + cxListFree(dnd->providers); + free(dnd); +} + +UiDnDAction ui_dnd_result(UiDnD *dnd) { + switch(dnd->selected_action) { + case 0: return UI_DND_ACTION_NONE; + case GDK_ACTION_COPY: return UI_DND_ACTION_COPY; + case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE; + case GDK_ACTION_LINK: return UI_DND_ACTION_LINK; + default: break; + } + return UI_DND_ACTION_CUSTOM; +} + +#else + +void ui_selection_settext(UiDnD *sel, char *str, int len) { + gtk_selection_data_set_text(sel->data, str, len); +} + +void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) { + char **uriarray = calloc(nelm+1, sizeof(char*)); + for(int i=0;idata, uriarray); + free(uriarray); +} + +char* ui_selection_gettext(UiDnD *sel) { + if(!sel->data) { + return NULL; + } + + guchar *text = gtk_selection_data_get_text(sel->data); + if(text) { + char *textcp = strdup((char*)text); + g_free(text); + return textcp; + } + return NULL; +} + +UiFileList ui_selection_geturis(UiDnD *sel) { + if(!sel->data) { + return (UiFileList){NULL,0}; + } + + gchar **uris = gtk_selection_data_get_uris(sel->data); + if(uris) { + size_t al = 32; + char **array = malloc(al * sizeof(char*)); + size_t i = 0; + while(uris[i] != NULL) { + if(i >= al) { + al *= 2; + array = realloc(array, al * sizeof(char*)); + } + array[i] = strdup((char*)uris[i]); + i++; + } + g_strfreev(uris); + return (UiFileList){array,i}; + } + + return (UiFileList){NULL,0}; +} + +UiDnDAction ui_dnd_result(UiDnD *dnd) { + switch(dnd->selected_action) { + case 0: return UI_DND_ACTION_NONE; + case GDK_ACTION_COPY: return UI_DND_ACTION_COPY; + case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE; + case GDK_ACTION_LINK: return UI_DND_ACTION_LINK; + default: break; + } + return UI_DND_ACTION_CUSTOM; +} + + +UiDnD* ui_create_dnd(void) { + UiDnD *dnd = malloc(sizeof(UiDnD)); + memset(dnd, 0, sizeof(UiDnD)); + return dnd; +} + +void ui_dnd_free(UiDnD *dnd) { + free(dnd); +} + +#endif + +UiBool ui_dnd_need_delete(UiDnD *dnd) { + return dnd->delete; +} + +void ui_dnd_accept(UiDnD *dnd, UiBool accept) { + dnd->accept = accept; +} + diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h index 43ab5c2..e245f99 100644 --- a/ui/gtk/dnd.h +++ b/ui/gtk/dnd.h @@ -32,12 +32,37 @@ #include "../ui/dnd.h" #include "toolkit.h" +#include + #ifdef __cplusplus extern "C" { #endif +#if GTK_MAJOR_VERSION >= 4 + +struct UiDnD { + GtkDropTarget *target; + const GValue *value; + CxList *providers; + GdkDragAction selected_action; + gboolean delete; + gboolean accept; +}; + +#else + +struct UiDnD { + GdkDragContext *context; + GtkSelectionData *data; + GdkDragAction selected_action; + gboolean delete; + gboolean accept; +}; +#endif +UiDnD* ui_create_dnd(void); +void ui_dnd_free(UiDnD *dnd); #ifdef __cplusplus } diff --git a/ui/gtk/draw_cairo.c b/ui/gtk/draw_cairo.c index 6e1ebca..fc85c32 100644 --- a/ui/gtk/draw_cairo.c +++ b/ui/gtk/draw_cairo.c @@ -33,12 +33,19 @@ #include "draw_cairo.h" -#ifdef UI_GTK3 -gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) { + +#if GTK_MAJOR_VERSION >= 3 +static void ui_drawingarea_draw( + GtkDrawingArea *area, + cairo_t *cr, + int width, + int height, + gpointer data) +{ UiCairoGraphics g; - g.g.width = gtk_widget_get_allocated_width(w); - g.g.height = gtk_widget_get_allocated_height(w); - g.widget = w; + g.g.width = width; + g.g.height = height; + g.widget = GTK_WIDGET(area); g.cr = cr; UiDrawEvent *event = data; @@ -48,10 +55,18 @@ gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) { ev.document = event->obj->ctx->document; event->callback(&ev, &g.g, event->userdata); - +} +#endif + +#if UI_GTK3 +gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) { + int width = gtk_widget_get_allocated_width(w); + int height = gtk_widget_get_allocated_height(w); + ui_drawingarea_draw(GTK_DRAWING_AREA(w), cr, width, height, data); return FALSE; } -#else +#endif +#ifdef UI_GTK2 gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) { UiCairoGraphics g; g.g.width = w->allocation.width; @@ -74,7 +89,9 @@ gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) { // function from graphics.h void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) { -#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION >= 4 + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), ui_drawingarea_draw, event, NULL); +#elif GTK_MAJOR_VERSION == 3 g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(ui_drawingarea_expose), diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c index 631beb0..38ebe17 100644 --- a/ui/gtk/entry.c +++ b/ui/gtk/entry.c @@ -34,67 +34,49 @@ #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) { +UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args) { double min = 0; double max = 1000; - if(type == UI_VAR_RANGE) { + + UiObject* current = uic_current_obj(obj); + + UiVar *var = NULL; + if(args.varname) { + var = uic_get_var(obj->ctx, args.varname); + } + + if(!var) { + if(args.intvalue) { + var = uic_widget_var(obj->ctx, current->ctx, args.intvalue, NULL, UI_VAR_INTEGER); + } else if(args.doublevalue) { + var = uic_widget_var(obj->ctx, current->ctx, args.doublevalue, NULL, UI_VAR_DOUBLE); + } else if(args.rangevalue) { + var = uic_widget_var(obj->ctx, current->ctx, args.rangevalue, NULL, UI_VAR_RANGE); + } + } + + if(var && var->type == UI_VAR_RANGE) { UiRange *r = var->value; min = r->min; max = r->max; } - if(step == 0) { - step = 1; + if(args.step == 0) { + args.step = 1; } #ifdef UI_GTK2LEGACY if(min == max) { max = min + 1; } #endif - GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step); - gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits); + GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args.step); + ui_set_name_and_style(spin, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, spin, args.groups); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args.digits); + UiObserver **obs = NULL; if(var) { double value = 0; - UiObserver **obs = NULL; - switch(type) { + switch(var->type) { default: break; case UI_VAR_INTEGER: { UiInteger *i = var->value; @@ -127,26 +109,28 @@ UIWIDGET ui_spinner_var(UiObject *obj, double step, int digits, UiVar *var, UiVa } } 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); + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->var = var; + event->observers = obs; + event->callback = args.onchange; + event->userdata = args.onchangedata; + + g_signal_connect( + spin, + "value-changed", + G_CALLBACK(ui_spinner_changed), + event); + g_signal_connect( + spin, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, spin, FALSE); return spin; } @@ -161,15 +145,22 @@ void ui_spinner_setdigits(UIWIDGET spinner, int digits) { void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) { + gdouble value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner)); UiEvent e; e.obj = event->obj; e.window = event->obj->window; e.document = event->obj->ctx->document; - e.eventdata = event->var->value; - e.intval = 0; + e.eventdata = &value; + e.intval = (int64_t)value; - UiObserver *observer = *event->observers; - ui_notify_evt(observer, &e); + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(event->observers) { + UiObserver *observer = *event->observers; + ui_notify_evt(observer, &e); + } } diff --git a/ui/gtk/entry.h b/ui/gtk/entry.h index 34961ab..ae43b0f 100644 --- a/ui/gtk/entry.h +++ b/ui/gtk/entry.h @@ -22,7 +22,6 @@ 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); diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c index b7c279f..dcaff88 100644 --- a/ui/gtk/graphics.c +++ b/ui/gtk/graphics.c @@ -51,6 +51,7 @@ UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { } +#if GTK_MAJOR_VERSION <= 3 static gboolean widget_button_pressed( GtkWidget *widget, GdkEvent *event, @@ -82,14 +83,18 @@ static gboolean widget_button_pressed( } return TRUE; } +#endif 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); +#if GTK_MAJOR_VERSION >= 4 + *width = gtk_widget_get_width(drawingarea); + *height = gtk_widget_get_height(drawingarea); +#elif GTK_MAJOR_VERSION == 3 + *width = gtk_widget_get_allocated_width(drawingarea); + *height = gtk_widget_get_allocated_height(drawingarea); #else - *width = drawingarea->allocation.width; - *height = drawingarea->allocation.height; + *width = drawingarea->allocation.width; + *height = drawingarea->allocation.height; #endif } @@ -98,12 +103,17 @@ void ui_drawingarea_redraw(UIWIDGET drawingarea) { } void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) { +#if GTK_MAJOR_VERSION >= 4 + // TODO +#else gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK); if(f) { UiEventData *event = malloc(sizeof(UiEventData)); event->obj = obj; event->callback = f; event->userdata = u; + event->customdata = NULL; + event->value = 0; g_signal_connect(G_OBJECT(widget), "button-press-event", @@ -112,6 +122,7 @@ void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, } else { // TODO: warning } +#endif } diff --git a/ui/gtk/headerbar.c b/ui/gtk/headerbar.c new file mode 100644 index 0000000..e1b80b5 --- /dev/null +++ b/ui/gtk/headerbar.c @@ -0,0 +1,174 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "headerbar.h" + +#include "button.h" +#include "menu.h" + +#if GTK_CHECK_VERSION(3, 10, 0) + +void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) { + CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER); + + UiToolbarMenuItem *appmenu = uic_get_appmenu(); + if(appmenu) { + ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT); +} + +static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) { + switch(i->type) { + case UI_TOOLBAR_ITEM: { + ui_add_headerbar_item(headerbar, box, (UiToolbarItem*)i, obj, pos); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + ui_add_headerbar_toggleitem(headerbar, box, (UiToolbarToggleItem*)i, obj, pos); + break; + } + case UI_TOOLBAR_MENU: { + ui_add_headerbar_menu(headerbar, box, (UiToolbarMenuItem*)i, obj, pos); + break; + } + default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type); + } +} + +static void headerbar_add(GtkWidget *headerbar, GtkWidget *box, GtkWidget *item, enum UiToolbarPos pos) { + switch(pos) { + case UI_TOOLBAR_LEFT: { + UI_HEADERBAR_PACK_START(headerbar, item); + break; + } + case UI_TOOLBAR_CENTER: { + +#if GTK_MAJOR_VERSION >= 4 + gtk_box_append(GTK_BOX(box), item); +#else + gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0); +#endif + break; + } + case UI_TOOLBAR_RIGHT: { + UI_HEADERBAR_PACK_END(headerbar, item); + break; + } + } +} + +void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos) { + GtkWidget *box = NULL; + + if(pos == UI_TOOLBAR_CENTER && cxListSize(items) > 0) { + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + UI_HEADERBAR_SET_TITLE_WIDGET(headerbar, box); + } + + CxIterator i = pos == UI_TOOLBAR_RIGHT ? cxListBackwardsIterator(items) : cxListIterator(items); + cx_foreach(char*, def, i) { + UiToolbarItemI* item = uic_toolbar_get_item(def); + if (!item) { + fprintf(stderr, "unknown toolbar item: %s\n", def); + continue; + } + create_item(obj, headerbar, box, item, pos); + } +} + +void ui_add_headerbar_item( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE); + ui_set_widget_groups(obj->ctx, button, item->args.groups); + WIDGET_ADD_CSS_CLASS(button, "flat"); + headerbar_add(headerbar, box, button, pos); +} + +void ui_add_headerbar_toggleitem( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarToggleItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + GtkWidget *button = gtk_toggle_button_new(); + ui_set_widget_groups(obj->ctx, button, item->args.groups); + WIDGET_ADD_CSS_CLASS(button, "flat"); + ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); + headerbar_add(headerbar, box, button, pos); +} + +void ui_add_headerbar_menu( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarMenuItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + + +#if GTK_MAJOR_VERSION >= 4 + GtkWidget *menubutton = gtk_menu_button_new(); + if(item->args.label) { + gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label); + } + if(item->args.icon) { + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); + } + + if(!item->args.label && !item->args.icon) { + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), "open-menu-symbolic"); + } + + GMenu *menu = g_menu_new(); + ui_gmenu_add_menu_items(menu, 0, &item->menu, obj); + + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu)); +#else + GtkWidget *menubutton = gtk_menu_button_new(); + + // TODO + + +#endif + + headerbar_add(headerbar, box, menubutton, pos); +} + +#endif // GTK_CHECK_VERSION(3, 10, 0) diff --git a/ui/gtk/headerbar.h b/ui/gtk/headerbar.h new file mode 100644 index 0000000..acea9fe --- /dev/null +++ b/ui/gtk/headerbar.h @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HEADERBAR_H +#define HEADERBAR_H + +#include "toolkit.h" +#include "../ui/toolbar.h" +#include "../common/toolbar.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if GTK_CHECK_VERSION(3, 10, 0) + +#ifdef UI_LIBADWAITA +#define UI_HEADERBAR AdwHeaderBar* +#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h) +#define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w) +#else +#define UI_HEADERBAR GtkHeaderBar* +#define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h) +#define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w) +#if GTK_MAJOR_VERSION >= 4 +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w) +#else +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w) +#endif +#endif + +void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar); + +void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos); + +void ui_add_headerbar_item( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +void ui_add_headerbar_toggleitem( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarToggleItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +void ui_add_headerbar_menu( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarMenuItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* HEADERBAR_H */ + diff --git a/ui/gtk/icon.c b/ui/gtk/icon.c new file mode 100644 index 0000000..608ad04 --- /dev/null +++ b/ui/gtk/icon.c @@ -0,0 +1,208 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "icon.h" +#include "../common/properties.h" + +static CxMap *image_map; + +static GtkIconTheme *icon_theme; + +#if GTK_MAJOR_VERSION >= 4 +#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_for_display(gdk_display_get_default()) +#else +#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_default() +#endif + +void ui_image_init(void) { + image_map = cxHashMapCreateSimple(CX_STORE_POINTERS); + + icon_theme = ICONTHEME_GET_DEFAULT(); +} + +// **** deprecated functions **** + +GdkPixbuf* ui_get_image(const char *name) { + UiImage *img = cxMapGet(image_map, name); + if(img) { + return img->pixbuf; + } else { + //ui_add_image(name, name); + //return ucx_map_cstr_get(image_map, name); + // TODO + return NULL; + } +} + +// **** deprecated2**** + +static UiIcon* get_icon(const char *name, int size, int scale) { +#if GTK_MAJOR_VERSION >= 4 + GtkIconPaintable *info = gtk_icon_theme_lookup_icon(icon_theme, name, NULL, size, scale, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR); +#elif defined(UI_SUPPORTS_SCALE) + GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0); +#else + GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0); +#endif + if(info) { + UiIcon *icon = malloc(sizeof(UiIcon)); + icon->info = info; + icon->pixbuf = NULL; + return icon; + } + return NULL; +} + +UiIcon* ui_icon(const char* name, size_t size) { + return get_icon(name, size, ui_get_scalefactor()); +} + +UiIcon* ui_imageicon(const char* file) { + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(file, &error); + if(!pixbuf) { + fprintf(stderr, "UiError: Cannot load image: %s\n", file); + return NULL; + } + + UiIcon *icon = malloc(sizeof(UiIcon)); + icon->info = NULL; + icon->pixbuf = pixbuf; + return icon; +} + +void ui_icon_free(UiIcon* icon) { + if(icon->info) { + g_object_unref(icon->info); + } + if(icon->pixbuf) { + g_object_unref(icon->pixbuf); + } + free(icon); +} + +UiIcon* ui_foldericon(size_t size) { + return ui_icon("folder", size); +} + +UiIcon* ui_fileicon(size_t size) { + UiIcon *icon = ui_icon("file", size); +#if GTK_MAJOR_VERSION >= 4 + GFile *file = gtk_icon_paintable_get_file(icon->info); + char *path = g_file_get_path(file); + if(!path) { + icon = ui_icon("application-x-generic", size); + } +#else + if(!icon) { + icon = ui_icon("application-x-generic", size); + } +#endif + return icon; +} + +UiIcon* ui_icon_unscaled(const char *name, int size) { + return get_icon(name, size, 1); +} + +#if GTK_MAJOR_VERSION >= 4 +GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) { + if(!icon->pixbuf) { + GFile *file = gtk_icon_paintable_get_file(icon->info); + GError *error = NULL; + char *path = g_file_get_path(file); + icon->pixbuf = gdk_pixbuf_new_from_file(path, &error); + } + return icon->pixbuf; +} +#else +GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) { + if(!icon->pixbuf) { + GError *error = NULL; + icon->pixbuf = gtk_icon_info_load_icon(icon->info, &error); + } + return icon->pixbuf; +} +#endif + +/* +UiImage* ui_icon_image(UiIcon *icon) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + if(pixbuf) { + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + return img; + } + return NULL; +} +*/ + +/* +UiImage* ui_image(const char *filename) { + return ui_named_image(filename, NULL); +} + +UiImage* ui_named_image(const char *filename, const char *name) { + char *path = uic_get_image_path(filename); + if(!path) { + fprintf(stderr, "UiError: pixmaps directory not set\n"); + return NULL; + } + UiImage *img = ui_load_image_from_path(path, name); + free(path); + return img; +} + +UiImage* ui_load_image_from_path(const char *path, const char *name) { + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error); + if(!pixbuf) { + fprintf(stderr, "UiError: Cannot load image: %s\n", path); + return NULL; + } + + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + if(name) { + cxMapPut(image_map, name, img); + } + return img; +} +*/ + +void ui_free_image(UiImage *img) { + g_object_unref(img->pixbuf); + free(img); +} diff --git a/ui/motif/stock.h b/ui/gtk/icon.h similarity index 74% rename from ui/motif/stock.h rename to ui/gtk/icon.h index 03e643f..d811817 100644 --- a/ui/motif/stock.h +++ b/ui/gtk/icon.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,31 +26,42 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef STOCK_H -#define STOCK_H +#ifndef ICON_H +#define ICON_H -#include "../ui/stock.h" +#include "../ui/icons.h" #ifdef __cplusplus extern "C" { #endif -typedef struct UiStockItem { - char *label; - char *accelerator; - char *accelerator_label; - // TODO: icon -} UiStockItem; +#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10 +#define UI_SUPPORTS_SCALE +#endif + -void ui_stock_init(); +struct UiIcon { +#if GTK_MAJOR_VERSION >= 4 + GtkIconPaintable *info; +#else + GtkIconInfo *info; +#endif + GdkPixbuf *pixbuf; +}; + +struct UiImage { + GdkPixbuf *pixbuf; +}; + +void ui_image_init(void); -void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon); +GdkPixbuf* ui_get_image(const char *name); -UiStockItem* ui_get_stock_item(char *id); +GdkPixbuf* ui_icon_pixbuf(UiIcon *icon); #ifdef __cplusplus } #endif -#endif /* STOCK_H */ +#endif /* ICON_H */ diff --git a/ui/gtk/image.c b/ui/gtk/image.c index 7b37be7..3c2a0e6 100644 --- a/ui/gtk/image.c +++ b/ui/gtk/image.c @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,111 +26,110 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include -#include - -#include "toolkit.h" #include "image.h" -#include "../common/properties.h" -static UcxMap *image_map; +#include "container.h" +#include "menu.h" +#include "../common/context.h" +#include "../common/object.h" -static GtkIconTheme *icon_theme; -void ui_image_init(void) { - image_map = ucx_map_new(8); +UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) { + UiObject *current = uic_current_obj(obj); - 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); + GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW(); +#if GTK_CHECK_VERSION(4, 0, 0) + GtkWidget *image = gtk_picture_new(); #else - GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0); + GtkWidget *image = gtk_image_new(); #endif - if(info) { - UiIcon *icon = malloc(sizeof(UiIcon)); - icon->info = info; - return icon; + + ui_set_name_and_style(image, args.name, args.style_class); + +#if GTK_MAJOR_VERSION < 4 + GtkWidget *eventbox = gtk_event_box_new(); + SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox); + gtk_container_add(GTK_CONTAINER(eventbox), image); +#else + SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image); + GtkWidget *eventbox = image; +#endif + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scrolledwindow, TRUE); + + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC); + if(var) { + UiGeneric *value = var->value; + value->get = ui_imageviewer_get; + value->get_type = ui_imageviewer_get_type; + value->set = ui_imageviewer_set; + value->obj = image; + if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) { + GdkPixbuf *pixbuf = value->value; + value->value = NULL; + ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE); + } } - return NULL; -} - -UiIcon* ui_icon(const char *name, int size) { - return get_icon(name, size, ui_get_scalefactor()); + + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox); + ui_widget_set_contextmenu(eventbox, menu); + } + + return scrolledwindow; } -UiIcon* ui_icon_unscaled(const char *name, int size) { - return get_icon(name, size, 1); +void* ui_imageviewer_get(UiGeneric *g) { + return g->value; } -void ui_free_icon(UiIcon *icon) { - g_object_unref(icon->info); - free(icon); +const char* ui_imageviewer_get_type(UiGeneric *g) { + } -UiImage* ui_icon_image(UiIcon *icon) { - GError *error = NULL; - GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); +int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) { + if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) { + return 1; + } + + // TODO: do we need to free the previous value here? + + g->value = value; + g->type = type; + GdkPixbuf *pixbuf = value; + if(pixbuf) { - UiImage *img = malloc(sizeof(UiImage)); - img->pixbuf = pixbuf; - return img; + int width = gdk_pixbuf_get_width(pixbuf); + int height = gdk_pixbuf_get_height(pixbuf); + +#if GTK_CHECK_VERSION(4, 0, 0) + GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf); + gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture)); +#else + gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf); +#endif + gtk_widget_set_size_request(g->obj, width, height); } - return NULL; -} -UiImage* ui_image(const char *filename) { - return ui_named_image(filename, NULL); + + return 0; } -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) { + +int ui_image_load_file(UiGeneric *obj, const char *path) { 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; + return 1; } - UiImage *img = malloc(sizeof(UiImage)); - img->pixbuf = pixbuf; - if(name) { - ucx_map_cstr_put(image_map, name, img); + if(obj->set) { + obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE); + } else { + obj->value = pixbuf; } - return img; -} - -void ui_free_image(UiImage *img) { - g_object_unref(img->pixbuf); - free(img); + + return 0; } diff --git a/ui/gtk/image.h b/ui/gtk/image.h index d335d54..00c205d 100644 --- a/ui/gtk/image.h +++ b/ui/gtk/image.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2017 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -27,35 +27,23 @@ */ #ifndef IMAGE_H -#define IMAGE_H +#define IMAGE_H #include "../ui/image.h" +#include "toolkit.h" -#ifdef __cplusplus +#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); +void* ui_imageviewer_get(UiGeneric *g); +const char* ui_imageviewer_get_type(UiGeneric *g); +int ui_imageviewer_set(UiGeneric *g, void *value, const char *type); -#ifdef __cplusplus +#ifdef __cplusplus } #endif -#endif /* IMAGE_H */ +#endif /* IMAGE_H */ diff --git a/ui/gtk/list.c b/ui/gtk/list.c new file mode 100644 index 0000000..50eb6e5 --- /dev/null +++ b/ui/gtk/list.c @@ -0,0 +1,1790 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 +#include + +#include "list.h" +#include "icon.h" +#include "menu.h" +#include "dnd.h" + + +void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} + +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +#if GTK_CHECK_VERSION(4, 10, 0) + + +/* BEGIN GObject wrapper for generic pointers */ + +typedef struct _ObjWrapper { + GObject parent_instance; + void *data; +} ObjWrapper; + +typedef struct _ObjWrapperClass { + GObjectClass parent_class; +} ObjWrapperClass; + +G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) + +static void obj_wrapper_class_init(ObjWrapperClass *klass) { + +} + +static void obj_wrapper_init(ObjWrapper *self) { + self->data = NULL; +} + +ObjWrapper* obj_wrapper_new(void* data) { + ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); + obj->data = data; + return obj; +} + +/* END GObject wrapper for generic pointers */ + +static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *image = gtk_image_new(); + GtkWidget *label = gtk_label_new(NULL); + BOX_ADD(hbox, image); + BOX_ADD(hbox, label); + gtk_list_item_set_child(item, hbox); + g_object_set_data(G_OBJECT(hbox), "image", image); + g_object_set_data(G_OBJECT(hbox), "label", label); + } else if(type == UI_ICON) { + GtkWidget *image = gtk_image_new(); + gtk_list_item_set_child(item, image); + } else { + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(label), 0); + gtk_list_item_set_child(item, label); + } +} + +static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + + ObjWrapper *obj = gtk_list_item_get_item(item); + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + + void *data = model->getvalue(obj->data, col->data_column); + GtkWidget *child = gtk_list_item_get_child(item); + + bool freevalue = TRUE; + switch(type) { + case UI_STRING: { + freevalue = FALSE; + } + case UI_STRING_FREE: { + gtk_label_set_label(GTK_LABEL(child), data); + if(freevalue) { + free(data); + } + break; + } + case UI_INTEGER: { + intptr_t intvalue = (intptr_t)data; + char buf[32]; + snprintf(buf, 32, "%d", (int)intvalue); + gtk_label_set_label(GTK_LABEL(child), buf); + break; + } + case UI_ICON: { + UiIcon *icon = data; + if(icon) { + gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); + } + break; + } + case UI_ICON_TEXT: { + freevalue = FALSE; + } + case UI_ICON_TEXT_FREE: { + void *data2 = model->getvalue(obj->data, col->data_column+1); + GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); + GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); + if(data && image) { + UiIcon *icon = data; + gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); + } + if(data2 && label) { + gtk_label_set_label(GTK_LABEL(label), data2); + } + if(freevalue) { + free(data2); + } + break; + } + } +} + +static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { + GtkSelectionModel *selection_model; + if(multiselection) { + selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); + } else { + selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); + } + g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); + return selection_model; +} + +static UiListView* create_listview(UiObject *obj, UiListArgs args) { + UiListView *tableview = malloc(sizeof(UiListView)); + memset(tableview, 0, sizeof(UiListView)); + tableview->obj = obj; + tableview->model = args.model; + tableview->onactivate = args.onactivate; + tableview->onactivatedata = args.onactivatedata; + tableview->onselection = args.onselection; + tableview->onselectiondata = args.onselectiondata; + tableview->ondragstart = args.ondragstart; + tableview->ondragstartdata = args.ondragstartdata; + tableview->ondragcomplete = args.ondragcomplete; + tableview->ondragcompletedata = args.ondragcompletedata; + tableview->ondrop = args.ondrop; + tableview->ondropdata = args.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + return tableview; +} + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_table_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkSelectionModel *selection_model = create_selection_model(listview, ls, args.multiselection); + GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + // columnview and listview can use the same callback function, because + // the first parameter (which is technically a different pointer type) + // is ignored + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_tableview_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); + gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, view, FALSE); + return view; +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + //g_list_store_append(ls, v1); + + // create obj to store all relevant data we need for handling events + // and list updates + UiListView *tableview = create_listview(obj, args); + + GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection); + GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init tableview + tableview->widget = view; + tableview->var = var; + tableview->liststore = ls; + tableview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + + // create columns from UiModel + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + tableview->columns = calloc(columns, sizeof(UiColData)); + + int addi = 0; + for(int i=0;icolumns[i].listview = tableview; + tableview->columns[i].model_column = i; + tableview->columns[i].data_column = i+addi; + + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + // icon+text has 2 data columns + addi++; + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + UiColData *col = &tableview->columns[i]; + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); + } + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = tableview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { + UiListSelection sel = { 0, NULL }; + GtkBitset *bitset = gtk_selection_model_get_selection(model); + int n = gtk_bitset_get_size(bitset); + printf("bitset %d\n", n); + + gtk_bitset_unref(bitset); + return sel; +} + +static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + if(cb) { + cb(&event, cbdata); + } +} + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { + UiListView *view = userdata; + listview_event(view->onactivate, view->onactivatedata, view); +} + +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { + UiListView *view = userdata; + free(view->selection.rows); + view->selection.count = 0; + view->selection.rows = NULL; + + CX_ARRAY_DECLARE(int, newselection); + cx_array_initialize(newselection, 8); + + size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); + + for(size_t i=0;iselectionmodel, i)) { + int s = (int)i; + cx_array_simple_add(newselection, s); + } + } + + if(newselection_size > 0) { + view->selection.count = newselection_size; + view->selection.rows = newselection; + } else { + free(newselection); + } + + listview_event(view->onselection, view->onselectiondata, view); +} + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) { + UiListView *view = userdata; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + int sel2 = (int)selection; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = &sel2; + } + + if(view->onactivate) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + view->onactivate(&event, view->onactivatedata); + } +} + +void ui_update_liststore(GListStore *liststore, UiList *list) { + g_list_store_remove_all(liststore); + void *elm = list->first(list); + while(elm) { + ObjWrapper *obj = obj_wrapper_new(elm); + g_list_store_append(liststore, obj); + elm = list->next(list); + } +} + +void ui_listview_update2(UiList *list, int i) { + UiListView *view = list->obj; + ui_update_liststore(view->liststore, view->var->value); +} + +UiListSelection ui_listview_getselection2(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection; + selection.count = view->selection.count; + selection.rows = calloc(selection.count, sizeof(int)); + memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); + return selection; +} + +void ui_listview_setselection2(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + UiListSelection newselection; + newselection.count = view->selection.count; + if(selection.count > 0) { + newselection.rows = calloc(newselection.count, sizeof(int)); + memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); + } else { + newselection.rows = NULL; + } + free(view->selection.rows); + view->selection = newselection; + + gtk_selection_model_unselect_all(view->selectionmodel); + if(selection.count > 0) { + for(int i=0;iselectionmodel, selection.rows[i], FALSE); + } + } +} + +UiListSelection ui_combobox_getselection(UiList *list) { + UiListView *view = list->obj; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = (int)selection; + } + return sel; +} + +void ui_combobox_setselection(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + if(selection.count > 0) { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]); + } else { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION); + } +} + +#else + +static GtkListStore* create_list_store(UiList *list, UiModel *model) { + int columns = model->columns; + GType types[2*columns]; + int c = 0; + for(int i=0;itypes[i]) { + case UI_STRING: + case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; + case UI_INTEGER: types[c] = G_TYPE_INT; break; + case UI_ICON: types[c] = G_TYPE_OBJECT; break; + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + types[c] = G_TYPE_OBJECT; + types[++c] = G_TYPE_STRING; + } + } + } + + GtkListStore *store = gtk_list_store_newv(c, types); + + if(list) { + void *elm = list->first(list); + while(elm) { + // insert new row + GtkTreeIter iter; + gtk_list_store_insert (store, &iter, -1); + + // set column values + int c = 0; + for(int i=0;igetvalue(elm, c); + + GValue value = G_VALUE_INIT; + switch(model->types[i]) { + case UI_STRING: + case UI_STRING_FREE: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(model->types[i] == UI_STRING_FREE) { + free(data); + } + break; + } + case UI_INTEGER: { + g_value_init(&value, G_TYPE_INT); + int *intptr = data; + g_value_set_int(&value, *intptr); + break; + } + case UI_ICON: { + g_value_init(&value, G_TYPE_OBJECT); + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + g_value_set_object(&value, icon->info); // TODO: does this work? +#else + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + + if(icon->pixbuf) { + g_value_set_object(&value, icon->pixbuf); + } +#endif + break; + } + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + if(icon) { + GValue iconvalue = G_VALUE_INIT; + g_value_init(&iconvalue, G_TYPE_OBJECT); + g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); + gtk_list_store_set_value(store, &iter, c, &iconvalue); + } +#else + GValue pixbufvalue = G_VALUE_INIT; + if(icon) { + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + g_value_init(&pixbufvalue, G_TYPE_OBJECT); + g_value_set_object(&pixbufvalue, icon->pixbuf); + gtk_list_store_set_value(store, &iter, c, &pixbufvalue); + } +#endif + c++; + + char *str = model->getvalue(elm, c); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, str); + if(model->types[i] == UI_ICON_TEXT_FREE) { + free(str); + } + break; + } + } + + gtk_list_store_set_value(store, &iter, c, &value); + } + + // next row + elm = list->next(list); + } + } + + return store; +} + + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + ui_set_name_and_style(view, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, view, args.groups); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 +#if GTK_MINOR_VERSION >= 8 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + // TODO: implement for older gtk3 +#endif +#else + // TODO: implement for gtk2 +#endif + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + UiListView *listview = malloc(sizeof(UiListView)); + listview->obj = obj; + listview->widget = view; + listview->var = var; + listview->model = model; + listview->selection.count = 0; + listview->selection.rows = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + list->obj = listview; + + // add callback + UiTreeEventData *event = malloc(sizeof(UiTreeEventData)); + event->obj = obj; + event->activate = args.onactivate; + event->activatedata = args.onactivatedata; + event->selection = args.onselection; + event->selectiondata = args.onselectiondata; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + if(args.onactivate) { + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + if(args.onselection) { + GtkTreeSelection *selection = gtk_tree_view_get_selection( + GTK_TREE_VIEW(view)); + g_signal_connect( + selection, + "changed", + G_CALLBACK(ui_listview_selection_event), + event); + } + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view); + ui_widget_set_contextmenu(view, menu); + } + + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + int addi = 0; + for(int i=0;itypes[i] == UI_ICON_TEXT) { + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, model->titles[i]); + + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); + + gtk_tree_view_column_pack_end(column, textrenderer, TRUE); + gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); + + + gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); + + addi++; + } else if (model->types[i] == UI_ICON) { + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + iconrenderer, + "pixbuf", + i + addi, + NULL); + } else { + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + renderer, + "text", + i + addi, + NULL); + } + + int colsz = model->columnsize[i]; + if(colsz > 0) { + gtk_tree_view_column_set_fixed_width(column, colsz); + } else if(colsz < 0) { + gtk_tree_view_column_set_expand(column, TRUE); + } + + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + +#endif + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); + //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); + + // add TreeView as observer to the UiList to update the TreeView if the + // data changes + UiListView *tableview = malloc(sizeof(UiListView)); + tableview->obj = obj; + tableview->widget = view; + tableview->var = var; + tableview->model = model; + tableview->ondragstart = args.ondragstart; + tableview->ondragstartdata = args.ondragstartdata; + tableview->ondragcomplete = args.ondragcomplete; + tableview->ondragcompletedata = args.ondragcompletedata; + tableview->ondrop = args.ondrop; + tableview->ondropdata = args.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + list->obj = tableview; + + // add callback + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activate = args.onactivate; + event->selection = args.onselection; + event->activatedata = args.onactivatedata; + event->selectiondata = args.onselectiondata; + if(args.onactivate) { + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + if(args.onselection) { + GtkTreeSelection *selection = gtk_tree_view_get_selection( + GTK_TREE_VIEW(view)); + g_signal_connect( + selection, + "changed", + G_CALLBACK(ui_listview_selection_event), + event); + } + // TODO: destroy callback + + + if(args.ondragstart) { + ui_listview_add_dnd(tableview, &args); + } + if(args.ondrop) { + ui_listview_enable_drop(tableview, &args); + } + + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + if(args.multiselection) { + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + } + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area); +#if GTK_MAJOR_VERSION >= 4 + ui_widget_set_contextmenu(scroll_area, menu); +#else + ui_widget_set_contextmenu(view, menu); +#endif + } + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + + + +void ui_listview_update(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(list, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), + NULL); + return selection; +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + UiListView *view = list->obj; + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); + GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); + gtk_tree_selection_select_path(sel, path); + //g_object_unref(path); +} + + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); + ui_set_name_and_style(combobox, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, combobox, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, combobox, FALSE); + current->container->current = combobox; + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new(); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + } + + uicbox->var = var; + uicbox->model = model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + if(list) { + list->update = ui_combobox_modelupdate; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + list->obj = uicbox; + } + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); + gtk_cell_layout_set_attributes( + GTK_CELL_LAYOUT(combobox), + renderer, + "text", + 0, + NULL); + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view->var->value, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(store); +} + +UiListSelection ui_combobox_getselection(UiList *list) { + UiListView *combobox = list->obj; + UiListSelection ret; + ret.rows = malloc(sizeof(int*)); + ret.count = 1; + ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); + return ret; +} + +void ui_combobox_setselection(UiList *list, UiListSelection selection) { + UiListView *combobox = list->obj; + if(selection.count > 0) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); + } +} + + + + +void ui_listview_activate_event( + GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(treeview), + event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->activate(&e, event->activatedata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection(treeselection, event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->selection(&e, event->selectiondata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection ls; + ls.count = g_list_length(rows); + ls.rows = calloc(ls.count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls.rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +#endif + + +#if GTK_MAJOR_VERSION >= 4 + +static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { + //printf("drag prepare\n"); + UiListView *listview = data; + + UiDnD *dnd = ui_create_dnd(); + GdkContentProvider *provider = NULL; + + + if(listview->ondragstart) { + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = dnd; + event.intval = 0; + listview->ondragstart(&event, listview->ondragstartdata); + } + + size_t numproviders = cxListSize(dnd->providers); + if(numproviders > 0) { + GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0); + provider = gdk_content_provider_new_union(providers, numproviders); + } + ui_dnd_free(dnd); + + return provider; +} + +static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) { + //printf("drag begin\n"); +} + +static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) { + //printf("drag end\n"); + UiListView *listview = user_data; + if(listview->ondragcomplete) { + UiDnD dnd; + dnd.target = NULL; + dnd.value = NULL; + dnd.providers = NULL; + dnd.selected_action = gdk_drag_get_selected_action(drag); + dnd.delete = delete_data; + dnd.accept = FALSE; + + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &dnd; + event.intval = 0; + listview->ondragcomplete(&event, listview->ondragcompletedata); + } +} + +static gboolean ui_listview_drop( + GtkDropTarget *target, + const GValue* value, + gdouble x, + gdouble y, + gpointer user_data) +{ + UiListView *listview = user_data; + UiDnD dnd; + dnd.providers = NULL; + dnd.target = target; + dnd.value = value; + dnd.selected_action = 0; + dnd.delete = FALSE; + dnd.accept = FALSE; + + if(listview->ondrop) { + dnd.accept = TRUE; + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &dnd; + event.intval = 0; + listview->ondrop(&event, listview->ondropdata); + } + + return dnd.accept; +} + +void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { + GtkDragSource *dragsource = gtk_drag_source_new(); + gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource)); + g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview); + g_signal_connect( + dragsource, + "drag-begin", + G_CALLBACK(ui_listview_drag_begin), + listview); + g_signal_connect( + dragsource, + "drag-end", + G_CALLBACK(ui_listview_drag_end), + listview); +} + +void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { + GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY); + gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target)); + GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING }; + gtk_drop_target_set_gtypes(target, default_types, 2); + g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview); +} + +#else + +static GtkTargetEntry targetentries[] = +{ + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, +}; + +static void ui_listview_drag_getdata( + GtkWidget* self, + GdkDragContext* context, + GtkSelectionData* data, + guint info, + guint time, + gpointer user_data) +{ + UiListView *listview = user_data; + UiDnD dnd; + dnd.context = context; + dnd.data = data; + dnd.selected_action = 0; + dnd.delete = FALSE; + dnd.accept = FALSE; + + if(listview->ondragstart) { + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &dnd; + event.intval = 0; + listview->ondragstart(&event, listview->ondragstartdata); + } +} + +static void ui_listview_drag_end( + GtkWidget *widget, + GdkDragContext *context, + guint time, + gpointer user_data) +{ + UiListView *listview = user_data; + UiDnD dnd; + dnd.context = context; + dnd.data = NULL; + dnd.selected_action = gdk_drag_context_get_selected_action(context); + dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE; + dnd.accept = FALSE; + if(listview->ondragcomplete) { + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &dnd; + event.intval = 0; + listview->ondragcomplete(&event, listview->ondragcompletedata); + } +} + +void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { + gtk_tree_view_enable_model_drag_source( + GTK_TREE_VIEW(listview->widget), + GDK_BUTTON1_MASK, + targetentries, + 2, + GDK_ACTION_COPY); + + g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview); + g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview); +} + + + + +static void ui_listview_drag_data_received( + GtkWidget *self, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + UiListView *listview = user_data; + UiDnD dnd; + dnd.context = context; + dnd.data = data; + dnd.selected_action = 0; + dnd.delete = FALSE; + dnd.accept = FALSE; + + if(listview->ondrop) { + dnd.accept = TRUE; + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &dnd; + event.intval = 0; + listview->ondrop(&event, listview->ondropdata); + } +} + +void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { + gtk_tree_view_enable_model_drag_dest( + GTK_TREE_VIEW(listview->widget), + targetentries, + 3, + GDK_ACTION_COPY); + if(listview->ondrop) { + g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview); + } +} + +#endif + + +GtkWidget* ui_get_tree_widget(UIWIDGET widget) { + return SCROLLEDWINDOW_GET_CHILD(widget); +} + +static char** targets2array(char *target0, va_list ap, int *nelm) { + int al = 16; + char **targets = calloc(16, sizeof(char*)); + targets[0] = target0; + + int i = 1; + char *target; + while((target = va_arg(ap, char*)) != NULL) { + if(i >= al) { + al *= 2; + targets = realloc(targets, al*sizeof(char*)); + } + targets[i] = target; + i++; + } + + *nelm = i; + return targets; +} + +/* +static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { + GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); + for(int i=0;iobj->ctx, v->var); +#if GTK_CHECK_VERSION(4, 10, 0) + free(v->columns); +#endif + free(v->selection.rows); + free(v); +} + +void ui_combobox_destroy(GtkWidget *w, UiListView *v) { + ui_destroy_boundvar(v->obj->ctx, v->var); + free(v); +} + + +/* ------------------------------ Source List ------------------------------ */ + +static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { + cxListFree(v->sublists); + free(v); +} + +static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { + free(sublist->header); + ui_destroy_boundvar(obj->ctx, sublist->var); + cxListFree(sublist->widgets); +} + +static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { + // first rows in sublists have the ui_listbox property + UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); + if(!listbox) { + return; + } + + UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); + if(!sublist) { + return; + } + + if(sublist->separator) { + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header(row, separator); + } else if(sublist->header) { + GtkWidget *header = gtk_label_new(sublist->header); + gtk_widget_set_halign(header, GTK_ALIGN_START); + if(row == listbox->first_row) { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); + } else { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); + } + gtk_list_box_row_set_header(row, header); + } +} + +#ifdef UI_GTK3 +typedef struct _UiSidebarListBoxClass { + GtkListBoxClass parent_class; +} UiSidebarListBoxClass; + +typedef struct _UiSidebarListBox { + GtkListBox parent_instance; +} UiSidebarListBox; + +G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX) + +/* Initialize the instance */ +static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + gtk_widget_class_set_css_name (widget_class, "placessidebar"); +} + +static void ui_sidebar_list_box_init(UiSidebarListBox *self) { + +} +#endif + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { + UiObject* current = uic_current_obj(obj); + +#ifdef UI_GTK3 + GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); +#else + GtkWidget *listbox = gtk_list_box_new(); +#endif + if(!args.style_class) { +#if GTK_MAJOR_VERSION >= 4 + WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); +#else + WIDGET_ADD_CSS_CLASS(listbox, "sidebar"); +#endif + } + gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); + + ui_set_name_and_style(listbox, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, listbox, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, TRUE); + + UiListBox *uilistbox = malloc(sizeof(UiListBox)); + uilistbox->obj = obj; + uilistbox->listbox = GTK_LIST_BOX(listbox); + uilistbox->getvalue = args.getvalue; + uilistbox->onactivate = args.onactivate; + uilistbox->onactivatedata = args.onactivatedata; + uilistbox->onbuttonclick = args.onbuttonclick; + uilistbox->onbuttonclickdata = args.onbuttonclickdata; + uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); + uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; + uilistbox->sublists->collection.destructor_data = obj; + uilistbox->first_row = NULL; + + if(args.numsublists == 0 && args.sublists) { + args.numsublists = INT_MAX; + } + for(int i=0;ictx, + current->ctx, + sublist.value, + sublist.varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist.header ? strdup(sublist.header) : NULL; + uisublist.separator = sublist.separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist.userdata; + uisublist.index = i; + + cxListAdd(uilistbox->sublists, &uisublist); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); + UiList *list = uisublist.var->value; + if(list) { + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } + } + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + + // register uilistbox for both widgets, so it doesn't matter which + // widget is used later + g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); + g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); + + // signals + g_signal_connect( + listbox, + "destroy", + G_CALLBACK(ui_destroy_sourcelist), + uilistbox); + + if(args.onactivate) { + g_signal_connect( + listbox, + "row-activated", + G_CALLBACK(ui_listbox_row_activate), + NULL); + } + + return scroll_area; +} + +void ui_listbox_update(UiListBox *listbox, int from, int to) { + CxIterator i = cxListIterator(listbox->sublists); + size_t pos = 0; + cx_foreach(UiListBoxSubList *, sublist, i) { + if(i.index < from) { + pos += sublist->numitems; + continue; + } + if(i.index > to) { + break; + } + + // reload sublist + ui_listbox_update_sublist(listbox, sublist, pos); + pos += sublist->numitems; + } +} + +static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + if(item->icon) { + GtkWidget *icon = ICON_IMAGE(item->icon); + BOX_ADD(hbox, icon); + } + GtkWidget *label = gtk_label_new(item->label); + gtk_widget_set_halign(label, GTK_ALIGN_START); + BOX_ADD_EXPAND(hbox, label); + // TODO: badge, button + GtkWidget *row = gtk_list_box_row_new(); + LISTBOX_ROW_SET_CHILD(row, hbox); + + // signals + UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); + memset(event, 0, sizeof(UiEventDataExt)); + event->obj = listbox->obj; + event->customdata0 = sublist; + event->customdata1 = sublist->var; + event->customdata2 = item->eventdata; + event->callback = listbox->onactivate; + event->userdata = listbox->onactivatedata; + event->callback2 = listbox->onbuttonclick; + event->userdata2 = listbox->onbuttonclickdata; + event->value0 = index; + + g_signal_connect( + row, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); + + return row; +} + +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { + // clear sublist + CxIterator r = cxListIterator(sublist->widgets); + cx_foreach(GtkWidget*, widget, r) { + LISTBOX_REMOVE(listbox->listbox, widget); + } + cxListClear(sublist->widgets); + + sublist->numitems = 0; + + // create items for each UiList element + UiList *list = sublist->var->value; + if(!list) { + return; + } + + size_t index = 0; + void *elm = list->first(list); + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + listbox->getvalue(sublist->userdata, elm, index, &item); + + // create listbox item + GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); + if(index == 0) { + // first row in the sublist, set ui_listbox data to the row + // which is then used by the headerfunc + g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + + if(listbox_insert_index == 0) { + // first row in the GtkListBox + listbox->first_row = GTK_LIST_BOX_ROW(row); + } + } + intptr_t rowindex = listbox_insert_index + index; + g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + cxListAdd(sublist->widgets, row); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); + + // next row + elm = list->next(list); + index++; + } + + sublist->numitems = cxListSize(sublist->widgets); +} + +void ui_listbox_list_update(UiList *list, int i) { + UiListBoxSubList *sublist = list->obj; +} + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { + UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); + if(!data) { + return; + } + UiListBoxSubList *sublist = data->customdata0; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = data->customdata2; + event.intval = data->value0; + + if(data->callback) { + data->callback(&event, data->userdata); + } +} diff --git a/ui/gtk/list.h b/ui/gtk/list.h new file mode 100644 index 0000000..d9ba1b2 --- /dev/null +++ b/ui/gtk/list.h @@ -0,0 +1,175 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiColData UiColData; + +typedef struct UiListView { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; +#if GTK_CHECK_VERSION(4, 10, 0) + GListStore *liststore; + GtkSelectionModel *selectionmodel; + UiColData *columns; +#endif + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropdata; + UiListSelection selection; + +} UiListView; + +struct UiColData { + UiListView *listview; + int model_column; + int data_column; +}; + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *activatedata; + void *selectiondata; +} UiTreeEventData; + +typedef struct UiListBox UiListBox; + +typedef struct UiListBoxSubList { + UiVar *var; + size_t numitems; + char *header; + UiBool separator; + CxList *widgets; + UiListBox *listbox; + void *userdata; + size_t index; +} UiListBoxSubList; + +struct UiListBox { + UiObject *obj; + GtkListBox *listbox; + CxList *sublists; // contains UiListBoxSubList elements + ui_sublist_getvalue_func getvalue; + ui_callback onactivate; + void *onactivatedata; + ui_callback onbuttonclick; + void *onbuttonclickdata; + + GtkListBoxRow *first_row; +}; + + +#if GTK_CHECK_VERSION(4, 10, 0) + +void ui_update_liststore(GListStore *liststore, UiList *list); + +void ui_listview_update2(UiList *list, int i); +UiListSelection ui_listview_getselection2(UiList *list); +void ui_listview_setselection2(UiList *list, UiListSelection selection); + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata); +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data); + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata); + +#endif + +void* ui_strmodel_getvalue(void *elm, int column); + +UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); + +GtkWidget* ui_get_tree_widget(UIWIDGET widget); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); +void ui_listview_setselection(UiList *list, UiListSelection selection); + +void ui_combobox_destroy(GtkWidget *w, UiListView *v); +void ui_listview_destroy(GtkWidget *w, UiListView *v); + +#if GTK_CHECK_VERSION(4, 10, 0) + +#else +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); +#endif + +void ui_listview_add_dnd(UiListView *listview, UiListArgs *args); +void ui_listview_enable_drop(UiListView *listview, UiListArgs *args); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_modelupdate(UiList *list, int i); +UiListSelection ui_combobox_getselection(UiList *list); +void ui_combobox_setselection(UiList *list, UiListSelection selection); + +void ui_listbox_update(UiListBox *listbox, int from, int to); +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); +void ui_listbox_list_update(UiList *list, int i); + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c index f7be2f5..085c4db 100644 --- a/ui/gtk/menu.c +++ b/ui/gtk/menu.c @@ -34,189 +34,58 @@ #include "menu.h" #include "toolkit.h" #include "../common/context.h" +#include "../common/menu.h" +#include "../common/types.h" #include "../ui/properties.h" #include "../ui/window.h" #include "container.h" -static UcxList *menus; -static UcxList *current; +#include +#include -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); -} +#if GTK_MAJOR_VERSION <= 3 -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); -} +static ui_menu_add_f createMenuItem[] = { + /* UI_MENU */ add_menu_widget, + /* UI_MENU_ITEM */ add_menuitem_widget, + /* UI_MENU_CHECK_ITEM */ add_checkitem_widget, + /* UI_MENU_RADIO_ITEM */ add_radioitem_widget, + /* UI_MENU_ITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_CHECKITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_RADIOITEM_LIST */ add_menuitem_list_widget, + /* UI_MENU_SEPARATOR */ add_menuseparator_widget +}; // private menu functions GtkWidget *ui_create_menubar(UiObject *obj) { - if(menus == NULL) { + UiMenu *menus_begin = uic_get_menu_list(); + if(menus_begin == NULL) { return NULL; } GtkWidget *mb = gtk_menu_bar_new(); - UcxList *ls = menus; + UiMenu *ls = menus_begin; while(ls) { - UiMenu *menu = ls->data; - menu->item.add_to(mb, 0, &menu->item, obj); + UiMenu *menu = ls; + add_menu_widget(mb, 0, &menu->item, obj); - ls = ls->next; + ls = (UiMenu*)ls->item.next; } return mb; } +void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj) { + UiMenuItemI *it = menu->items_begin; + int index = 0; + while(it) { + createMenuItem[it->type](parent, index, it, obj); + it = it->next; + index++; + } +} + void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenu *menu = (UiMenu*)item; @@ -224,15 +93,8 @@ void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) 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++; - } + ui_add_menu_items(menu_widget, i, menu, obj); + gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item); } @@ -249,6 +111,7 @@ void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObje event->userdata = i->userdata; event->callback = i->callback; event->value = 0; + event->customdata = NULL; g_signal_connect( widget, @@ -265,10 +128,14 @@ void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObje 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); + CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); + cxListAddArray(groups, i->groups, i->ngroups); + uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); + cxListFree(groups); } } +/* void add_menuitem_st_widget( GtkWidget *parent, int index, @@ -304,6 +171,7 @@ void add_menuitem_st_widget( uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups); } } +*/ void add_menuseparator_widget( GtkWidget *parent, @@ -317,7 +185,7 @@ void add_menuseparator_widget( } void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { - UiCheckItem *ci = (UiCheckItem*)item; + UiMenuCheckItem *ci = (UiMenuCheckItem*)item; GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); gtk_menu_shell_append(GTK_MENU_SHELL(p), widget); @@ -327,7 +195,8 @@ void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject * event->userdata = ci->userdata; event->callback = ci->callback; event->value = 0; - + event->customdata = NULL; + g_signal_connect( widget, "toggled", @@ -341,6 +210,11 @@ void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject * } } +void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { + // TODO +} + +/* void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { UiCheckItemNV *ci = (UiCheckItemNV*)item; GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); @@ -357,27 +231,31 @@ void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject // TODO: error } } +*/ void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { UiMenuItemList *il = (UiMenuItemList*)item; - UcxMempool *mp = obj->ctx->mempool; + const CxAllocator *a = obj->ctx->allocator; - UiActiveMenuItemList *ls = ucx_mempool_malloc( - mp, + UiActiveMenuItemList *ls = cxMalloc( + a, sizeof(UiActiveMenuItemList)); ls->object = obj; ls->menu = GTK_MENU_SHELL(p); ls->index = index; ls->oldcount = 0; - ls->list = il->list; + ls->getvalue = il->getvalue; + + UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + ls->list = var->value; + ls->callback = il->callback; ls->userdata = il->userdata; - ls->list->observers = ui_add_observer( - ls->list->observers, - (ui_callback)ui_update_menuitem_list, - ls); + UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls); + ls->list->observers = ui_obsvlist_add(ls->list->observers, observer); + uic_list_register_observer_destructor(obj->ctx, ls->list, observer); ui_update_menuitem_list(NULL, ls); } @@ -398,25 +276,29 @@ void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) { } } - char *str = ui_list_first(list->list); - if(str) { + void* elm = ui_list_first(list->list); + if(elm) { GtkWidget *widget = gtk_separator_menu_item_new(); gtk_menu_shell_insert(list->menu, widget, list->index); gtk_widget_show(widget); } + + ui_getvaluefunc getvalue = list->getvalue; int i = 1; - while(str) { - GtkWidget *widget = gtk_menu_item_new_with_label(str); + while(elm) { + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + + GtkWidget *widget = gtk_menu_item_new_with_label(label); gtk_menu_shell_insert(list->menu, widget, list->index + i); gtk_widget_show(widget); if(list->callback) { - // TODO: use mempool UiEventData *event = malloc(sizeof(UiEventData)); event->obj = list->object; event->userdata = list->userdata; event->callback = list->callback; event->value = i - 1; + event->customdata = elm; g_signal_connect( widget, @@ -430,7 +312,7 @@ void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) { event); } - str = ui_list_next(list->list); + elm = ui_list_next(list->list); i++; } @@ -442,7 +324,7 @@ void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) { evt.obj = event->obj; evt.window = event->obj->window; evt.document = event->obj->ctx->document; - evt.eventdata = NULL; + evt.eventdata = event->customdata; evt.intval = event->value; event->callback(&evt, event->userdata); } @@ -473,34 +355,28 @@ void ui_checkitem_set(UiInteger *i, int64_t value) { * widget menu functions */ +UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) { + GtkWidget *menu_widget = gtk_menu_new(); + ui_add_menu_items(menu_widget, 0, builder->menus_begin, obj); + return GTK_MENU(menu_widget); +} + static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) { if(event->type == GDK_BUTTON_PRESS) { GdkEventButton *e = (GdkEventButton*)event; if(e->button == 3) { gtk_widget_show_all(GTK_WIDGET(menu)); - ui_contextmenu_popup(menu); - return TRUE; + ui_contextmenu_popup(menu, widget, 0, 0); } } 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()); +void ui_widget_set_contextmenu(GtkWidget *widget, GtkMenu *menu) { g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu); - - ct->menu = menu; - return menu; } -void ui_contextmenu_popup(UIMENU menu) { +void ui_contextmenu_popup(UIMENU menu, GtkWidget *widget, int x, int y) { #if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16 gtk_menu_popup_at_pointer(menu, NULL); #else @@ -508,102 +384,273 @@ void ui_contextmenu_popup(UIMENU menu) { #endif } -void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) { - ui_widget_menuitem_gr(obj, label, f, userdata, -1); -} +#endif /* GTK_MAJOR_VERSION <= 3 */ -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; + + +#if GTK_MAJOR_VERSION >= 4 + +GtkWidget *ui_create_menubar(UiObject *obj) { + UiMenu *menus_begin = uic_get_menu_list(); + if(menus_begin == NULL) { + return NULL; } - // 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); + GMenu *menu = g_menu_new(); + UiMenu *ls = menus_begin; + while(ls) { + GMenu *sub_menu = g_menu_new(); + ui_gmenu_add_menu_items(sub_menu, 0, ls, obj); + g_menu_append_submenu(menu, ls->label, G_MENU_MODEL(sub_menu)); + ls = (UiMenu*)ls->item.next; + } + + + // Create a menubar from the menu model + return gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(menu)); +} + +static ui_gmenu_add_f createMenuItem[] = { + /* UI_MENU */ ui_gmenu_add_menu, + /* UI_MENU_ITEM */ ui_gmenu_add_menuitem, + /* UI_MENU_CHECK_ITEM */ ui_gmenu_add_checkitem, + /* UI_MENU_RADIO_ITEM */ ui_gmenu_add_radioitem, + /* UI_MENU_ITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_CHECKITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_RADIOITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_SEPARATOR */ ui_gmenu_add_menuseparator +}; + +void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) { + UiMenuItemI *it = menu->items_begin; + int index = 0; + int index_section = 0; + GMenu *section = NULL; + while(it) { + if(it->type == UI_MENU_SEPARATOR) { + section = g_menu_new(); + g_menu_append_section(parent, NULL, G_MENU_MODEL(section)); + index++; + index_section = 0; + } else { + if(section) { + createMenuItem[it->type](section, index_section++, it, obj); + } else { + createMenuItem[it->type](parent, index++, it, obj); + } + } + it = it->next; } - va_end(ap); +} + +void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenu *mi = (UiMenu*)item; + GMenu *menu = g_menu_new(); + ui_gmenu_add_menu_items(menu, 0, mi, obj); + g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu)); +} + +static void action_enable(GSimpleAction *action, int enabled) { + g_simple_action_set_enabled(action, enabled); +} + +void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *i = (UiMenuItem*)item; + + GSimpleAction *action = g_simple_action_new(item->id, NULL); + g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); - // create menuitem - GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label); - gtk_widget_show(widget); + if(i->groups) { + CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); + cxListAddArray(groups, i->groups, i->ngroups); + uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); + cxListFree(groups); + } - if(f) { + if(i->callback != NULL) { UiEventData *event = malloc(sizeof(UiEventData)); event->obj = obj; - event->userdata = userdata; - event->callback = f; + event->userdata = i->userdata; + event->callback = i->callback; event->value = 0; + event->customdata = NULL; g_signal_connect( - widget, + action, "activate", - G_CALLBACK(ui_menu_event_wrapper), + G_CALLBACK(ui_activate_event_wrapper), event); g_signal_connect( - widget, + obj->widget, "destroy", G_CALLBACK(ui_destroy_userdata), event); } - gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget); + char action_name[32]; + snprintf(action_name, 32, "win.%s", item->id); + g_menu_append(parent, i->label, action_name); +} + +void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { - 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_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item; + + // TODO +} + +void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { + } -void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) { - UiContainer *ct = uic_get_current_container(obj); - if(!ct->menu) { - return; - } +void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuItemList *il = (UiMenuItemList*)item; - // 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); + const CxAllocator *a = obj->ctx->allocator; - // create menuitem - GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group); - gtk_widget_show(widget); + UiActiveGMenuItemList *ls = cxMalloc( + a, + sizeof(UiActiveGMenuItemList)); - if(f) { - UiEventData *event = malloc(sizeof(UiEventData)); - event->obj = obj; - event->userdata = userdata; - event->callback = f; - event->value = 0; + ls->object = obj; + ls->menu = p; + ls->index = index; + ls->oldcount = 0; + ls->getvalue = il->getvalue; + + UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + ls->var = var; + UiList *list = var->value; + + ls->callback = il->callback; + ls->userdata = il->userdata; + + UiObserver *observer = ui_observer_new((ui_callback)ui_update_gmenu_item_list, ls); + list->observers = ui_obsvlist_add(list->observers, observer); + uic_list_register_observer_destructor(obj->ctx, list, observer); + + GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i")); + g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); + snprintf(ls->action, 32, "win.%s", item->id); + + + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = il->userdata; + event->callback = il->callback; + event->customdata = var; + event->value = 0; + + g_signal_connect( + action, + "activate", + G_CALLBACK(ui_menu_list_item_activate_event_wrapper), + event); + g_signal_connect( + obj->widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + ui_update_gmenu_item_list(NULL, ls); +} - g_signal_connect( - widget, - "activate", - G_CALLBACK(ui_menu_event_wrapper), - event); - g_signal_connect( - widget, - "destroy", - G_CALLBACK(ui_destroy_userdata), - event); +void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) { + int intval = event->value; + if(parameter && g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) { + intval = g_variant_get_int32(parameter); } - gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget); + UiEvent evt; + evt.obj = event->obj; + evt.window = event->obj->window; + evt.document = event->obj->ctx->document; + evt.eventdata = event->customdata; + evt.intval = intval; + event->callback(&evt, event->userdata); +} + +void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) { + int index = g_variant_get_int32(parameter); + UiVar *var = event->customdata; + UiList *list = var->value; + + UiEvent evt; + evt.obj = event->obj; + evt.window = event->obj->window; + evt.document = event->obj->ctx->document; + evt.eventdata = ui_list_get(list, index); + evt.intval = index; + event->callback(&evt, event->userdata); - if(groups) { - uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); +} + +void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) { + // remove old items + for(int i=0;ioldcount;i++) { + g_menu_remove(list->menu, list->index); + } + UiList *ls = list->var->value; + + // add list items + ui_getvaluefunc getvalue = list->getvalue; + int i = 0; + void* elm = ui_list_first(ls); + while(elm) { + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + + GMenuItem *item = g_menu_item_new(label, NULL); + GVariant *v = g_variant_new("i", i); + g_menu_item_set_action_and_target_value(item, list->action, v); + g_menu_insert_item(list->menu, list->index+i, item); + + elm = ui_list_next(ls); + i++; } + + list->oldcount = i; +} + + +/* --------------------- context menu / menubuilder --------------------- */ + +static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) { + gtk_widget_unparent(GTK_WIDGET(menu)); } + +UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) { + GMenu *menu = g_menu_new(); + ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj); + GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); + gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE); + gtk_widget_set_halign(contextmenu, GTK_ALIGN_START); + gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(remove_popover), + contextmenu); + return GTK_POPOVER_MENU(contextmenu); +} + +static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { + gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 }); + gtk_popover_popup(GTK_POPOVER(user_data)); +} + +void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) { + GtkGesture *gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3); + gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture)); + g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu); +} + +void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) { + gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 }); + gtk_popover_popup(GTK_POPOVER(menu)); +} + +#endif diff --git a/ui/gtk/menu.h b/ui/gtk/menu.h index c42b955..b8bff03 100644 --- a/ui/gtk/menu.h +++ b/ui/gtk/menu.h @@ -30,89 +30,45 @@ #define MENU_H #include "../ui/menu.h" -#include +#include "../common/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*); +UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj); +void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu); -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; -}; +GtkWidget *ui_create_menubar(UiObject *obj); -struct UiCheckItem { - UiMenuItemI item; - char *label; - ui_callback callback; - void *userdata; -}; +#if GTK_MAJOR_VERSION <= 3 -struct UiCheckItemNV { - UiMenuItemI item; - char *label; - char *varname; -}; +typedef struct UiActiveMenuItemList UiActiveMenuItemList; -struct UiMenuItemList { - UiMenuItemI item; - ui_callback callback; - void *userdata; - UiList *list; -}; +typedef void(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*); struct UiActiveMenuItemList { - UiObject *object; - GtkMenuShell *menu; - int index; - int oldcount; - UiList *list; - ui_callback callback; - void *userdata; + UiObject *object; + GtkMenuShell *menu; + int index; + int oldcount; + UiList *list; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; }; -GtkWidget *ui_create_menubar(UiObject *obj); +void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj); void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj); void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj); void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); +void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj); void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj); @@ -122,6 +78,41 @@ void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event); int64_t ui_checkitem_get(UiInteger *i); void ui_checkitem_set(UiInteger *i, int64_t value); +#endif /* GTK_MAJOR_VERSION <= 3 */ + +#if GTK_MAJOR_VERSION >= 4 + +typedef void(*ui_gmenu_add_f)(GMenu *, int, UiMenuItemI*, UiObject*); + +typedef struct UiActiveGMenuItemList UiActiveGMenuItemList; +struct UiActiveGMenuItemList { + UiObject *object; + GMenu *menu; + char action[32]; + int index; + int oldcount; + UiVar *var; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj); + +void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj); +void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj); +void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj); +void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj); +void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj); +void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj); + +void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event); +void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event); +void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list); + +#endif + + #ifdef __cplusplus } #endif diff --git a/ui/gtk/objs.mk b/ui/gtk/objs.mk index 3043aad..865216e 100644 --- a/ui/gtk/objs.mk +++ b/ui/gtk/objs.mk @@ -38,13 +38,14 @@ GTKOBJ += toolbar.o GTKOBJ += button.o GTKOBJ += display.o GTKOBJ += text.o -GTKOBJ += model.o -GTKOBJ += tree.o +GTKOBJ += list.o GTKOBJ += image.o +GTKOBJ += icon.o GTKOBJ += graphics.o GTKOBJ += range.o GTKOBJ += entry.o GTKOBJ += dnd.o +GTKOBJ += headerbar.o TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%) TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c) diff --git a/ui/gtk/range.c b/ui/gtk/range.c index c191b0a..c992159 100644 --- a/ui/gtk/range.c +++ b/ui/gtk/range.c @@ -31,13 +31,12 @@ #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 +#if GTK_MAJOR_VERSION >= 3 GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL); #else GtkWidget *scrollbar; @@ -62,6 +61,7 @@ static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange * event->userdata = userdata; event->callback = f; event->value = 0; + event->customdata = NULL; g_signal_connect( G_OBJECT(scrollbar), @@ -124,7 +124,7 @@ void ui_scrollbar_setextent(UiRange *range, double extent) { #else gtk_adjustment_set_page_size(a, extent); #endif -#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18) +#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318 gtk_adjustment_changed(a); #endif range->extent = extent; diff --git a/ui/gtk/text.c b/ui/gtk/text.c index 2b86ec2..39f4a6b 100644 --- a/ui/gtk/text.c +++ b/ui/gtk/text.c @@ -33,6 +33,12 @@ #include "text.h" #include "container.h" +#include + +#include + + +#include "../common/types.h" static void selection_handler( GtkTextBuffer *buf, @@ -56,8 +62,14 @@ static void selection_handler( } } -UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) { + UiObject* current = uic_current_obj(obj); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT); + GtkWidget *text_area = gtk_text_view_new(); + ui_set_name_and_style(text_area, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, text_area, args.groups); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); g_signal_connect( text_area, @@ -66,9 +78,12 @@ UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { NULL); UiTextArea *uitext = malloc(sizeof(UiTextArea)); + uitext->obj = obj; uitext->ctx = obj->ctx; uitext->var = var; uitext->last_selection_state = 0; + uitext->onchange = args.onchange; + uitext->onchangedata = args.onchangedata; g_signal_connect( text_area, @@ -76,29 +91,29 @@ UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { G_CALLBACK(ui_textarea_destroy), uitext); - GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL); + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 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); + SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); // font and padding - PangoFontDescription *font; - font = pango_font_description_from_string("Monospace"); - gtk_widget_modify_font(text_area, font); - pango_font_description_free(font); + //PangoFontDescription *font; + //font = pango_font_description_from_string("Monospace"); + //gtk_widget_modify_font(text_area, font); // TODO + //pango_font_description_free(font); gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2); gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2); // add - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, scroll_area, TRUE); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, TRUE); // bind value - UiText *value = var->value; - if(value) { + if(var) { + UiText *value = var->value; GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area)); if(value->value.ptr) { @@ -150,31 +165,14 @@ UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { } 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 + if(textarea->var) { + ui_destroy_boundvar(textarea->ctx, textarea->var); } - return NULL; + free(textarea); } UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) { - return gtk_bin_get_child(GTK_BIN(textarea)); + return SCROLLEDWINDOW_GET_CHILD(textarea); } char* ui_textarea_get(UiText *text) { @@ -191,7 +189,7 @@ char* ui_textarea_get(UiText *text) { return str; } -void ui_textarea_set(UiText *text, char *str) { +void ui_textarea_set(UiText *text, const char *str) { gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1); if(text->value.ptr) { text->value.free(text->value.ptr); @@ -273,13 +271,19 @@ void ui_textarea_realize_event(GtkWidget *widget, gpointer data) { void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { UiText *value = textarea->var->value; + + UiEvent e; + e.obj = textarea->obj; + e.window = e.obj->window; + e.document = textarea->ctx->document; + e.eventdata = value; + e.intval = 0; + + if(textarea->onchange) { + textarea->onchange(&e, textarea->onchangedata); + } + if(value->observers) { - 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); } } @@ -304,19 +308,19 @@ void ui_textbuf_insert( } if(mgr->cur) { - UcxList *elm = mgr->cur->next; + UiTextBufOp *elm = mgr->cur->next; if(elm) { mgr->cur->next = NULL; + mgr->end = mgr->cur; while(elm) { elm->prev = NULL; - UcxList *next = elm->next; - ui_free_textbuf_op(elm->data); - free(elm); + UiTextBufOp *next = elm->next; + ui_free_textbuf_op(elm); elm = next; } } - UiTextBufOp *last_op = mgr->cur->data; + UiTextBufOp *last_op = mgr->cur; if( last_op->type == UI_TEXTBUF_INSERT && ui_check_insertstr(last_op->text, last_op->len, text, length) == 0) @@ -341,15 +345,22 @@ void ui_textbuf_insert( dpstr[length] = 0; UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); + op->prev = NULL; + op->next = NULL; op->type = UI_TEXTBUF_INSERT; op->start = gtk_text_iter_get_offset(location); op->end = op->start+length; op->len = length; op->text = dpstr; - UcxList *elm = ucx_list_append(NULL, op); - mgr->cur = elm; - mgr->begin = ucx_list_concat(mgr->begin, elm); + cx_linked_list_add( + (void**)&mgr->begin, + (void**)&mgr->end, + offsetof(UiTextBufOp, prev), + offsetof(UiTextBufOp, next), + op); + + mgr->cur = op; } void ui_textbuf_delete( @@ -369,14 +380,14 @@ void ui_textbuf_delete( } if(mgr->cur) { - UcxList *elm = mgr->cur->next; + UiTextBufOp *elm = mgr->cur->next; if(elm) { mgr->cur->next = NULL; + mgr->end = mgr->cur; while(elm) { elm->prev = NULL; - UcxList *next = elm->next; - ui_free_textbuf_op(elm->data); - free(elm); + UiTextBufOp *next = elm->next; + ui_free_textbuf_op(elm); elm = next; } } @@ -385,6 +396,8 @@ void ui_textbuf_delete( char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE); UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); + op->prev = NULL; + op->next = NULL; op->type = UI_TEXTBUF_DELETE; op->start = gtk_text_iter_get_offset(start); op->end = gtk_text_iter_get_offset(end); @@ -395,20 +408,39 @@ void ui_textbuf_delete( 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); + cx_linked_list_add( + (void**)&mgr->begin, + (void**)&mgr->end, + offsetof(UiTextBufOp, prev), + offsetof(UiTextBufOp, next), + op); + + mgr->cur = op; } UiUndoMgr* ui_create_undomgr() { UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr)); mgr->begin = NULL; + mgr->end = NULL; mgr->cur = NULL; mgr->length = 0; mgr->event = 1; return mgr; } +void ui_destroy_undomgr(UiUndoMgr *mgr) { + UiTextBufOp *op = mgr->begin; + while(op) { + UiTextBufOp *nextOp = op->next; + if(op->text) { + free(op->text); + } + free(op); + op = nextOp; + } + free(mgr); +} + void ui_free_textbuf_op(UiTextBufOp *op) { if(op->text) { free(op->text); @@ -440,7 +472,7 @@ void ui_text_undo(UiText *value) { UiUndoMgr *mgr = value->undomgr; if(mgr->cur) { - UiTextBufOp *op = mgr->cur->data; + UiTextBufOp *op = mgr->cur; mgr->event = 0; switch(op->type) { case UI_TEXTBUF_INSERT: { @@ -468,7 +500,7 @@ void ui_text_undo(UiText *value) { void ui_text_redo(UiText *value) { UiUndoMgr *mgr = value->undomgr; - UcxList *elm = NULL; + UiTextBufOp *elm = NULL; if(mgr->cur) { if(mgr->cur->next) { elm = mgr->cur->next; @@ -478,7 +510,7 @@ void ui_text_redo(UiText *value) { } if(elm) { - UiTextBufOp *op = elm->data; + UiTextBufOp *op = elm; mgr->event = 0; switch(op->type) { case UI_TEXTBUF_INSERT: { @@ -504,12 +536,23 @@ void ui_text_redo(UiText *value) { } -static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) { + + +static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) { GtkWidget *textfield = gtk_entry_new(); + ui_set_name_and_style(textfield, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, textfield, args.groups); + + UiObject* current = uic_current_obj(obj); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); UiTextField *uitext = malloc(sizeof(UiTextField)); - uitext->ctx = obj->ctx; + uitext->obj = obj; uitext->var = var; + uitext->onchange = args.onchange; + uitext->onchangedata = args.onchangedata; + uitext->onactivate = args.onactivate; + uitext->onactivatedata = args.onactivatedata; g_signal_connect( textfield, @@ -517,8 +560,11 @@ static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, G_CALLBACK(ui_textfield_destroy), uitext); - if(width > 0) { - gtk_entry_set_width_chars(GTK_ENTRY(textfield), width); + if(args.width > 0) { + // TODO: gtk4 +#if GTK_MAJOR_VERSION <= 3 + gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width); +#endif } if(frameless) { // TODO: gtk2legacy workaroud @@ -528,13 +574,13 @@ static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, textfield, FALSE); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, textfield, FALSE); if(var) { UiString *value = var->value; if(value->value.ptr) { - gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr); + ENTRY_SET_TEXT(textfield, value->value.ptr); value->value.free(value->value.ptr); value->value.ptr = NULL; value->value.free = NULL; @@ -545,7 +591,9 @@ static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, value->value.ptr = NULL; value->value.free = NULL; value->obj = GTK_ENTRY(textfield); - + } + + if(args.onchange || var) { g_signal_connect( textfield, "changed", @@ -553,102 +601,541 @@ static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, uitext); } + if(args.onactivate) { + g_signal_connect( + textfield, + "activate", + G_CALLBACK(ui_textfield_activate), + 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; +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) { + return create_textfield(obj, FALSE, FALSE, args); } -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); +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) { + return create_textfield(obj, TRUE, FALSE, args); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) { + return create_textfield(obj, FALSE, TRUE, args); } + void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { - 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->obj; + e.window = e.obj->window; + e.document = textfield->obj->ctx->document; + e.eventdata = value; + e.intval = 0; + + if(textfield->onchange) { + textfield->onchange(&e, textfield->onchangedata); + } + + if(textfield->var) { + ui_notify_evt(value->observers, &e); + } +} + +void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) { + if(textfield->onactivate) { UiEvent e; - e.obj = textfield->ctx->obj; + e.obj = textfield->obj; e.window = e.obj->window; - e.document = textfield->ctx->document; - e.eventdata = value; + e.document = textfield->obj->ctx->document; + e.eventdata = NULL; e.intval = 0; - ui_notify_evt(value->observers, &e); + textfield->onactivate(&e, textfield->onactivatedata); + } +} + +char* ui_textfield_get(UiString *str) { + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj)); + str->value.free = (ui_freefunc)g_free; + return str->value.ptr; +} + +void ui_textfield_set(UiString *str, const char *value) { + ENTRY_SET_TEXT(str->obj, value); + if(str->value.ptr) { + str->value.free(str->value.ptr); + str->value.ptr = NULL; + str->value.free = NULL; + } +} + +// ----------------------- path textfield ----------------------- + +// TODO: move to common +static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { + cxstring *pathelms; + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + + if (nelm == 0) { + *ret_nelm = 0; + return NULL; + } + + UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm)); + size_t n = nelm; + int j = 0; + for (int i = 0; i < nelm; i++) { + cxstring c = pathelms[i]; + if (c.length == 0) { + if (i == 0) { + c.length = 1; + } + else { + n--; + continue; + } + } + + cxmutstr m = cx_strdup(c); + elms[j].name = m.ptr; + elms[j].name_len = m.length; + + size_t elm_path_len = c.ptr + c.length - full_path; + cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len)); + elms[j].path = elm_path.ptr; + elms[j].path_len = elm_path.length; + + j++; + } + *ret_nelm = n; + + return elms; +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ientry); + free(pathtf); +} + +void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) { + UiPathTextField *pathtf = event->customdata1; + for(int i=0;ivalue1;i++) { + if(i <= event->value0) { + WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } else { + WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } } + + UiPathElm *elm = event->customdata0; + cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); + UiEvent evt; + evt.obj = event->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = elm->path; + evt.intval = event->value0; + event->callback(&evt, event->userdata); + free(path.ptr); } -UIWIDGET ui_textfield(UiObject *obj, UiString *value) { - return create_textfield(obj, 0, FALSE, FALSE, value); +int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) { + size_t full_path_len = strlen(full_path); + if(full_path_len == 0) { + return 1; + } + + size_t nelm = 0; + UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata); + if (!path_elm) { + return 1; + } + + free(pathtf->current_path); + pathtf->current_path = strdup(full_path); + + ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm); + free(pathtf->current_path_buttons); + pathtf->current_path_buttons = calloc(nelm, sizeof(GtkWidget*)); + pathtf->current_pathelms = path_elm; + pathtf->current_nelm = nelm; + + return ui_pathtextfield_update_widget(pathtf); } -UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) { - return create_textfield_nv(obj, 0, FALSE, FALSE, varname); +static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) { + cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len)); + GtkWidget *button = gtk_button_new_with_label(name.ptr); + pathtf->current_path_buttons[i] = button; + free(name.ptr); + + if(pathtf->onactivate) { + UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); + memset(eventdata, 0, sizeof(UiEventDataExt)); + eventdata->callback = pathtf->onactivate; + eventdata->userdata = pathtf->onactivatedata; + eventdata->obj = pathtf->obj; + eventdata->customdata0 = elm; + eventdata->customdata1 = pathtf; + eventdata->value0 = i; + eventdata->value1 = pathtf->current_nelm; + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_path_button_clicked), + eventdata); + + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_userdata), + eventdata); + } + + return button; } -UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) { - return create_textfield(obj, width, FALSE, FALSE, value); +static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) { + const gchar *text = ENTRY_GET_TEXT(pathtf->entry); + if(strlen(text) == 0) { + return; + } + + UiObject *obj = pathtf->obj; + + if(ui_pathtextfield_update(pathtf, text)) { + return; + } + + if(pathtf->onactivate) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = (char*)text; + evt.intval = -1; + pathtf->onactivate(&evt, pathtf->onactivatedata); + } } -UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) { - return create_textfield_nv(obj, width, FALSE, FALSE, varname); +#if GTK_MAJOR_VERSION >= 4 + +static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) { + if(pathtf->current_path) { + gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); + ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path); + } } -UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) { - return create_textfield(obj, 0, TRUE, FALSE, value); +static gboolean ui_path_textfield_key_controller( + GtkEventControllerKey* self, + guint keyval, + guint keycode, + GdkModifierType state, + UiPathTextField *pathtf) +{ + if(keyval == GDK_KEY_Escape) { + pathbar_show_hbox(NULL, pathtf); + } + return FALSE; } -UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) { - return create_textfield_nv(obj, 0, TRUE, FALSE, varname); +UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { + UiObject* current = uic_current_obj(obj); + + UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); + memset(pathtf, 0, sizeof(UiPathTextField)); + pathtf->obj = obj; + pathtf->getpathelm = args.getpathelm; + pathtf->getpathelmdata = args.getpathelmdata; + pathtf->onactivate = args.onactivate; + pathtf->onactivatedata = args.onactivatedata; + pathtf->ondragcomplete = args.ondragcomplete; + pathtf->ondragcompletedata = args.ondragcompletedata; + pathtf->ondragstart = args.ondragstart; + pathtf->ondragstartdata = args.ondragstartdata; + pathtf->ondrop = args.ondrop; + pathtf->ondropdata = args.ondropsdata; + + if(!pathtf->getpathelm) { + pathtf->getpathelm = default_pathelm_func; + pathtf->getpathelmdata = NULL; + } + + pathtf->stack = gtk_stack_new(); + gtk_widget_set_name(pathtf->stack, "path-textfield-box"); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, pathtf->stack, FALSE); + + pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + pathtf->entry = gtk_entry_new(); + gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry); + gtk_widget_set_hexpand(pathtf->entry, TRUE); + + GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic"); + gtk_widget_add_css_class(cancel_button, "flat"); + gtk_widget_add_css_class(cancel_button, "pathbar-extra-button"); + gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button); + g_signal_connect( + cancel_button, + "clicked", + G_CALLBACK(pathbar_show_hbox), + pathtf); + + gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box); + g_object_ref(pathtf->entry); // for compatibility with older pathbar version + g_signal_connect( + pathtf->entry, + "activate", + G_CALLBACK(ui_path_textfield_activate), + pathtf); + + GtkEventController *entry_cancel = gtk_event_controller_key_new(); + gtk_widget_add_controller(pathtf->entry, entry_cancel); + g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf); + + gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); + + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); + if (var) { + UiString* value = (UiString*)var->value; + value->obj = pathtf; + value->get = ui_path_textfield_get; + value->set = ui_path_textfield_set; + + if(value->value.ptr) { + char *str = strdup(value->value.ptr); + ui_string_set(value, str); + free(str); + } + } + + return pathtf->stack; } -UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) { - return create_textfield(obj, 0, FALSE, TRUE, value); +static void pathbar_pressed( + GtkGestureClick* self, + gint n_press, + gdouble x, + gdouble y, + UiPathTextField *pathtf) +{ + gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); + gtk_widget_grab_focus(pathtf->entry); } -UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) { - return create_textfield_nv(obj, 0, FALSE, TRUE, varname); +int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { + // recreate button hbox + if(pathtf->hbox) { + gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox); + } + pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE); + gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox); + gtk_widget_set_name(pathtf->hbox, "pathbar"); + + // add buttons for path elements + for (int i=0;icurrent_nelm;i++) { + UiPathElm *elm = &pathtf->current_pathelms[i]; + + GtkWidget *button = ui_path_elm_button(pathtf, elm, i); + gtk_widget_add_css_class(button, "flat"); + + gtk_box_append(GTK_BOX(pathtf->hbox), button); + + if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) { + GtkWidget *path_separator = gtk_label_new("/"); + gtk_widget_add_css_class(path_separator, "pathbar-button-inactive"); + gtk_box_append(GTK_BOX(pathtf->hbox), path_separator); + } + } + gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); + + // create a widget for receiving button press events + GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkGesture *handler = gtk_gesture_click_new(); + gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler)); + g_signal_connect( + handler, + "pressed", + G_CALLBACK(pathbar_pressed), + pathtf); + gtk_widget_set_hexpand(event_area, TRUE); + gtk_widget_set_vexpand(event_area, TRUE); + gtk_box_append(GTK_BOX(pathtf->hbox), event_area); + + return 0; +} + +#else + +static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) { + gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0); + gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); + pathtf->buttonbox = NULL; + + gtk_widget_show(pathtf->entry); + gtk_widget_grab_focus(pathtf->entry); + + return TRUE; } -UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) { - return create_textfield(obj, width, FALSE, TRUE, value); +static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) { + if (event->keyval == GDK_KEY_Escape) { + // reset GtkEntry value + gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path); + const gchar *text = gtk_entry_get_text(GTK_ENTRY(self)); + ui_pathtextfield_update(pathtf, text); + return TRUE; + } + return FALSE; } -UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) { - return create_textfield_nv(obj, width, FALSE, TRUE, varname); +static GtkWidget* create_path_button_box() { + GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style + gtk_box_set_homogeneous(GTK_BOX(bb), FALSE); + gtk_box_set_spacing(GTK_BOX(bb), 0); + return bb; } -char* ui_textfield_get(UiString *str) { +UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { + UiObject* current = uic_current_obj(obj); + + UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); + memset(pathtf, 0, sizeof(UiPathTextField)); + pathtf->obj = obj; + pathtf->getpathelm = args.getpathelm; + pathtf->getpathelmdata = args.getpathelmdata; + pathtf->onactivate = args.onactivate; + pathtf->onactivatedata = args.onactivatedata; + pathtf->ondragcomplete = args.ondragcomplete; + pathtf->ondragcompletedata = args.ondragcompletedata; + pathtf->ondragstart = args.ondragstart; + pathtf->ondragstartdata = args.ondragstartdata; + pathtf->ondrop = args.ondrop; + pathtf->ondropdata = args.ondropsdata; + + if(!pathtf->getpathelm) { + pathtf->getpathelm = default_pathelm_func; + pathtf->getpathelmdata = NULL; + } + + // top level container for the path textfield is a GtkEventBox + // the event box is needed to handle background button presses + GtkWidget *eventbox = gtk_event_box_new(); + g_signal_connect( + eventbox, + "button-press-event", + G_CALLBACK(path_textfield_btn_pressed), + pathtf); + g_signal_connect( + eventbox, + "destroy", + G_CALLBACK(ui_path_textfield_destroy), + pathtf); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, eventbox, FALSE); + + // hbox as parent for the GtkEntry and GtkButtonBox + GtkWidget *hbox = ui_gtk_hbox_new(0); + pathtf->hbox = hbox; + gtk_container_add(GTK_CONTAINER(eventbox), hbox); + gtk_widget_set_name(hbox, "path-textfield-box"); + + // create GtkEntry, that is also visible by default (with input yet) + pathtf->entry = gtk_entry_new(); + g_object_ref(G_OBJECT(pathtf->entry)); + gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0); + + g_signal_connect( + pathtf->entry, + "activate", + G_CALLBACK(ui_path_textfield_activate), + pathtf); + g_signal_connect( + pathtf->entry, + "key-press-event", + G_CALLBACK(ui_path_textfield_key_press), + pathtf); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); + if (var) { + UiString* value = (UiString*)var->value; + value->obj = pathtf; + value->get = ui_path_textfield_get; + value->set = ui_path_textfield_set; + + if(value->value.ptr) { + char *str = strdup(value->value.ptr); + ui_string_set(value, str); + free(str); + } + } + + return hbox; +} + +int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { + GtkWidget *buttonbox = create_path_button_box(); + + // switch from entry to buttonbox or remove current buttonbox + if(pathtf->buttonbox) { + gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); + } else { + gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry); + } + gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0); + pathtf->buttonbox = buttonbox; + + for (int i=0;icurrent_nelm;i++) { + UiPathElm *elm = &pathtf->current_pathelms[i]; + GtkWidget *button = ui_path_elm_button(pathtf, elm, i); + gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0); + } + + gtk_widget_show_all(buttonbox); + + return 0; +} + +#endif + +char* ui_path_textfield_get(UiString *str) { if(str->value.ptr) { str->value.free(str->value.ptr); } - str->value.ptr = g_strdup(gtk_entry_get_text(str->obj)); + UiPathTextField *tf = str->obj; + str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry)); str->value.free = (ui_freefunc)g_free; return str->value.ptr; } -void ui_textfield_set(UiString *str, char *value) { - gtk_entry_set_text(str->obj, value); +void ui_path_textfield_set(UiString *str, const char *value) { + UiPathTextField *tf = str->obj; + ENTRY_SET_TEXT(tf->entry, value); + ui_pathtextfield_update(tf, value); if(str->value.ptr) { str->value.free(str->value.ptr); str->value.ptr = NULL; diff --git a/ui/gtk/text.h b/ui/gtk/text.h index 0eefa2a..f7b8ad7 100644 --- a/ui/gtk/text.h +++ b/ui/gtk/text.h @@ -31,7 +31,7 @@ #include "../ui/text.h" #include "toolkit.h" -#include +#include #include "../common/context.h" #ifdef __cplusplus @@ -40,38 +40,79 @@ extern "C" { #define UI_TEXTBUF_INSERT 0 #define UI_TEXTBUF_DELETE 1 -typedef struct UiTextBufOp { + +typedef struct UiTextBufOp UiTextBufOp; +struct UiTextBufOp { + UiTextBufOp *prev; + UiTextBufOp *next; int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE int start; int end; int len; char *text; -} UiTextBufOp; +}; typedef struct UiUndoMgr { - UcxList *begin; - UcxList *cur; - int length; - int event; + UiTextBufOp *begin; + UiTextBufOp *end; + UiTextBufOp *cur; + int length; + int event; } UiUndoMgr; typedef struct UiTextArea { - UiContext *ctx; - UiVar *var; - int last_selection_state; + UiObject *obj; + UiContext *ctx; + UiVar *var; + int last_selection_state; + ui_callback onchange; + void *onchangedata; } UiTextArea; typedef struct UiTextField { - UiContext *ctx; - UiVar *var; - // TODO: validatefunc + UiObject *obj; + UiVar *var; + ui_callback onchange; + void *onchangedata; + ui_callback onactivate; + void *onactivatedata; } UiTextField; +typedef struct UiPathTextField { + UiObject *obj; + + GtkWidget *stack; + GtkWidget *hbox; + GtkWidget *entry_box; + GtkWidget *entry; +#if GTK_MAJOR_VERSION == 3 + GtkWidget *buttonbox; +#endif + + char *current_path; + UiPathElm *current_pathelms; + GtkWidget **current_path_buttons; + size_t current_nelm; + + ui_pathelm_func getpathelm; + void* getpathelmdata; + + ui_callback onactivate; + void* onactivatedata; + + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; +} UiPathTextField; + UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var); void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea); char* ui_textarea_get(UiText *text); -void ui_textarea_set(UiText *text, char *str); +void ui_textarea_set(UiText *text, const char *str); char* ui_textarea_getsubstr(UiText *text, int begin, int end); void ui_textarea_insert(UiText *text, int pos, char *str); void ui_textarea_setposition(UiText *text, int pos); @@ -95,14 +136,21 @@ void ui_textbuf_delete( GtkTextIter *end, void *data); UiUndoMgr* ui_create_undomgr(); +void ui_destroy_undomgr(UiUndoMgr *mgr); void ui_free_textbuf_op(UiTextBufOp *op); int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield); void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield); +void ui_textfield_activate(GtkEntry* self, UiTextField *textfield); char* ui_textfield_get(UiString *str); -void ui_textfield_set(UiString *str, char *value); +void ui_textfield_set(UiString *str, const char *value); + +int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path); +int ui_pathtextfield_update_widget(UiPathTextField* pathtf); +char* ui_path_textfield_get(UiString *str); +void ui_path_textfield_set(UiString *str, const char *value); #ifdef __cplusplus } diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c index 3365722..60c4bac 100644 --- a/ui/gtk/toolbar.c +++ b/ui/gtk/toolbar.c @@ -31,199 +31,20 @@ #include #include "toolbar.h" +#include "menu.h" #include "button.h" -#include "image.h" -#include "tree.h" -#include +#include "icon.h" +#include "list.h" +#include +#include +#include +#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); -} +#if UI_GTK2 || UI_GTK3 GtkWidget* ui_create_toolbar(UiObject *obj) { - if(!defaults) { - return NULL; - } - GtkWidget *toolbar = gtk_toolbar_new(); #ifdef UI_GTK3 gtk_style_context_add_class( @@ -231,68 +52,110 @@ GtkWidget* ui_create_toolbar(UiObject *obj) { GTK_STYLE_CLASS_PRIMARY_TOOLBAR); #endif + CxMap *items = uic_get_toolbar_items(); + CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + ui_toolbar_add_items(obj, toolbar, items, left_defaults); + ui_toolbar_add_items(obj, toolbar, items, center_defaults); + ui_toolbar_add_items(obj, toolbar, items, right_defaults); + + /* GtkToolbar *tb = GTK_TOOLBAR(toolbar); - UCX_FOREACH(elm, defaults) { - UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data); + CxIterator i = cxListIterator(defaults); + cx_foreach(char *, def, i) { + UiToolItemI *item = cxMapGet(toolbar_items, def); if(item) { item->add_to(tb, item, obj); - } else if(!strcmp(elm->data, "@separator")) { + } else if(!strcmp(def, "@separator")) { gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1); } else { - fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data); + fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def); } } + */ return toolbar; } -void add_toolitem_widget(GtkToolbar *tb, UiToolItem *item, UiObject *obj) { - GtkToolItem *button = gtk_tool_button_new(NULL, item->label); - gtk_tool_item_set_homogeneous(button, FALSE); - if(item->image) { - GdkPixbuf *pixbuf = ui_get_image(item->image); - GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); - gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), image); - } else { - gtk_tool_item_set_is_important(button, TRUE); - } - - if(item->callback) { - UiEventData *event = 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); +static void create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) { + GtkToolbar *tb = GTK_TOOLBAR(toolbar); + switch(i->type) { + case UI_TOOLBAR_ITEM: { + add_toolitem_widget(tb, (UiToolbarItem*)i, obj); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + add_toolitem_toggle_widget(tb, (UiToolbarToggleItem*)i, obj); + break; + } + case UI_TOOLBAR_MENU: { + add_toolitem_menu_widget(tb, (UiToolbarMenuItem*)i, obj); + break; + } + default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type); } - - 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_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults) { + // add pre-configured items + CxIterator i = cxListIterator(defaults); + cx_foreach(char*, def, i) { + UiToolbarItemI* item = uic_toolbar_get_item(def); + if (!item) { + fprintf(stderr, "unknown toolbar item: %s\n", def); + continue; + } + create_item(obj, toolbar, item); } } -void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) { - GtkToolItem *button = gtk_tool_button_new_from_stock(item->stockid); +static void set_toolbutton_icon(GtkToolItem *item, const char *icon_name) { +#if GTK_MAJOR_VERSION >= 3 + gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), icon_name); +#else + UiIcon *icon = ui_icon(icon_name, 24); + if(icon) { + GdkPixbuf *pixbuf = ui_icon_pixbuf(icon); + if(pixbuf) { + GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), image); + } + } +#endif +} + +void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) { + GtkToolItem *button; + if(item->args.stockid) { +#ifdef UI_GTK2 + button = gtk_tool_button_new_from_stock(item->args.stockid); +#else + // TODO: gtk3 stock + button = gtk_tool_button_new(NULL, item->args.label); +#endif + } else { + button = gtk_tool_button_new(NULL, item->args.label); + } + gtk_tool_item_set_homogeneous(button, FALSE); - if(item->isimportant) { - gtk_tool_item_set_is_important(button, TRUE); + if(item->args.icon) { + set_toolbutton_icon(button, item->args.icon); } + gtk_tool_item_set_is_important(button, TRUE); - if(item->callback) { - UiEventData *event = ucx_mempool_malloc( - obj->ctx->mempool, + ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + + if(item->args.onclick) { + UiEventData *event = cxMalloc( + obj->ctx->allocator, sizeof(UiEventData)); event->obj = obj; - event->userdata = item->userdata; - event->callback = item->callback; + event->callback = item->args.onclick; + event->userdata = item->args.onclickdata; + event->customdata = NULL; + event->value = 0; g_signal_connect( button, @@ -303,74 +166,72 @@ void add_toolitem_st_widget(GtkToolbar *tb, UiStToolItem *item, UiObject *obj) { 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) { +void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj) { GtkToolItem *button; - if(item->stockid) { - button = gtk_toggle_tool_button_new_from_stock(item->stockid); + if(item->args.stockid) { +#ifdef UI_GTK2 + button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); +#else + button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); // TODO: gtk3 stock +#endif } else { button = gtk_toggle_tool_button_new(); gtk_tool_item_set_homogeneous(button, FALSE); - if(item->label) { - gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->label); + if(item->args.label) { + gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.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); + if(item->args.icon) { + set_toolbutton_icon(button, item->args.icon); } } + ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); - UiVar *var; - 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); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = (UiInteger*)var->value; + if(i) { + i->get = ui_tool_toggle_button_get; + i->set = ui_tool_toggle_button_set; + i->obj = button; + + if(i->value != 0) { + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE); + } } } - // register event - // the event func will call the UiInteger observer callbacks - UiEventData *event = ucx_mempool_malloc( - obj->ctx->mempool, - sizeof(UiEventData)); + UiVarEventData *event = cxMalloc( + obj->ctx->allocator, + sizeof(UiVarEventData)); event->obj = obj; - event->userdata = var; - event->callback = NULL; + event->callback = item->args.onchange; + event->userdata = item->args.onchangedata; + event->var = var; g_signal_connect( - button, - "toggled", - G_CALLBACK(ui_tool_button_toggled), - event); + 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) { +void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event) { UiEvent e; e.obj = event->obj; e.window = event->obj->window; @@ -378,10 +239,16 @@ void ui_tool_button_toggled(GtkToggleToolButton *widget, UiEventData *event) { e.eventdata = NULL; e.intval = gtk_toggle_tool_button_get_active(widget); - UiVar *var = event->userdata; - UiInteger *i = var->value; + if(event->callback) { + event->callback(&e, event->userdata); + } + + UiVar *var = event->var; + UiInteger *i = var ? var->value : NULL; - ui_notify_evt(i->observers, &e); + if(i) { + ui_notify_evt(i->observers, &e); + } } int64_t ui_tool_toggle_button_get(UiInteger *integer) { @@ -395,6 +262,70 @@ void ui_tool_toggle_button_set(UiInteger *integer, int64_t value) { integer->value = s; } + + +typedef struct UiToolbarMenuWidget { + GtkWidget *button; + GtkMenu *menu; +} UiToolbarMenuWidget; + +static void ui_toolbar_menubutton_clicked(GtkWidget *widget, UiToolbarMenuWidget *menu) { + int x; + gtk_menu_popup_at_widget(menu->menu, menu->button, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); +} + +static void ui_toolbar_menubutton_destroy(GtkWidget *widget, UiToolbarMenuWidget *menu) { + free(menu); +} + +void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj) { + GtkToolItem *button; + if(item->args.stockid) { +#ifdef UI_GTK2 + button = gtk_tool_button_new_from_stock(item->args.stockid); +#else + // TODO: gtk3 stock + button = gtk_tool_button_new(NULL, item->args.label); +#endif + } else { + button = gtk_tool_button_new(NULL, item->args.label); + } + + gtk_tool_item_set_homogeneous(button, FALSE); + if(item->args.icon) { + set_toolbutton_icon(button, item->args.icon); + } + gtk_tool_item_set_is_important(button, TRUE); + + gtk_toolbar_insert(tb, button, -1); + + // menu + GtkWidget *menu_widget = gtk_menu_new(); + ui_add_menu_items(menu_widget, 0, &item->menu, obj); + gtk_widget_show_all(menu_widget); + + UiToolbarMenuWidget *tbmenu = malloc(sizeof(UiToolbarMenuWidget)); + tbmenu->button = GTK_WIDGET(button); + tbmenu->menu = GTK_MENU(menu_widget); + + g_signal_connect( + button, + "clicked", + G_CALLBACK(ui_toolbar_menubutton_clicked), + tbmenu); + + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_toolbar_menubutton_destroy), + tbmenu); +} + + + + +// deprecated / unsupported +/* void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) { UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1); modelinfo->getvalue = cb->getvalue; @@ -419,4 +350,79 @@ void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject * gtk_toolbar_insert(tb, item, -1); } } +*/ + + + +#ifdef UI_GTK3 + +GtkWidget* ui_create_headerbar(UiObject *obj) { + GtkWidget *headerbar = gtk_header_bar_new(); + + CxMap *items = uic_get_toolbar_items(); + CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + ui_toolbar_headerbar_add_items(obj, headerbar, items, left_defaults); + ui_toolbar_headerbar_add_items(obj, headerbar, items, center_defaults); + ui_toolbar_headerbar_add_items(obj, headerbar, items, right_defaults); + + return headerbar; +} + +static void hb_create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) { + GtkHeaderBar *tb = GTK_HEADER_BAR(toolbar); + switch(i->type) { + case UI_TOOLBAR_ITEM: { + add_headerbar_item_widget(tb, (UiToolbarItem*)i, obj); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + add_headerbar_item_toggle_widget(tb, (UiToolbarToggleItem*)i, obj); + break; + } + case UI_TOOLBAR_MENU: { + add_headerbar_item_menu_widget(tb, (UiToolbarMenuItem*)i, obj); + break; + } + default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type); + } +} + + +void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) { + // add pre-configured items + CxIterator i = cxListIterator(defaults); + cx_foreach(char*, def, i) { + UiToolbarItemI* item = uic_toolbar_get_item(def); + if (!item) { + fprintf(stderr, "unknown toolbar item: %s\n", def); + continue; + } + hb_create_item(obj, headerbar, item); + } +} + +void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj) { + GtkWidget *button = gtk_button_new_with_label(item->args.label); + if(item->args.icon) { + ui_button_set_icon_name(button, item->args.icon); + } + ui_set_widget_groups(obj->ctx, button, item->args.groups); + + gtk_header_bar_pack_start(hb, button); + +} + +void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj) { + +} + +void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj) { + +} + +#endif /* UI_GTK3 */ +#endif /* UI_GTK2 || UI_GTK3 */ diff --git a/ui/gtk/toolbar.h b/ui/gtk/toolbar.h index 68e95ac..80bd113 100644 --- a/ui/gtk/toolbar.h +++ b/ui/gtk/toolbar.h @@ -30,16 +30,18 @@ #define TOOLBAR_H #include "../ui/toolbar.h" -#include -#include +#include "../common/toolbar.h" +#include +#include -#include "model.h" -#include "tree.h" +#include "list.h" #ifdef __cplusplus extern "C" { #endif +#if UI_GTK2 || UI_GTK3 + typedef struct UiToolItemI UiToolItemI; typedef struct UiToolItem UiToolItem; typedef struct UiStToolItem UiStToolItem; @@ -61,7 +63,7 @@ struct UiToolItem { ui_callback callback; void *userdata; const char *varname; - UcxList *groups; + CxList *groups; int isimportant; }; @@ -71,7 +73,7 @@ struct UiStToolItem { ui_callback callback; void *userdata; const char *varname; - UcxList *groups; + CxList *groups; int isimportant; }; @@ -82,7 +84,7 @@ struct UiToggleToolItem { const char *stockid; UiInteger *value; const char *var; - UcxList *groups; + CxList *groups; int isimportant; }; @@ -97,12 +99,11 @@ struct UiToolbarComboBox { struct UiToolbarComboBoxNV { UiToolItemI item; char *listname; - ui_getvaluefunc getvalue; + ui_getvaluefunc getvalue; ui_callback callback; void *userdata; }; -void ui_toolbar_init(); void ui_toolitem_vstgr( char *name, @@ -114,18 +115,33 @@ void ui_toolitem_vstgr( 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 ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults); + +void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj); +void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj); +void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj); + +void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event); +int64_t ui_tool_toggle_button_get(UiInteger *integer); +void ui_tool_toggle_button_set(UiInteger *integer, int64_t value); + +GtkWidget* ui_create_headerbar(UiObject *obj); + +void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults); +void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj); +void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj); +void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj); + + +/* void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj); void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj); void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); void ui_combobox_update(UiEvent *event, void *combobox); +*/ -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); +#endif #ifdef __cplusplus } diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c index a6e0484..19a0990 100644 --- a/ui/gtk/toolkit.c +++ b/ui/gtk/toolkit.c @@ -33,20 +33,24 @@ #include "toolkit.h" #include "toolbar.h" -#include "model.h" -#include "image.h" +#include "icon.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/menu.h" +#include "../common/toolbar.h" +#include "../common/threadpool.h" -#include +#include +#include +#include #include -#ifndef UI_GTK2 -static GtkApplication *app; +#ifdef UI_APPLICATION +UI_APPLICATION app; #endif -static char *application_name; +static const char *application_name; static ui_callback startup_func; static void *startup_data; @@ -62,29 +66,32 @@ static UiObject *active_window; static int scale_factor = 1; -void ui_init(char *appname, int argc, char **argv) { +UIEXPORT void ui_init(const char *appname, int argc, char **argv) { + application_name = appname; uic_init_global_context(); +#if GTK_MAJOR_VERSION >= 4 + gtk_init(); +#else gtk_init(&argc, &argv); - application_name = appname; +#endif + ui_css_init(); uic_docmgr_init(); - ui_toolbar_init(); - - // init custom types - ui_list_init(); - + uic_menu_init(); + uic_toolbar_init(); ui_image_init(); - uic_load_app_properties(); -#ifdef UI_SUPPORTS_SCALE +#if GTK_MAJOR_VERSION >= 4 + scale_factor = 1; // TODO +#elif defined(UI_SUPPORTS_SCALE) scale_factor = gdk_monitor_get_scale_factor( gdk_display_get_primary_monitor(gdk_display_get_default())); #endif } -char* ui_appname() { +const char* ui_appname() { return application_name; } @@ -117,14 +124,11 @@ static void app_activate(GtkApplication* app, gpointer userdata) { #endif void ui_main() { -#ifndef UI_GTK2 - sstr_t appid = ucx_sprintf( +#ifdef UI_APPLICATION + cxmutstr appid = cx_asprintf( "ui.%s", application_name ? application_name : "application1"); - - app = gtk_application_new( - appid.ptr, - G_APPLICATION_FLAGS_NONE); + app = UI_APPLICATION_NEW(appid.ptr); g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); g_application_run(G_APPLICATION (app), 0, NULL); @@ -149,17 +153,32 @@ void ui_app_quit() { } GtkApplication* ui_get_application() { - return app; + return GTK_APPLICATION(app); } #endif void ui_show(UiObject *obj) { + gboolean visible = gtk_widget_is_visible(obj->widget); + uic_check_group_widgets(obj->ctx); +#if GTK_MAJOR_VERSION >= 4 + gtk_window_present(GTK_WINDOW(obj->widget)); +#elif GTK_MAJOR_VERSION <= 3 gtk_widget_show_all(obj->widget); +#endif + + if(!visible) { + obj->ref++; + } } void ui_close(UiObject *obj) { + uic_context_prepare_close(obj->ctx); +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_window_close(GTK_WINDOW(obj->widget)); +#else gtk_widget_destroy(obj->widget); +#endif } @@ -181,12 +200,31 @@ static gboolean ui_job_finished(void *data) { static void* ui_jobthread(void *data) { UiJob *job = data; int result = job->job_func(job->job_data); - if(!result) { + if(!result && job->finish_callback) { g_idle_add(ui_job_finished, job); + } else { + free(job); } return NULL; } +static gboolean ui_idle_func(void *data) { + UiJob *job = data; + job->job_func(job->job_data); + free(job); + return FALSE; +} + +void ui_call_mainthread(ui_threadfunc tf, void* td) { + UiJob *job = malloc(sizeof(UiJob)); + job->job_func = tf; + job->job_data = td; + job->finish_callback = NULL; + job->finish_data = NULL; + job->obj = NULL; + g_idle_add(ui_idle_func, job); +} + void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { UiJob *job = malloc(sizeof(UiJob)); job->obj = obj; @@ -203,24 +241,38 @@ void ui_set_enabled(UIWIDGET widget, int enabled) { } void ui_set_show_all(UIWIDGET widget, int value) { + // TODO: gtk4 +#if GTK_MAJOR_VERSION <= 3 gtk_widget_set_no_show_all(widget, !value); +#endif } void ui_set_visible(UIWIDGET widget, int visible) { + // TODO: gtk4 +#if GTK_MAJOR_VERSION <= 3 if(visible) { gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_show_all(widget); } else { gtk_widget_hide(widget); } +#endif } void ui_clipboard_set(char *str) { +#if GTK_MAJOR_VERSION >= 4 + // TODO: gtk4: needs widget +#else GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text(cb, str, strlen(str)); +#endif } char* ui_clipboard_get() { +#if GTK_MAJOR_VERSION >= 4 + // TODO: gtk4: needs widget + return NULL; +#else GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); char *str = gtk_clipboard_wait_for_text(cb); if(str) { @@ -230,6 +282,7 @@ char* ui_clipboard_get() { } else { return NULL; } +#endif } int ui_get_scalefactor() { @@ -241,15 +294,25 @@ void ui_destroy_userdata(GtkWidget *object, void *userdata) { } void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) { - ui_destroy_boundvar(data->obj->ctx, data->var); + if(data->var) { + ui_destroy_boundvar(data->obj->ctx, data->var); + } free(data); } +void ui_destroy_widget_var(GtkWidget *object, UiVar *var) { + ui_destroy_boundvar(NULL, var); +} + void ui_destroy_boundvar(UiContext *ctx, UiVar *var) { + uic_unbind_var(var); + if(var->type == UI_VAR_SPECIAL) { - free(var); + ui_free(var->from_ctx, var); } else { - uic_remove_bound_var(ctx, var); + ui_free(var->from_ctx, var); + // TODO: free or unbound + //uic_remove_bound_var(ctx, var); } } @@ -262,3 +325,160 @@ UiObject *ui_get_active_window() { } +#if GTK_MAJOR_VERSION >= 3 + +static GtkCssProvider* ui_gtk_css_provider; + +#if GTK_MAJOR_VERSION == 4 +static const char *ui_gtk_css = +"#path-textfield-box {\n" +" background-color: alpha(currentColor, 0.1);" +" border-radius: 6px;" +" padding: 0px;" +"}\n" +".pathbar-extra-button {\n" +" border-top-right-radius: 6px;" +" border-bottom-right-radius: 6px;" +" border-top-left-radius: 0px;" +" border-bottom-left-radius: 0px;" +"}\n" +"#pathbar button {\n" +" margin: 3px;" +" border-radius: 4px;" +" padding-top: 0px;" +" padding-bottom: 0px;" +" padding-left: 8px;" +" padding-right: 8px;" +"}\n" +"#path-textfield-box entry {\n" +" background-color: #00000000;" +" border-top-left-radius: 6px;" +" border-bottom-left-radius: 6px;" +" border-top-right-radius: 0px;" +" border-bottom-right-radius: 0px;" +"}\n" +".pathbar-button-inactive {\n" +" color: alpha(currentColor, 0.5);" +"}\n" +".ui_test {\n" +" background-color: red;\n" +"}\n" +".ui_label_title {\n" +" font-weight: bold;\n" +"}\n" +".ui-listbox-header {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 12px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-first {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 4px;\n" +" margin-bottom: 10px;\n" +"}\n" +; + +#elif GTK_MAJOR_VERSION == 3 +static const char *ui_gtk_css = +"#path-textfield-box {\n" +" background-color: @theme_base_color;\n" +" border-radius: 5px;\n" +" padding: 0px;\n" +"}\n" +".pathbar-button-inactive {\n" +" color: alpha(currentColor, 0.5);" +"}\n" +".ui_test {\n" +" background-color: red;\n" +"}\n" +".ui_label_title {\n" +" font-weight: bold;\n" +"}\n" +"placessidebar row {\n" +" padding-left: 10px;\n" +"}\n" +".ui-listbox-header {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 12px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-first {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 4px;\n" +" margin-bottom: 10px;\n" +"}\n" +; +#endif + +void ui_css_init(void) { + ui_gtk_css_provider = gtk_css_provider_new(); + +#ifdef UI_GTK3 + gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL); + + GdkScreen *screen = gdk_screen_get_default(); + gtk_style_context_add_provider_for_screen( + screen, + GTK_STYLE_PROVIDER(ui_gtk_css_provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); +#endif /* UI_GTK3 */ + +#ifdef UI_GTK4 + + +#if GTK_MINOR_VERSION < 12 + gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1); +#else + gtk_css_provider_load_from_string(ui_gtk_css_provider, ui_gtk_css); +#endif /* GTK_MINOR_VERSION < 12 */ + + GdkDisplay *display = gdk_display_get_default(); + gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + +#endif /* UI_GTK4 */ +} + + + +#endif + +void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style_classes) { + if(name) { + gtk_widget_set_name(widget, name); + } + if(style_classes) { + cxstring *cls = NULL; + size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls); + for(int i=0;i= 4 + gtk_widget_add_css_class(widget, m.ptr); +#elif GTK_MAJOR_VERSION >= 3 + GtkStyleContext *ctx = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(ctx, m.ptr); +#endif + free(m.ptr); + } + free(cls); + + } +} + +void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) { + if(!groups) { + return; + } + size_t ngroups = uic_group_array_size(groups); + ui_set_widget_ngroups(ctx, widget, groups, ngroups); +} + +void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) { + if(ngroups > 0) { + uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); + ui_set_enabled(widget, FALSE); + } +} diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h index 48f4d10..62e82bc 100644 --- a/ui/gtk/toolkit.h +++ b/ui/gtk/toolkit.h @@ -39,49 +39,153 @@ extern "C" { #pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 2074 +#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS +#else +#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE +#endif + +#ifdef UI_LIBADWAITA +#define UI_APPLICATION AdwApplication* +#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS) +#elif GTK_MAJOR_VERSION >= 3 +#define UI_APPLICATION GtkApplication* +#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS) +#endif + +#if GTK_MAJOR_VERSION >= 4 +#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window)) +#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window)) +#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child) +#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child) +#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child) +#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child) +#define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child) +#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text) +#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry)) +#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new() +#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child) +#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw)) +#define FRAME_SET_CHILD(frame, child) gtk_frame_set_child(GTK_FRAME(frame), child) +#define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child) +#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass) +#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon) +#define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child) +#else +#define WINDOW_SHOW(window) gtk_widget_show_all(window) +#define WINDOW_DESTROY(window) gtk_widget_destroy(window) +#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child) +#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child) +#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0) +#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0) +#define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child) +#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text) +#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry)) +#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL) +#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child) +#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw)) +#define FRAME_SET_CHILD(frame, child) gtk_container_add(GTK_CONTAINER(frame), child) +#define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child) +#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass) +#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON) +#define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child) +#endif + +#ifdef UI_GTK2 +#undef SCROLLEDWINDOW_SET_CHILD +#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child) +#endif + +#if GTK_MAJOR_VERSION >= 4 +#define UI_GTK_SINCE_V4(st) st +#define UI_GTK_SINCE_V3(st) +#define UI_GTK_V2(st) +#define UI_GTK_V3(st) +#define UI_GTK_V4(st) st +#elif GTK_MAJOR_VERSION >= 3 +#define UI_GTK_SINCE_V4(st) st +#define UI_GTK_SINCE_V3(st) st +#define UI_GTK_V2(st) +#define UI_GTK_V3(st) st +#define UI_GTK_V4(st) +#else +#define UI_GTK_SINCE_V4(st) +#define UI_GTK_SINCE_V3(st) +#define UI_GTK_V2(st) st +#define UI_GTK_V3(st) +#define UI_GTK_V4(st) +#endif + + typedef struct UiEventData { UiObject *obj; ui_callback callback; void *userdata; int value; + void *customdata; } UiEventData; +typedef struct UiEventDataExt { + UiObject *obj; + ui_callback callback; + void *userdata; + ui_callback callback2; + void *userdata2; + int value0; + int value1; + int value2; + int value3; + void *customdata0; + void *customdata1; + void *customdata2; + void *customdata3; +} UiEventDataExt; + typedef struct UiVarEventData { - UiObject *obj; - UiVar *var; - UiObserver **observers; + UiObject *obj; + UiVar *var; + UiObserver **observers; + ui_callback callback; + void *userdata; } UiVarEventData; +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; -typedef struct UiJob { - UiObject *obj; - ui_threadfunc job_func; - void *job_data; - ui_callback finish_callback; - void *finish_data; -} UiJob; - +#ifndef UI_GTK4 struct UiSelection { GtkSelectionData *data; }; +#endif -typedef enum UiOrientation UiOrientation; -enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; - -#ifndef UI_GTK2 +#ifdef UI_APPLICATION void ui_app_quit(); GtkApplication* ui_get_application(); #endif int ui_get_scalefactor(); +void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style); +void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups); +void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups); + void ui_destroy_userdata(GtkWidget *object, void *userdata); void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data); +void ui_destroy_widget_var(GtkWidget *object, UiVar *var); void ui_destroy_boundvar(UiContext *ctx, UiVar *var); void ui_set_active_window(UiObject *obj); UiObject *ui_get_active_window(); +#if GTK_MAJOR_VERSION >= 3 +void ui_css_init(void); +#endif + #ifdef __cplusplus } #endif diff --git a/ui/gtk/window.c b/ui/gtk/window.c index 7313b0b..0928ee1 100644 --- a/ui/gtk/window.c +++ b/ui/gtk/window.c @@ -33,29 +33,25 @@ #include "../ui/window.h" #include "../ui/properties.h" #include "../common/context.h" +#include "../common/menu.h" +#include "../common/toolbar.h" + +#include #include "menu.h" #include "toolbar.h" #include "container.h" +#include "headerbar.h" +#include "button.h" static int nwindows = 0; static int window_default_width = 650; static int window_default_height = 550; -void ui_exit_event(GtkWidget *widget, gpointer data) { +static gboolean ui_window_destroy(void *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 + uic_object_destroy(obj); nwindows--; #ifdef UI_GTK2 @@ -63,13 +59,56 @@ void ui_exit_event(GtkWidget *widget, gpointer data) { gtk_main_quit(); } #endif + + return FALSE; } -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 +void ui_window_widget_destroy(UiObject *obj) { +#if GTK_MAJOR_VERSION >= 4 + gtk_window_destroy(GTK_WINDOW(obj->widget)); +#else + gtk_widget_destroy(obj->widget); +#endif +} + +void ui_exit_event(GtkWidget *widget, gpointer data) { + // delay exit handler + g_idle_add(ui_window_destroy, data); +} + +static gboolean ui_window_close_request(UiObject *obj) { + uic_context_prepare_close(obj->ctx); + obj->ref--; + if(obj->ref > 0) { +#if GTK_CHECK_VERSION(2, 18, 0) + gtk_widget_set_visible(obj->widget, FALSE); +#else + gtk_widget_hide(obj->widget); +#endif + return TRUE; + } else { + return FALSE; + } +} + +#if GTK_MAJOR_VERSION >= 4 +static gboolean close_request(GtkWindow* self, UiObject *obj) { + return ui_window_close_request(obj); +} +#else +static gboolean close_request(GtkWidget* self, GdkEvent* event, UiObject *obj) { + return ui_window_close_request(obj); +} +#endif + +static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) { + CxMempool *mp = cxBasicMempoolCreate(256); + UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); + obj->ref = 0; + +#ifdef UI_LIBADWAITA + obj->widget = adw_application_window_new(ui_get_application()); +#elif !defined(UI_GTK2) obj->widget = gtk_application_window_new(ui_get_application()); #else obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); @@ -79,12 +118,16 @@ static UiObject* create_window(char *title, void *window_data, UiBool simple) { obj->ctx = uic_context(obj, mp); obj->window = window_data; +#if GTK_CHECK_VERSION(4, 0, 0) + obj->ctx->action_map = G_ACTION_MAP(obj->widget); +#endif + if(title != NULL) { gtk_window_set_title(GTK_WINDOW(obj->widget), title); } - char *width = ui_get_property("ui.window.width"); - char *height = ui_get_property("ui.window.height"); + const char *width = ui_get_property("ui.window.width"); + const char *height = ui_get_property("ui.window.height"); if(width && height) { gtk_window_set_default_size( GTK_WINDOW(obj->widget), @@ -93,35 +136,125 @@ static UiObject* create_window(char *title, void *window_data, UiBool simple) { } else { gtk_window_set_default_size( GTK_WINDOW(obj->widget), - window_default_width, + window_default_width + sidebar*250, window_default_height); } + obj->destroy = ui_window_widget_destroy; g_signal_connect( obj->widget, "destroy", G_CALLBACK(ui_exit_event), obj); +#if GTK_MAJOR_VERSION >= 4 + g_signal_connect( + obj->widget, + "close-request", + G_CALLBACK(close_request), + obj); +#else + g_signal_connect( + obj->widget, + "delete-event", + G_CALLBACK(close_request), + obj); +#endif GtkWidget *vbox = ui_gtk_vbox_new(0); - gtk_container_add(GTK_CONTAINER(obj->widget), vbox); +#ifdef UI_LIBADWAITA + GtkWidget *toolbar_view = adw_toolbar_view_new(); + adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox); + GtkWidget *content_box = ui_gtk_vbox_new(0); + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + + if(sidebar) { + GtkWidget *splitview = adw_overlay_split_view_new(); + adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview); + + GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new(); + adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view); + GtkWidget *sidebar_headerbar = adw_header_bar_new(); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar); + + adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view); + + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view); + } else { + adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view); + } + + + GtkWidget *headerbar = adw_header_bar_new(); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar); + g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar); + + if(!simple) { + ui_fill_headerbar(obj, headerbar); + } +#elif GTK_MAJOR_VERSION >= 4 + GtkWidget *content_box = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + if(!simple) { + if(uic_get_menu_list()) { + GtkWidget *mb = ui_create_menubar(obj); + if(mb) { + BOX_ADD(vbox, mb); + } + } + } + if(sidebar) { + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + gtk_paned_set_start_child(GTK_PANED(paned), sidebar_vbox); + gtk_paned_set_end_child(GTK_PANED(paned), content_box); + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + } else { + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + } +#else if(!simple) { // menu - GtkWidget *mb = ui_create_menubar(obj); - if(mb) { - gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0); + if(uic_get_menu_list()) { + GtkWidget *mb = ui_create_menubar(obj); + if(mb) { + gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0); + } } // toolbar - GtkWidget *tb = ui_create_toolbar(obj); - if(tb) { - gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); + if(uic_toolbar_isenabled()) { + GtkWidget *tb = ui_create_toolbar(obj); + if(tb) { + gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); + } } + + //GtkWidget *hb = ui_create_headerbar(obj); + //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb); } + GtkWidget *content_box = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + if(sidebar) { + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + gtk_paned_add1(GTK_PANED(paned), sidebar_vbox); + gtk_paned_add2(GTK_PANED(paned), content_box); + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + gtk_paned_set_position (GTK_PANED(paned), 200); + } else { + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + } + +#endif + // window content // the content has a (TODO: not yet) configurable frame + // TODO: really? why + /* GtkWidget *frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); @@ -130,58 +263,637 @@ static UiObject* create_window(char *title, void *window_data, UiBool simple) { GtkWidget *content_box = ui_gtk_vbox_new(0); gtk_container_add(GTK_CONTAINER(frame), content_box); obj->container = ui_box_container(obj, content_box); + */ + obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX); nwindows++; return obj; } -UiObject* ui_window(char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject* ui_window(const char *title, void *window_data) { + return create_window(title, window_data, FALSE, FALSE); } -UiObject* ui_simplewindow(char *title, void *window_data) { - return create_window(title, window_data, TRUE); +UiObject *ui_sidebar_window(const char *title, void *window_data) { + return create_window(title, window_data, TRUE, FALSE); } -static char* ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action) { - char *button; - char *title; +UiObject* ui_simple_window(const char *title, void *window_data) { + return create_window(title, window_data, FALSE, TRUE); +} + +void ui_window_size(UiObject *obj, int width, int height) { + gtk_window_set_default_size( + GTK_WINDOW(obj->widget), + width, + height); +} + +#ifdef UI_LIBADWAITA + +static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) { + UiEvent evt; + evt.obj = data->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.eventdata = NULL; + evt.intval = 0; + + if(!strcmp(response, "btn1")) { + evt.intval = 1; + } else if(!strcmp(response, "btn2")) { + evt.intval = 2; + } + + if(data->customdata) { + GtkWidget *entry = data->customdata; + evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry)); + } + + if(data->callback) { + data->callback(&evt, data->userdata); + } +} + +void ui_dialog_create(UiObject *parent, UiDialogArgs args) { + AdwDialog *dialog = adw_alert_dialog_new(args.title, args.content); + UiEventData *event = malloc(sizeof(UiEventData)); + event->callback = args.result; + event->userdata = args.resultdata; + event->customdata = NULL; + event->value = 0; + event->obj = parent; + + if(args.button1_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn1", args.button1_label); + } + if(args.button2_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn2", args.button2_label); + } + if(args.closebutton_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "close", args.closebutton_label); + adw_alert_dialog_set_close_response(ADW_ALERT_DIALOG(dialog), "close"); + } + + GtkWidget *entry = NULL; + if(args.input || args.password) { + entry = gtk_entry_new(); + if(args.password) { + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + } + if(args.input_value) { + ENTRY_SET_TEXT(entry, args.input_value); + } + adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry); + event->customdata = entry; + } + + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), event); + adw_dialog_present(dialog, parent->widget); + + if(entry) { + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry)); + } +} +#else + +static void ui_dialog_response (GtkDialog* self, gint response_id, gpointer user_data) { + UiEventData *data = user_data; + UiEvent evt; + evt.obj = data->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.eventdata = NULL; + evt.intval = 0; + + if(data->customdata) { + GtkWidget *entry = data->customdata; + evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry)); + + } + if(response_id == 1 || response_id == 2) { + evt.intval = response_id; + } + + + if(data->callback) { + data->callback(&evt, data->userdata); + } + + WINDOW_DESTROY(GTK_WIDGET(self)); +} + +void ui_dialog_create(UiObject *parent, UiDialogArgs args) { + GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new()); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + + GtkWidget *dialog_w = GTK_WIDGET(dialog); + if(args.title) { + gtk_window_set_title(GTK_WINDOW(dialog), args.title); + } + if(args.button1_label) { + gtk_dialog_add_button(dialog, args.button1_label, 1); + } + if(args.button2_label) { + gtk_dialog_add_button(dialog, args.button2_label, 2); + } + if(args.closebutton_label) { + gtk_dialog_add_button(dialog, args.closebutton_label, 0); + } + + GtkWidget *content_area = gtk_dialog_get_content_area(dialog); + if(args.content) { + GtkWidget *label = gtk_label_new(args.content); + BOX_ADD(content_area, label); + } + + GtkWidget *textfield = NULL; + if(args.input || args.password) { + textfield = gtk_entry_new(); + if(args.password) { + gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); + } + if(args.input_value) { + ENTRY_SET_TEXT(textfield, args.input_value); + } + BOX_ADD(content_area, textfield); + } + + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = parent; + event->callback = args.result; + event->userdata = args.resultdata; + event->value = 0; + event->customdata = textfield; + + g_signal_connect(dialog_w, + "response", + G_CALLBACK(ui_dialog_response), + event); + + WINDOW_SHOW(GTK_WIDGET(dialog_w)); +} +#endif + + +#if GTK_MAJOR_VERSION >= 3 +UiFileList listmodel2filelist(GListModel *selection) { + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + flist.nfiles = g_list_model_get_n_items(selection); + flist.files = calloc(flist.nfiles, sizeof(char*)); + for(int i=0;ivalue; + int multi = mode & UI_FILEDIALOG_SELECT_MULTI; + if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + if(multi) { + selection = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error); + } else { + file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), result, &error); + } + } else if((mode & UI_GTK_FILEDIALOG_OPEN) == UI_GTK_FILEDIALOG_OPEN) { + if(multi) { + selection = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error); + } else { + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), result, &error); + } + } else { + file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error); + } + + UiEvent evt; + evt.obj = event->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = 0; + + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + evt.eventdata = &flist; + + if(selection) { + flist = listmodel2filelist(selection); + g_object_unref(selection); + } else if(file) { + char *path = g_file_get_path(file); + if(path) { + flist.nfiles = 1; + flist.files = calloc(flist.nfiles, sizeof(char*)); + flist.files[0] = strdup(path); + } + g_object_unref(file); + } + + if(event->callback) { + event->callback(&evt, event->userdata); + } + + for(int i=0;iwidget), - 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; + mode |= UI_GTK_FILEDIALOG_SAVE; + } + + UiEventData *event = malloc(sizeof(UiEventData)); + event->callback = file_selected_callback; + event->userdata = cbdata; + event->customdata = NULL; + event->value = mode; + event->obj = obj; + + GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(obj->widget)); + GtkFileDialog *dialog = gtk_file_dialog_new(); + if(name) { + gtk_file_dialog_set_initial_name(dialog, name); + } + + int multi = mode & UI_FILEDIALOG_SELECT_MULTI; + if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + if(multi) { + gtk_file_dialog_select_multiple_folders(dialog, parent, NULL, filechooser_opened, event); + } else { + gtk_file_dialog_select_folder(dialog, parent, NULL, filechooser_opened, event); + } + } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) { + if(multi) { + gtk_file_dialog_open_multiple(dialog, parent, NULL, filechooser_opened, event); + } else { + gtk_file_dialog_open(dialog, parent, NULL, filechooser_opened, event); + } } else { - gtk_widget_destroy(dialog); - return NULL; + gtk_file_dialog_save(dialog, parent, NULL, filechooser_opened, event); } + + g_object_unref(dialog); } +#else + + -char* ui_openfiledialog(UiObject *obj) { - return ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN); +static void filechooser_response(GtkDialog* self, gint response_id, UiEventData *data) { + UiEvent evt; + evt.obj = data->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = 0; + + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + evt.eventdata = &flist; + + if(response_id == GTK_RESPONSE_ACCEPT) { +#if GTK_CHECK_VERSION(4, 0, 0) + GListModel *selection = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(self)); + flist = flist = listmodel2filelist(selection); + g_object_unref(selection); +#else + GSList *selection = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(self)); + flist.nfiles = g_slist_length(selection); + flist.files = calloc(flist.nfiles, sizeof(char*)); + int i = 0; + while(selection) { + char *file = selection->data; + flist.files[i] = strdup(file); + g_free(file); + selection = selection->next; + i++; + } + g_slist_free(selection); +#endif + } + + + if(data->callback) { + data->callback(&evt, data->userdata); + } + + for(int i=0;iwidget), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "Cancel", + GTK_RESPONSE_CANCEL, + "Select Folder", + GTK_RESPONSE_ACCEPT, + NULL); + } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) { + dialog = gtk_file_chooser_dialog_new ( + "Select Folder", + GTK_WINDOW(obj->widget), + action, + "Cancel", + GTK_RESPONSE_CANCEL, + "Open File", + GTK_RESPONSE_ACCEPT, + NULL); + } else { + dialog = gtk_file_chooser_dialog_new ( + "Save File", + GTK_WINDOW(obj->widget), + action, + "Cancel", + GTK_RESPONSE_CANCEL, + "Save File", + GTK_RESPONSE_ACCEPT, + NULL); + } + + if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + } + + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = cbdata; + event->callback = file_selected_callback; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + dialog, + "response", + G_CALLBACK(filechooser_response), + event); + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + + UiEvent evt; + evt.obj = obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = 0; + + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + evt.eventdata = &flist; + + gtk_widget_show(dialog); } +#endif + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN, mode, NULL, file_selected_callback, cbdata); +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata); +} + +#if GTK_CHECK_VERSION(4, 10, 0) +#define DIALOG_NEW() gtk_window_new() +#else +#define DIALOG_NEW() gtk_dialog_new() + +static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) { + UiEventData *event = user_data; + // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT? + if(event->callback) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = event->value; + event->callback(&e, event->userdata); + } +} + +#endif + +#if GTK_CHECK_VERSION(4, 0, 0) +#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set) +#define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button) +#else +#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set) +#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button) +#endif + + +UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) { + GtkWidget *dialog = DIALOG_NEW(); + if(args.width > 0 || args.height > 0) { + gtk_window_set_default_size( + GTK_WINDOW(dialog), + args.width, + args.height); + } + + + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); + if(args.modal != UI_OFF) { + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + } + + CxMempool *mp = cxBasicMempoolCreate(256); + UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->widget = dialog; + obj->ref = 0; + obj->destroy = ui_window_widget_destroy; + nwindows++; + + if(args.title != NULL) { + gtk_window_set_title(GTK_WINDOW(dialog), args.title); + } + +#if ! GTK_CHECK_VERSION(4, 10, 0) + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = args.onclickdata; + event->callback = args.onclick; + event->value = 0; + event->customdata = NULL; + + g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event); + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); +#endif + + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_exit_event), + obj); +#if GTK_MAJOR_VERSION >= 4 + g_signal_connect( + obj->widget, + "close-request", + G_CALLBACK(close_request), + obj); +#else + g_signal_connect( + obj->widget, + "delete-event", + G_CALLBACK(close_request), + obj); +#endif + +#if GTK_MAJOR_VERSION < 4 + GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_remove(GTK_CONTAINER(dialog), c); +#endif + + GtkWidget *content_vbox = ui_gtk_vbox_new(0); + obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX); + if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) { +#if GTK_CHECK_VERSION(3, 10, 0) + if(args.titlebar_buttons != UI_OFF) { + GtkWidget *headerbar = gtk_header_bar_new(); + gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar); + if(args.show_closebutton == UI_OFF) { + HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE); + } + + if(args.lbutton1) { + GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1); + gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 1) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.lbutton2) { + GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2); + gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 2) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + + if(args.rbutton4) { + GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4); + gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 4) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.rbutton3) { + GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3); + gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 3) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + WINDOW_SET_CONTENT(obj->widget, content_vbox); + return obj; + } +#endif + GtkWidget *vbox = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + + GtkWidget *grid = ui_create_grid_widget(10, 10); + GtkWidget *widget = ui_box_set_margin(grid, 16); + gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); + + if(args.lbutton1) { + GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1); + gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1); + if(args.default_button == 1) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.lbutton2) { + GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2); + gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1); + if(args.default_button == 2) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + GtkWidget *space = gtk_label_new(NULL); + gtk_widget_set_hexpand(space, TRUE); + gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1); + if(args.rbutton3) { + GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3); + gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1); + if(args.default_button == 3) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.rbutton4) { + GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4); + gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1); + if(args.default_button == 4) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + + BOX_ADD_EXPAND(vbox, content_vbox); + BOX_ADD_NO_EXPAND(vbox, separator); + BOX_ADD_NO_EXPAND(vbox, widget); + } else { + WINDOW_SET_CONTENT(obj->widget, content_vbox); + } + + return obj; +} diff --git a/ui/motif/Makefile b/ui/motif/Makefile deleted file mode 100644 index 3f7c064..0000000 --- a/ui/motif/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -# 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 deleted file mode 100644 index 49d0701..0000000 --- a/ui/motif/button.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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/container.c b/ui/motif/container.c deleted file mode 100644 index a90a678..0000000 --- a/ui/motif/container.c +++ /dev/null @@ -1,805 +0,0 @@ -/* - * 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 deleted file mode 100644 index 57c2c3b..0000000 --- a/ui/motif/container.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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/graphics.c b/ui/motif/graphics.c deleted file mode 100644 index fefeca6..0000000 --- a/ui/motif/graphics.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * 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/image.c b/ui/motif/image.c deleted file mode 100644 index 5142dbc..0000000 --- a/ui/motif/image.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 deleted file mode 100644 index 681d232..0000000 --- a/ui/motif/image.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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/list.c b/ui/motif/list.c deleted file mode 100644 index a92f9c8..0000000 --- a/ui/motif/list.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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/menu.c b/ui/motif/menu.c deleted file mode 100644 index 15a248d..0000000 --- a/ui/motif/menu.c +++ /dev/null @@ -1,626 +0,0 @@ -/* - * 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/objs.mk b/ui/motif/objs.mk deleted file mode 100644 index 179deac..0000000 --- a/ui/motif/objs.mk +++ /dev/null @@ -1,49 +0,0 @@ -# -# 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 deleted file mode 100644 index 07d7ada..0000000 --- a/ui/motif/range.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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/stock.c b/ui/motif/stock.c deleted file mode 100644 index 1747cf6..0000000 --- a/ui/motif/stock.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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/text.c b/ui/motif/text.c deleted file mode 100644 index a4835c3..0000000 --- a/ui/motif/text.c +++ /dev/null @@ -1,488 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0f44f95..0000000 --- a/ui/motif/text.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 deleted file mode 100644 index 3456dd3..0000000 --- a/ui/motif/toolbar.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - * 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 deleted file mode 100644 index 22d0a5d..0000000 --- a/ui/motif/toolbar.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 deleted file mode 100644 index 9757f64..0000000 --- a/ui/motif/toolkit.c +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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/tree.c b/ui/motif/tree.c deleted file mode 100644 index 0620e0f..0000000 --- a/ui/motif/tree.c +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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 deleted file mode 100644 index 68e5b06..0000000 --- a/ui/motif/tree.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 deleted file mode 100644 index 041ab7f..0000000 --- a/ui/motif/window.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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 index cbf8fe8..17407b7 100644 --- a/ui/ui/button.h +++ b/ui/ui/button.h @@ -34,14 +34,64 @@ #ifdef __cplusplus extern "C" { #endif + +typedef struct UiButtonArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char* label; + const char* stockid; + const char* icon; + UiLabelType labeltype; + ui_callback onclick; + void* onclickdata; + + const int* groups; +} UiButtonArgs; + +typedef struct UiToggleArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char* label; + const char* stockid; + const char* icon; + UiLabelType labeltype; + UiInteger* value; + const char* varname; + ui_callback onchange; + void* onchangedata; + int enable_group; -UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data); + const int* groups; +} UiToggleArgs; + +#define ui_button(obj, ...) ui_button_create(obj, (UiButtonArgs){ __VA_ARGS__ } ) +#define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } ) +#define ui_checkbox(obj, ...) ui_checkbox_create(obj, (UiToggleArgs){ __VA_ARGS__ } ) +#define ui_switch(obj, ...) ui_switch_create(obj, (UiToggleArgs){ __VA_ARGS__ } ) +#define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } ) -UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value); -UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname); +UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args); +UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args); +UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args); +UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args); +UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args); -UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup); -UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname); #ifdef __cplusplus diff --git a/ui/ui/container.h b/ui/ui/container.h index 423342a..e08553f 100644 --- a/ui/ui/container.h +++ b/ui/ui/container.h @@ -34,54 +34,262 @@ #ifdef __cplusplus extern "C" { #endif + +typedef enum UiSubContainerType { + UI_CONTAINER_VBOX = 0, + UI_CONTAINER_HBOX, + UI_CONTAINER_GRID, + UI_CONTAINER_NO_SUB +} UiSubContainerType; + +typedef enum UiTabViewType { + UI_TABVIEW_DEFAULT = 0, + UI_TABVIEW_DOC, + UI_TABVIEW_NAVIGATION_SIDE, + UI_TABVIEW_NAVIGATION_TOP, + UI_TABVIEW_NAVIGATION_TOP2, + UI_TABVIEW_INVISIBLE +} UiTabViewType; + +typedef enum UiHeaderbarAlternative { + UI_HEADERBAR_ALTERNATIVE_DEFAULT = 0, + UI_HEADERBAR_ALTERNATIVE_TOOLBAR, + UI_HEADERBAR_ALTERNATIVE_BOX +} UiHeaderbarAlternative; + +typedef struct UiContainerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int margin; + int spacing; + int columnspacing; + int rowspacing; +} UiContainerArgs; + +typedef struct UiFrameArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiSubContainerType subcontainer; + + int margin; + int spacing; + int columnspacing; + int rowspacing; + + const char* label; + UiBool isexpanded; +} UiFrameArgs; + +typedef struct UiTabViewArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiTabViewType tabview; + + UiSubContainerType subcontainer; -#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)) + UiInteger *value; + const char* varname; + + int margin; + int spacing; + int columnspacing; + int rowspacing; + + const char* label; + UiBool isexpanded; +} UiTabViewArgs; + +typedef struct UiHeaderbarArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; -void ui_end(UiObject *obj); + UiBool showtitle; + UiBool showwindowbuttons; -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); + UiHeaderbarAlternative alternative; + int alt_spacing; +} UiHeaderbarArgs; -UIWIDGET ui_grid(UiObject *obj); -UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing); +typedef struct UiSidebarArgs { + const char *name; + const char *style_class; + int margin; + int spacing; +} UiSidebarArgs; -UIWIDGET ui_scrolledwindow(UiObject *obj); +typedef struct UiItemListContainerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int margin; + int spacing; + + int sub_margin; + int sub_spacing; + int sub_columnspacing; + int sub_rowspacing; + + UiList *value; + const char *varname; + /* + * void create_ui(UiObject *obj, int index, void *elm, void *userdata) + * + * UI constructor for each list element + * + * This callback is executed for each list element. A new UiObject is + * created for every item. + */ + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + + /* + * ItemList container type + * Only UI_CONTAINER_VBOX or UI_CONTAINER_HBOX are supported + */ + UiSubContainerType container; + + /* + * item root container + */ + UiSubContainerType subcontainer; +} UiItemListContainerArgs; -UIWIDGET ui_sidebar(UiObject *obj); +struct UiContainerX { + void *container; + int close; + UiContainerX *prev; + UiContainerX *next; +}; -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); +#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj)) -// 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); +#define ui_vbox(obj, ...) for(ui_vbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_hbox(obj, ...) for(ui_hbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_grid(obj, ...) for(ui_grid_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_frame(obj, ...) for(ui_frame_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_expander(obj, ...) for(ui_expander_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_scrolledwindow(obj, ...) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_tabview(obj, ...) for(ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_sidebar(obj, ...) for(ui_sidebar_create(obj, (UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_grid0(obj) for(ui_grid_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_frame0(obj) for(ui_frame_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_expander0(obj) for(ui_expande_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_tabview0(obj) for(ui_tabview_create(obj, (UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_sidebar0(obj) for(ui_sidebar_create(obj, (UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_itemlist(obj, ...) ui_itemlist_create(obj, (UiItemListContainerArgs) { __VA_ARGS__} ) + +UIEXPORT void ui_end(UiObject *obj); // deprecated +UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end + +UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args); +UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args); +UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args); +UIEXPORT UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args); +UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args); +UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs args); + +UIEXPORT UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args); +UIEXPORT void ui_tab_create(UiObject *obj, const char* title); +UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab); +UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab); +UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index); + +UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args); +UIEXPORT void ui_headerbar_start_create(UiObject *obj); +UIEXPORT void ui_headerbar_center_create(UiObject *obj); +UIEXPORT void ui_headerbar_end_create(UiObject *obj); +UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args); -UiTabbedPane* ui_tabbed_document_view(UiObject *obj); +UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args); -UiObject* ui_document_tab(UiTabbedPane *view); +UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO +UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO + + +// box container layout functions +UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill); +// grid container layout functions +UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand); +UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand); +UIEXPORT void ui_layout_hfill(UiObject *obj, UiBool fill); +UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill); +UIEXPORT void ui_layout_width(UiObject *obj, int width); +UIEXPORT void ui_layout_height(UiObject* obj, int width); +UIEXPORT void ui_layout_colspan(UiObject *obj, int cols); +UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows); +UIEXPORT void ui_newline(UiObject *obj); + +// TODO +UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj); +UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view); /* used for macro */ -void ui_container_begin_close(UiObject *obj); -int ui_container_finish(UiObject *obj); +UIEXPORT void ui_container_begin_close(UiObject *obj); +UIEXPORT int ui_container_finish(UiObject *obj); + +#define UI_APPLY_LAYOUT1(obj, args) \ + if(args.fill != UI_DEFAULT) ui_layout_fill(obj, args.fill == UI_ON ? 1 : 0 ); \ + if(args.hexpand) ui_layout_hexpand(obj, 1); \ + if(args.vexpand) ui_layout_vexpand(obj, 1); \ + if(args.hfill) ui_layout_hfill(obj, 1); \ + if(args.vfill) ui_layout_vfill(obj, 1); \ + if(args.colspan > 0) ui_layout_colspan(obj, args.colspan); \ + if(args.rowspan > 0) ui_layout_rowspan(obj, args.rowspan); \ + /*force caller to add ';'*/(void)0 #ifdef __cplusplus diff --git a/ui/ui/display.h b/ui/ui/display.h index 0673488..66f6f39 100644 --- a/ui/ui/display.h +++ b/ui/ui/display.h @@ -39,16 +39,96 @@ extern "C" { #endif +enum UiAlignment { + UI_ALIGN_DEFAULT = 0, + UI_ALIGN_LEFT, + UI_ALIGN_RIGHT, + UI_ALIGN_CENTER +}; + +typedef enum UiAlignment UiAlignment; + +enum UiLabelStyle { + UI_LABEL_STYLE_DEFAULT = 0, + UI_LABEL_STYLE_TITLE, + UI_LABEL_STYLE_SUBTITLE, + UI_LABEL_STYLE_DIM +}; + +typedef enum UiLabelStyle UiLabelStyle; + +typedef struct UiLabelArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char* label; + UiAlignment align; + UiLabelStyle style; + UiString* value; + const char* varname; +} UiLabelArgs; + +typedef struct UiProgressbarArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; + + double min; + double max; + UiDouble* value; + const char* varname; +} UiProgressbarArgs; + +typedef struct UiProgressbarSpinnerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiInteger* value; + const char* varname; +} UiProgressbarSpinnerArgs; + /* 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); + +#define ui_label(obj, ...) ui_label_create(obj, (UiLabelArgs) { __VA_ARGS__ }) +#define ui_llabel(obj, ...) ui_llabel_create(obj, (UiLabelArgs) { __VA_ARGS__ }) +#define ui_rlabel(obj, ...) ui_rlabel_create(obj, (UiLabelArgs) { __VA_ARGS__ }) + + +UIEXPORT UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args); +UIEXPORT UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args); +UIEXPORT UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args); + +UIWIDGET ui_space_deprecated(UiObject *obj); +UIWIDGET ui_separator_deprecated(UiObject *obj); + +/* progress bar/spinner */ + +#define ui_progressbar(obj, ...) ui_progressbar_create(obj, (UiProgressbarArgs) { __VA_ARGS__ } ) +#define ui_progressspinner(obj, ...) ui_progressspinner_create(obj, (UiProgressbarSpinnerArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args); +UIEXPORT UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args); #ifdef __cplusplus diff --git a/ui/ui/dnd.h b/ui/ui/dnd.h index 71efd36..4c6913f 100644 --- a/ui/ui/dnd.h +++ b/ui/ui/dnd.h @@ -37,11 +37,16 @@ extern "C" { #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); +UIEXPORT void ui_selection_settext(UiDnD *sel, char *str, int len); +UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm); -char* ui_selection_gettext(UiSelection *sel); -char** ui_selection_geturis(UiSelection *sel, size_t *nelm); +UIEXPORT char* ui_selection_gettext(UiDnD *sel); +UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel); + +UIEXPORT UiDnDAction ui_dnd_result(UiDnD *dnd); +UIEXPORT UiBool ui_dnd_need_delete(UiDnD *dnd); + +UIEXPORT void ui_dnd_accept(UiDnD *dnd, UiBool accept); #ifdef __cplusplus diff --git a/ui/ui/entry.h b/ui/ui/entry.h index 5d63351..d6fc039 100644 --- a/ui/ui/entry.h +++ b/ui/ui/entry.h @@ -35,13 +35,35 @@ 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); +typedef struct UiSpinnerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + double step; + int digits; + UiInteger *intvalue; + UiDouble* doublevalue; + UiRange *rangevalue; + const char* varname; + ui_callback onchange; + void* onchangedata; + + const int *groups; +} UiSpinnerArgs; + + + +UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args); + +#define ui_spinner(obj, ...) ui_spinner_create(obj, (UiSpinnerArgs){ __VA_ARGS__ } ) void ui_spinner_setrange(UIWIDGET spinner, double min, double max); void ui_spinner_setdigits(UIWIDGET spinner, int digits); diff --git a/ui/motif/button.h b/ui/ui/icons.h similarity index 50% rename from ui/motif/button.h rename to ui/ui/icons.h index b317dd3..cd4ef6c 100644 --- a/ui/motif/button.h +++ b/ui/ui/icons.h @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,44 +26,64 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef BUTTON_H -#define BUTTON_H +#ifndef UI_ICONS_H +#define UI_ICONS_H -#include "../ui/button.h" #include "toolkit.h" -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif -typedef struct { - UcxList *buttons; - Widget current; - int ref; -} RadioButtonGroup; +#ifdef UI_GTK + +#define UI_ICON_HOME "go-home" +#define UI_ICON_NEW_WINDOW "list-add" +#define UI_ICON_REFRESH "view-refresh" +#define UI_ICON_NEW_FOLDER "folder-new" +#define UI_ICON_ADD "document-new" +#define UI_ICON_UPLOAD "document-open" +#define UI_ICON_SAVE_LOCAL "document-save-as" +#define UI_ICON_DELETE "edit-delete" +#define UI_ICON_DOCK_LEFT "" +#define UI_ICON_DOCK_RIGHT "" +#define UI_ICON_GO_BACK "go-previous" +#define UI_ICON_GO_FORWARD "go-next" + +#endif /* UI_GTK */ + + -typedef struct { - UiObject *obj; - ui_callback callback; - void *userdata; - RadioButtonGroup *group; -} RadioEventData; +#ifdef UI_WINUI + +#define UI_ICON_HOME "Home" +#define UI_ICON_NEW_WINDOW "NewWindow" +#define UI_ICON_REFRESH "Refresh" +#define UI_ICON_NEW_FOLDER "NewFolder" +#define UI_ICON_ADD "Add" +#define UI_ICON_UPLOAD "Upload" +#define UI_ICON_SAVE_LOCAL "SaveLocal" +#define UI_ICON_DELETE "Delete" +#define UI_ICON_DOCK_LEFT "DockLeft" +#define UI_ICON_DOCK_RIGHT "DockRight" +#define UI_ICON_GO_BACK "Back" +#define UI_ICON_GO_FORWARD "Forward" + +#endif /* UI_WINUI */ + + +UIEXPORT UiIcon* ui_icon(const char* name, size_t size); +UIEXPORT UiIcon* ui_icon_unscaled(const char *name, int size); +UIEXPORT UiIcon* ui_imageicon(const char* file); +UIEXPORT void ui_icon_free(UiIcon* icon); -// 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); +UIEXPORT UiIcon* ui_foldericon(size_t size); +UIEXPORT UiIcon* ui_fileicon(size_t size); -int64_t ui_radiobutton_get(UiInteger *value); -void ui_radiobutton_set(UiInteger *value, int64_t i); -#ifdef __cplusplus +#ifdef __cplusplus } #endif -#endif /* BUTTON_H */ +#endif /* UI_ICONS_H */ diff --git a/ui/ui/image.h b/ui/ui/image.h index efd6f70..c26ec3c 100644 --- a/ui/ui/image.h +++ b/ui/ui/image.h @@ -35,16 +35,31 @@ 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); +#define UI_IMAGE_OBJECT_TYPE "image" + +typedef struct UiImageViewerArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; -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); + UiBool scrollarea; + UiBool autoscale; + UiGeneric *value; + const char *varname; + UiMenuBuilder *contextmenu; +} UiImageViewerArgs; + +#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args); +UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path); #ifdef __cplusplus } diff --git a/ui/ui/menu.h b/ui/ui/menu.h index a472839..2e7b6b1 100644 --- a/ui/ui/menu.h +++ b/ui/ui/menu.h @@ -35,36 +35,74 @@ 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, ...); +typedef struct UiMenuItemArgs { + const char* label; + const char* stockid; + const char* icon; + + ui_callback onclick; + void* onclickdata; + + const int* groups; +} UiMenuItemArgs; + +typedef struct UiMenuToggleItemArgs { + const char* label; + const char* stockid; + const char* icon; + + const char* varname; + ui_callback onchange; + void* onchangedata; + + const int* groups; +} UiMenuToggleItemArgs; -void ui_menuseparator(); +typedef struct UiMenuItemListArgs { + const char* varname; + ui_getvaluefunc getvalue; + ui_callback onselect; + void* onselectdata; + UiBool addseparator; +} UiMenuItemListArgs; -void ui_checkitem(char *label, ui_callback f, void *userdata); -void ui_checkitem_nv(char *label, char *vname); +#define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close()) -void ui_menuitem_list(UiList *items, ui_callback f, void *userdata); +#define ui_menuitem(...) ui_menuitem_create((UiMenuItemArgs){ __VA_ARGS__ }) +#define ui_menu_toggleitem(...) ui_menu_toggleitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ }) +#define ui_menu_radioitem(...) ui_menu_radioitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ }) +#define ui_menu_itemlist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS__ } ) +#define ui_menu_togglelist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} ) +#define ui_menu_radiolist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} ) + +UIEXPORT void ui_menu_create(const char* label); +UIEXPORT void ui_menuitem_create(UiMenuItemArgs args); +UIEXPORT void ui_menu_toggleitem_create(UiMenuToggleItemArgs args); +UIEXPORT void ui_menu_radioitem_create(UiMenuToggleItemArgs args); + +UIEXPORT void ui_menuseparator(); + +UIEXPORT void ui_menu_itemlist_create(UiMenuItemListArgs args); +UIEXPORT void ui_menu_toggleitemlist_create(UiMenuItemListArgs args); +UIEXPORT void ui_menu_radioitemlist_create(UiMenuItemListArgs args); + +UIEXPORT void ui_menu_end(void); // TODO: private /* * widget menu functions */ -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, ...); + +#define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close()) + +UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder); +UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder); +UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget); +UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y); + +// used for macro +UIEXPORT void ui_menu_close(void); +UIEXPORT int ui_menu_is_open(void); #ifdef __cplusplus } diff --git a/ui/ui/properties.h b/ui/ui/properties.h index 2675812..5c985d9 100644 --- a/ui/ui/properties.h +++ b/ui/ui/properties.h @@ -35,15 +35,11 @@ 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); +const char* ui_get_property(const char *name); +void ui_set_property(const char *name, const char *value); +const char* ui_set_default_property(const char *name, const char *value); -void ui_set_default_property(char *name, char *value); +int ui_properties_store(void); void ui_locales_dir(char *path); void ui_pixmaps_dir(char *path); diff --git a/ui/ui/text.h b/ui/ui/text.h index 7df2ce3..cb54f4c 100644 --- a/ui/ui/text.h +++ b/ui/ui/text.h @@ -35,28 +35,106 @@ extern "C" { #endif -UIWIDGET ui_textarea(UiObject *obj, UiText *value); -UIWIDGET ui_textarea_nv(UiObject *obj, char *varname); +typedef struct UiTextAreaArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; -UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea); + UiText *value; + const char *varname; + ui_callback onchange; + void *onchangedata; + + const int *groups; +} UiTextAreaArgs; + +typedef struct UiTextFieldArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; -void ui_text_undo(UiText *value); -void ui_text_redo(UiText *value); + UiString* value; + const char *varname; + ui_callback onchange; + void *onchangedata; + ui_callback onactivate; + void *onactivatedata; + + const int *groups; +} UiTextFieldArgs; -UIWIDGET ui_textfield(UiObject *obj, UiString *value); -UIWIDGET ui_textfield_nv(UiObject *obj, char *varname); +typedef struct UiPathElmRet { + char *name; + size_t name_len; + char *path; + size_t path_len; +} UiPathElm; -UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value); -UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname); +typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void *data); -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); +typedef struct UiPathTextFieldArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiString *value; + const char *varname; + + ui_pathelm_func getpathelm; + void *getpathelmdata; + + ui_callback onactivate; + void *onactivatedata; + + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropsdata; +} UiPathTextFieldArgs; + +#define ui_textarea(obj, ...) ui_textarea_create(obj, (UiTextAreaArgs) { __VA_ARGS__ }) + +UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args); + +UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea); + +UIEXPORT void ui_text_undo(UiText *value); +UIEXPORT void ui_text_redo(UiText *value); + +#define ui_textfield(obj, ...) ui_textfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ }) +#define ui_frameless_textfield(obj, ...) ui_frameless_field_create(obj, (UiTextFieldArgs) { __VA_ARGS__ }) +#define ui_passwordfield(obj, ...) ui_passwordfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ }) +#define ui_path_textfield(obj, ...) ui_path_textfield_create(obj, (UiPathTextFieldArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args); +UIEXPORT UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args); +UIEXPORT UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args); + +UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args); #ifdef __cplusplus } diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h index f5b364d..0509321 100644 --- a/ui/ui/toolbar.h +++ b/ui/ui/toolbar.h @@ -36,23 +36,55 @@ 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); +typedef struct UiToolbarItemArgs { + const char* label; + const char* stockid; + const char* icon; + + ui_callback onclick; + void* onclickdata; + + const int *groups; +} UiToolbarItemArgs; + +typedef struct UiToolbarToggleItemArgs { + const char* label; + const char* stockid; + const char* icon; + + const char* varname; + ui_callback onchange; + void* onchangedata; + + const int *groups; +} UiToolbarToggleItemArgs; + +typedef struct UiToolbarMenuArgs { + const char* label; + const char* stockid; + const char* icon; +} UiToolbarMenuArgs; + +enum UiToolbarPos { + UI_TOOLBAR_LEFT = 0, + UI_TOOLBAR_CENTER, + UI_TOOLBAR_RIGHT +}; + +#define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, (UiToolbarItemArgs){ __VA_ARGS__ } ) +#define ui_toolbar_toggleitem(name, ...) ui_toolbar_toggleitem_create(name, (UiToolbarToggleItemArgs){ __VA_ARGS__ } ) + +#define ui_toolbar_menu(name, ...) for(ui_toolbar_menu_create(name, (UiToolbarMenuArgs){ __VA_ARGS__ });ui_menu_is_open();ui_menu_close()) +#define ui_toolbar_appmenu() for(ui_toolbar_menu_create(NULL, (UiToolbarMenuArgs){ 0 });ui_menu_is_open();ui_menu_close()) + + +UIEXPORT void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args); +UIEXPORT void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args); +UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args); + +UIEXPORT void ui_toolbar_add_default(const char *name, enum UiToolbarPos pos); + + #ifdef __cplusplus } diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index d967d45..d723909 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -33,22 +33,32 @@ #ifdef UI_COCOA +#include #ifdef __OBJC__ #import -#define UIWIDGET NSView* -#define UIMENU NSMenu* -#else -typedef void* UIWIDGET; -typedef void* UIMENU; #endif +typedef void* UIWIDGET; // NSView* +typedef void* UIWINDOW; // NSWindow* +typedef void* UIMENU; // NSMenu* -#elif UI_GTK2 || UI_GTK3 +#elif UI_GTK2 || UI_GTK3 || UI_GTK4 #include #define UIWIDGET GtkWidget* + +#if UI_GTK2 || UI_GTK3 #define UIMENU GtkMenu* +#endif +#ifdef UI_GTK4 +#define UIMENU GtkPopoverMenu* +#endif + #define UI_GTK +#ifdef UI_LIBADWAITA +#include +#endif + #elif UI_MOTIF #include @@ -67,9 +77,58 @@ typedef void* UIMENU; #define UIMENU void* #endif -#elif UI_WPF +#elif UI_WINUI + +#define UIEXPORT __declspec(dllexport) + +#ifdef __cplusplus + +#include +#ifndef UI_WINUI_PCH +#include +#undef GetCurrentTime +#include +#include +#include +#endif + +class UiWindow { +public: + winrt::Microsoft::UI::Xaml::Window window { nullptr }; + + UiWindow(winrt::Microsoft::UI::Xaml::Window& win); +}; + +class UiWidget { +public: + winrt::Microsoft::UI::Xaml::UIElement uielement; + void* data1 = nullptr; + void* data2 = nullptr; + void* data3 = nullptr; + std::function Show; + UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm); + +}; + +#define UIWIDGET UiWidget* +#define UIWINDOW UiWindow* +#define UIMENU void* + +/* +// winrt::Microsoft::UI::Xaml::UIElement #define UIWIDGET void* +// winrt::Microsoft::UI::Xaml::Window +#define UIWINDOW void* #define UIMENU void* +*/ + +#else +#define UIWIDGET void* +#define UIWINDOW void* +#define UIMENU void* +#endif + + #endif #ifndef TRUE @@ -79,16 +138,23 @@ typedef void* UIMENU; #define FALSE 0 #endif +#ifndef UIEXPORT +#define UIEXPORT +#endif + #ifdef __cplusplus extern "C" { #endif #define UI_GROUP_SELECTION 20000 + +#define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 } /* public types */ -typedef int UiBool; +typedef _Bool UiBool; typedef struct UiObject UiObject; +typedef struct UiContainerX UiContainerX; typedef struct UiEvent UiEvent; typedef struct UiMouseEvent UiMouseEvent; typedef struct UiObserver UiObserver; @@ -99,24 +165,39 @@ typedef struct UiString UiString; typedef struct UiText UiText; typedef struct UiList UiList; typedef struct UiRange UiRange; +typedef struct UiGeneric UiGeneric; typedef struct UiStr UiStr; +typedef struct UiFileList UiFileList; + +typedef struct UiListSelection UiListSelection; + /* begin opaque types */ -typedef struct UiContext UiContext; -typedef struct UiContainer UiContainer; +typedef struct UiContext UiContext; +typedef struct UiContainer UiContainer; +typedef struct UiMenuBuilder UiMenuBuilder; -typedef struct UiIcon UiIcon; -typedef struct UiImage UiImage; +typedef struct UiIcon UiIcon; +typedef struct UiImage UiImage; -typedef struct UiSelection UiSelection; +typedef struct UiDnD UiDnD; + +typedef struct UiThreadpool UiThreadpool; /* end opaque types */ typedef struct UiTabbedPane UiTabbedPane; +typedef enum UiTri UiTri; +typedef enum UiLabelType UiLabelType; + +typedef enum UiDnDAction UiDnDAction; + enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 }; +enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON }; +enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM }; typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */ @@ -126,13 +207,20 @@ typedef int(*ui_threadfunc)(void*); typedef void(*ui_freefunc)(void*); -typedef void(*ui_enablefunc)(void*, UiBool); +typedef void(*ui_enablefunc)(void*, int); struct UiObject { /* * native widget */ UIWIDGET widget; + +#if defined(UI_COCOA) || defined(UI_WINUI) + /* + * native window object + */ + UIWINDOW wobj; +#endif /* * user window data @@ -145,14 +233,31 @@ struct UiObject { UiContext *ctx; /* - * container interface + * container interface (deprecated) */ UiContainer *container; + /* + * container list + * TODO: remove old UiContainer and rename UiContainerX to UiContainer + */ + UiContainerX *container_begin; + UiContainerX *container_end; + /* * next container object */ UiObject *next; + + /* + * obj destroy func + */ + void (*destroy)(UiObject *obj); + + /* + * reference counter + */ + unsigned int ref; }; struct UiTabbedPane { @@ -218,7 +323,7 @@ struct UiDouble { struct UiString { char* (*get)(UiString*); - void (*set)(UiString*, char*); + void (*set)(UiString*, const char*); void *obj; UiStr value; @@ -226,7 +331,7 @@ struct UiString { }; struct UiText { - void (*set)(UiText*, char*); + void (*set)(UiText*, const char*); char* (*get)(UiText*); char* (*getsubstr)(UiText*, int, int); /* text, begin, end */ void (*insert)(UiText*, int, char*); @@ -242,6 +347,17 @@ struct UiText { // TODO: replacefunc, ... UiObserver *observers; }; + +struct UiGeneric { + void* (*get)(UiGeneric*); + const char* (*get_type)(UiGeneric*); + int (*set)(UiGeneric*, void *, const char *type); + void *obj; + + void *value; + const char *type; + UiObserver *observers; +}; /* * abstract list @@ -260,8 +376,10 @@ struct UiList { /* private - implementation dependent */ void *data; - /* binding function */ + /* binding functions */ void (*update)(UiList *list, int i); + UiListSelection (*getselection)(UiList *list); + void (*setselection)(UiList *list, UiListSelection selection); /* binding object */ void *obj; @@ -269,6 +387,19 @@ struct UiList { UiObserver *observers; }; + +struct UiListSelection { + /* + * number of selected items + */ + int count; + + /* + * indices of selected rows + */ + int *rows; +}; + struct UiRange { double (*get)(UiRange *range); void (*set)(UiRange *range, double value); @@ -283,87 +414,169 @@ struct UiRange { UiObserver *observers; }; +enum UiTri { + UI_DEFAULT = 0, + UI_ON, + UI_OFF +}; + +struct UiFileList { + char **files; + size_t nfiles; +}; + +typedef struct UiCondVar { + void *data; + int intdata; +} UiCondVar; + + +UIEXPORT void ui_init(const char *appname, int argc, char **argv); +UIEXPORT const char* ui_appname(); -void ui_init(char *appname, int argc, char **argv); -char* ui_appname(); +UIEXPORT UiContext* ui_global_context(void); -UiContext* ui_global_context(void); +UIEXPORT void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata); -void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata); +UIEXPORT void ui_context_destroy(UiContext *ctx); -void ui_onstartup(ui_callback f, void *userdata); -void ui_onopen(ui_callback f, void *userdata); -void ui_onexit(ui_callback f, void *userdata); +UIEXPORT void ui_object_ref(UiObject *obj); +UIEXPORT int ui_object_unref(UiObject *obj); -void ui_main(); -void ui_show(UiObject *obj); -void ui_close(UiObject *obj); +UIEXPORT void ui_onstartup(ui_callback f, void *userdata); +UIEXPORT void ui_onopen(ui_callback f, void *userdata); +UIEXPORT void ui_onexit(ui_callback f, void *userdata); -void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd); +UIEXPORT void ui_main(void); +UIEXPORT void ui_show(UiObject *obj); +UIEXPORT void ui_close(UiObject *obj); -void* ui_document_new(size_t size); -void ui_document_destroy(void *doc); +UIEXPORT void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd); +UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td); +UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads); +UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool); +UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd); -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 +UIEXPORT void* ui_document_new(size_t size); +UIEXPORT void ui_document_destroy(void *doc); -UiContext* ui_document_context(void *doc); +UIEXPORT void ui_set_document(UiObject *obj, void *document); // deprecated +UIEXPORT void ui_detach_document(UiObject *obj); // deprecated +UIEXPORT void* ui_get_document(UiObject *obj); // deprecated +UIEXPORT void ui_set_subdocument(void *document, void *sub); // deprecated +UIEXPORT void ui_detach_subdocument(void *document, void *sub); // deprecated +UIEXPORT void* ui_get_subdocument(void *document); // deprecated -void ui_attach_document(UiContext *ctx, void *document); -void ui_detach_document2(UiContext *ctx, void *document); +UIEXPORT UiContext* ui_document_context(void *doc); -void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_attach_document(UiContext *ctx, void *document); +UIEXPORT void ui_detach_document2(UiContext *ctx, void *document); -void ui_set_group(UiContext *ctx, int group); -void ui_unset_group(UiContext *ctx, int group); -int* ui_active_groups(UiContext *ctx, int *ngroups); +UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); + +UIEXPORT void ui_set_group(UiContext *ctx, int group); +UIEXPORT void ui_unset_group(UiContext *ctx, int group); +UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups); -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); +UIEXPORT void* ui_allocator(UiContext *ctx); +UIEXPORT void* ui_cx_mempool(UiContext *ctx); + +UIEXPORT void* ui_malloc(UiContext *ctx, size_t size); +UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize); +UIEXPORT void ui_free(UiContext *ctx, void *ptr); +UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size); +UIEXPORT char* ui_strdup(UiContext *ctx, const char *str); // types -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); +UIEXPORT UiInteger* ui_int_new(UiContext *ctx, char *name); +UIEXPORT UiDouble* ui_double_new(UiContext *ctx, char *name); +UIEXPORT UiString* ui_string_new(UiContext *ctx, char *name); +UIEXPORT UiText* ui_text_new(UiContext *ctx, char *name); +UIEXPORT UiRange* ui_range_new(UiContext *ctx, char *name); +UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name); + +#define ui_get(v) _Generic(v, \ + UiInteger*: ui_int_get, \ + UiDouble*: ui_double_get, \ + UiString*: ui_string_get, \ + UiText*:ui_text_get) (v) + +#define ui_set(v, n) _Generic(v, \ + UiInteger*: ui_int_set, \ + UiDouble*: ui_double_set, \ + UiString*: ui_string_set, \ + UiText*:ui_text_set) (v, n) + +UIEXPORT void ui_int_set(UiInteger *i, int64_t value); +UIEXPORT int64_t ui_int_get(UiInteger *i); +UIEXPORT void ui_double_set(UiDouble *d, double value); +UIEXPORT double ui_double_get(UiDouble *d); +UIEXPORT void ui_string_set(UiString *s, const char *value); +UIEXPORT char* ui_string_get(UiString *s); +UIEXPORT void ui_text_set(UiText *s, const char* value); +UIEXPORT char* ui_text_get(UiText *s); + +UIEXPORT void ui_var_set_int(UiContext *ctx, const char *name, int64_t value); +UIEXPORT int64_t ui_var_get_int(UiContext *ctx, const char *name); +UIEXPORT void ui_var_set_double(UiContext *ctx, const char *name, double value); +UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name); +UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value); +UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name); + +UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data); +UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer); +UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data); +UIEXPORT void ui_notify(UiObserver *observer, void *data); +UIEXPORT void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data); +UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event); + + +UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name); +UIEXPORT void* ui_list_first(UiList *list); +UIEXPORT void* ui_list_next(UiList *list); +UIEXPORT void* ui_list_get(UiList *list, int i); +UIEXPORT int ui_list_count(UiList *list); +UIEXPORT void ui_list_append(UiList *list, void *data); +UIEXPORT void ui_list_prepend(UiList *list, void *data); +UIEXPORT void ui_list_remove(UiList *list, int i); +UIEXPORT void ui_list_clear(UiList *list); +UIEXPORT void ui_list_update(UiList *list); +UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data); +UIEXPORT void ui_list_notify(UiList *list); + +UIEXPORT UiListSelection ui_list_getselection(UiList *list); +UIEXPORT void ui_list_setselection(UiList *list, int index); + +UIEXPORT UiFileList ui_filelist_copy(UiFileList list); +UIEXPORT void ui_filelist_free(UiFileList list); + +UIEXPORT void ui_clipboard_set(char *str); +UIEXPORT char* ui_clipboard_get(); + +UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove? -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); +// general widget functions +UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled); +UIEXPORT void ui_set_show_all(UIWIDGET widget, int value); +UIEXPORT void ui_set_visible(UIWIDGET widget, int visible); -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(); +UIEXPORT void ui_listselection_free(UiListSelection selection); -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); +UIEXPORT UiStr ui_str(char *cstr); +UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v)); + + +UIEXPORT char* ui_getappdir(void); +UIEXPORT char* ui_configfile(char *name); +UIEXPORT UiCondVar* ui_condvar_create(void); +UIEXPORT void ui_condvar_wait(UiCondVar *var); +UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata); +UIEXPORT void ui_condvar_destroy(UiCondVar *var); #ifdef __cplusplus } diff --git a/ui/ui/tree.h b/ui/ui/tree.h index 1bb603d..28c9f31 100644 --- a/ui/ui/tree.h +++ b/ui/ui/tree.h @@ -35,15 +35,23 @@ extern "C" { #endif -typedef struct UiModel UiModel; -typedef struct UiListCallbacks UiListCallbacks; -typedef struct UiListSelection UiListSelection; +typedef struct UiModel UiModel; +typedef struct UiListCallbacks UiListCallbacks; +typedef struct UiListDnd UiListDnd; + +typedef struct UiListArgs UiListArgs; +typedef struct UiSourceListArgs UiSourceListArgs; + +typedef struct UiSubList UiSubList; +typedef struct UiSubListItem UiSubListItem; typedef enum UiModelType { UI_STRING = 0, + UI_STRING_FREE, UI_INTEGER, UI_ICON, UI_ICON_TEXT, + UI_ICON_TEXT_FREE } UiModelType; struct UiModel { @@ -64,6 +72,11 @@ struct UiModel { */ char **titles; + /* + * array of column size hints + */ + int *columnsize; + /* * function for translating model data to view data * first argument is the pointer returned by UiList->get or UiTree->get @@ -71,12 +84,6 @@ struct UiModel { * 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 { @@ -96,36 +103,129 @@ struct UiListCallbacks { void *userdata; }; -struct UiListSelection { +struct UiListArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiList* list; + const char* varname; + UiModel* model; + ui_getvaluefunc getvalue; + ui_callback onactivate; + void* onactivatedata; + ui_callback onselection; + void* onselectiondata; + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropsdata; + UiBool multiselection; + UiMenuBuilder *contextmenu; + + const int *groups; +}; + +typedef void (*ui_sublist_getvalue_func)(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); + +struct UiSubList { + UiList *value; + const char *varname; + const char *header; + UiBool separator; + void *userdata; +}; + +/* + * list item members must be filled by the sublist getvalue func + * all members must be allocated (by malloc, strdup, ...) the pointer + * will be passed to free + */ +struct UiSubListItem { + char *icon; + char *label; + char *button_icon; + char *button_label; + char *badge; + void *eventdata; +}; + +struct UiSourceListArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const int *groups; + + /* + * list of sublists + * a sublist must have a varname or a value + * + * the last entry in the list must contain all NULL values or numsublists + * must contain the number of sublists + */ + UiSubList *sublists; + /* + * optional number of sublists + * if the value is 0, it is assumed, that sublists is null-terminated + * (last item contains only NULL values) + */ + size_t numsublists; + /* - * number of selected items + * callback for each list item, that should fill all necessary + * UiSubListItem fields */ - int count; + ui_sublist_getvalue_func getvalue; /* - * indices of selected rows + * activated when a list item is selected */ - int *rows; + ui_callback onactivate; + void *onactivatedata; + + /* + * activated, when the additional list item button is clicked + */ + ui_callback onbuttonclick; + void *onbuttonclickdata; }; -UiModel* ui_model(UiContext *ctx, ...); -void ui_model_free(UiContext *ctx, UiModel *mi); +#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } +#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } + + +UIEXPORT UiModel* ui_model(UiContext *ctx, ...); +UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); +UIEXPORT 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); +#define ui_listview(obj, ...) ui_listview_create(obj, (UiListArgs) { __VA_ARGS__ } ) +#define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } ) +#define ui_combobox(obj, ...) ui_combobox_create(obj, (UiListArgs) { __VA_ARGS__ } ) +#define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, (UiListArgs) { __VA_ARGS__ } ) +#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, (UiSourceListArgs) { __VA_ARGS__ } ) -UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb); -UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb); +UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args); +UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args); +UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args); +UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args); -void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...); -void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm); -void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...); -void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm); +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args); -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 } diff --git a/ui/ui/ui.h b/ui/ui/ui.h index a7f7030..a0311e9 100644 --- a/ui/ui/ui.h +++ b/ui/ui/ui.h @@ -45,6 +45,7 @@ #include "image.h" #include "display.h" #include "dnd.h" +#include "icons.h" #endif /* UI_H */ diff --git a/ui/ui/window.h b/ui/ui/window.h index 93bebe9..352abb7 100644 --- a/ui/ui/window.h +++ b/ui/ui/window.h @@ -35,11 +35,61 @@ extern "C" { #endif -UiObject* ui_window(char *title, void *window_data); -UiObject* ui_simplewindow(char *title, void *window_data); +#define UI_FILEDIALOG_SELECT_SINGLE 0 +#define UI_FILEDIALOG_SELECT_MULTI 1 +#define UI_FILEDIALOG_SELECT_FOLDER 2 + +typedef struct UiDialogArgs { + const char *title; + const char *content; + const char *button1_label; + const char *button2_label; + const char *closebutton_label; + const char *input_value; + UiBool input; + UiBool password; + ui_callback result; + void *resultdata; +} UiDialogArgs; + +typedef struct UiDialogWindowArgs { + UiTri modal; + UiTri titlebar_buttons; + UiTri show_closebutton; + const char *title; + const char *lbutton1; + const char *lbutton2; + const char *rbutton3; + const char *rbutton4; + const int *lbutton1_groups; + const int *lbutton2_groups; + const int *rbutton3_groups; + const int *rbutton4_groups; + int default_button; + int width; + int height; + ui_callback onclick; + void *onclickdata; +} UiDialogWindowArgs; + +UIEXPORT UiObject *ui_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args); + +#define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ }); +#define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 }); + +UIEXPORT void ui_window_size(UiObject *obj, int width, int height); + +#define ui_dialog(parent, ...) ui_dialog_create(parent, (UiDialogArgs){ __VA_ARGS__ } ) + +UIEXPORT void ui_dialog_create(UiObject *parent, UiDialogArgs args); + +UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata); +UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata); + -char* ui_openfiledialog(UiObject *obj); -char* ui_savefiledialog(UiObject *obj); #ifdef __cplusplus } -- 2.43.5